Merge "Add a 'unbounded method refs only' clause to PooledLambda javadoc"
diff --git a/Android.bp b/Android.bp
index a5bbb18..2dcbc92 100644
--- a/Android.bp
+++ b/Android.bp
@@ -215,7 +215,6 @@
         "core/java/android/os/IDeviceIdleController.aidl",
         "core/java/android/os/IHardwarePropertiesManager.aidl",
         "core/java/android/os/IIncidentManager.aidl",
-        "core/java/android/os/IIncidentReportCompletedListener.aidl",
         "core/java/android/os/IIncidentReportStatusListener.aidl",
         "core/java/android/os/IMaintenanceActivityListener.aidl",
         "core/java/android/os/IMessenger.aidl",
@@ -474,6 +473,7 @@
         "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
+	"telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
         "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl",
@@ -494,13 +494,11 @@
         "telephony/java/com/android/ims/internal/IImsService.aidl",
         "telephony/java/com/android/ims/internal/IImsServiceController.aidl",
         "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl",
-        "telephony/java/com/android/ims/internal/IImsSmsFeature.aidl",
         "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl",
         "telephony/java/com/android/ims/internal/IImsUt.aidl",
         "telephony/java/com/android/ims/internal/IImsUtListener.aidl",
         "telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl",
-        "telephony/java/com/android/ims/internal/ISmsListener.aidl",
         "telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl",
         "telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl",
         "telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl",
@@ -635,7 +633,7 @@
     name: "framework-statslog-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out)",
-    out: ["android/util/StatsLog.java"],
+    out: ["android/util/StatsLogInternal.java"],
 }
 
 gensrcs {
@@ -671,6 +669,7 @@
         "libphonenumber-platform",
         "nist-sip",
         "tagsoup",
+        "rappor",
     ],
     dxflags: ["--core-library"],
 }
@@ -693,7 +692,6 @@
     srcs: [
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
-        "tools/streaming_proto/stream.proto",
     ],
 
     target: {
diff --git a/Android.mk b/Android.mk
index 1035362..1f37326 100644
--- a/Android.mk
+++ b/Android.mk
@@ -274,8 +274,8 @@
   ../opt/net/voip/src/java/android/net/rtp \
   ../opt/net/voip/src/java/android/net/sip \
 
-framework_base_android_test_mock_src_files := \
-  $(call all-java-files-under, test-mock/src/android/test/mock)
+framework_base_android_test_base_src_files := \
+  $(call all-java-files-under, test-base/src/junit)
 
 framework_base_android_test_runner_src_files := \
   $(call all-java-files-under, test-runner/src/junit)
@@ -284,7 +284,6 @@
 # to document and check apis
 files_to_check_apis := \
   $(call find-other-java-files, \
-    test-base/src \
     $(non_base_dirs) \
   )
 
@@ -308,6 +307,8 @@
 files_to_document := \
   $(files_to_check_apis) \
   $(call find-other-java-files,\
+    test-base/src \
+    test-mock/src \
     test-runner/src)
 
 # These are relative to frameworks/base
@@ -327,7 +328,7 @@
 
 # These are relative to frameworks/base
 framework_docs_LOCAL_API_CHECK_SRC_FILES := \
-  $(framework_base_android_test_mock_src_files) \
+  $(framework_base_android_test_base_src_files) \
   $(framework_base_android_test_runner_src_files) \
   $(files_to_check_apis) \
   $(common_src_files) \
@@ -355,7 +356,6 @@
 	icu4j \
 	framework \
 	voip-common \
-	android.test.mock \
 
 # Platform docs can refer to Support Library APIs, but we don't actually build
 # them as part of the docs target, so we need to include them on the classpath.
@@ -999,7 +999,6 @@
     -Iexternal/protobuf/src
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 LOCAL_SRC_FILES := \
-    tools/streaming_proto/stream.proto \
     cmds/am/proto/instrumentation_data.proto \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto) \
diff --git a/api/current.txt b/api/current.txt
index 8a09b82..c537622 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3953,7 +3953,7 @@
     field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR;
     field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190
     field public static final int IMPORTANCE_CACHED = 400; // 0x190
-    field public static final int IMPORTANCE_CANT_SAVE_STATE = 270; // 0x10e
+    field public static final int IMPORTANCE_CANT_SAVE_STATE = 350; // 0x15e
     field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4
     field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64
     field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d
@@ -3961,7 +3961,8 @@
     field public static final int IMPORTANCE_PERCEPTIBLE = 230; // 0xe6
     field public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130; // 0x82
     field public static final int IMPORTANCE_SERVICE = 300; // 0x12c
-    field public static final int IMPORTANCE_TOP_SLEEPING = 150; // 0x96
+    field public static final int IMPORTANCE_TOP_SLEEPING = 325; // 0x145
+    field public static final deprecated int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150; // 0x96
     field public static final int IMPORTANCE_VISIBLE = 200; // 0xc8
     field public static final int REASON_PROVIDER_IN_USE = 1; // 0x1
     field public static final int REASON_SERVICE_IN_USE = 2; // 0x2
@@ -5199,6 +5200,7 @@
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
     field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+    field public static final java.lang.String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
     field public static final deprecated java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
     field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
@@ -5482,7 +5484,9 @@
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
+    method public boolean isGroupConversation();
     method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
+    method public android.app.Notification.MessagingStyle setGroupConversation(boolean);
     field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
   }
 
@@ -6364,6 +6368,7 @@
     method public int getOrganizationColor(android.content.ComponentName);
     method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
+    method public java.lang.String getPasswordBlacklistName(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -6450,6 +6455,7 @@
     method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
     method public void setLockTaskFeatures(android.content.ComponentName, int);
@@ -6463,6 +6469,7 @@
     method public void setOrganizationColor(android.content.ComponentName, int);
     method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence);
     method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean);
+    method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
     method public void setPasswordHistoryLength(android.content.ComponentName, int);
     method public void setPasswordMinimumLength(android.content.ComponentName, int);
@@ -6643,6 +6650,7 @@
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.Object getData();
+    method public long getId();
     method public int getTag();
     method public long getTimeNanos();
     method public void writeToParcel(android.os.Parcel, int);
@@ -7020,6 +7028,7 @@
     method public android.net.Uri getUri();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+    field public static final java.lang.String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
     field public static final java.lang.String HINT_ACTIONS = "actions";
     field public static final java.lang.String HINT_HORIZONTAL = "horizontal";
     field public static final java.lang.String HINT_LARGE = "large";
@@ -7029,12 +7038,15 @@
     field public static final java.lang.String HINT_NO_TINT = "no_tint";
     field public static final java.lang.String HINT_PARTIAL = "partial";
     field public static final java.lang.String HINT_SELECTED = "selected";
+    field public static final java.lang.String HINT_SHORTCUT = "shortcut";
     field public static final java.lang.String HINT_SUMMARY = "summary";
     field public static final java.lang.String HINT_TITLE = "title";
     field public static final java.lang.String SUBTYPE_COLOR = "color";
     field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+    field public static final java.lang.String SUBTYPE_PRIORITY = "priority";
     field public static final java.lang.String SUBTYPE_SLIDER = "slider";
     field public static final java.lang.String SUBTYPE_SOURCE = "source";
+    field public static final java.lang.String SUBTYPE_TOGGLE = "toggle";
   }
 
   public static class Slice.Builder {
@@ -8219,7 +8231,7 @@
     method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
     method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
     method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
-    method public void onIntrData(android.bluetooth.BluetoothDevice, byte, byte[]);
+    method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
     method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
     method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
     method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
@@ -11082,6 +11094,7 @@
     field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
     field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
     field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+    field public static final java.lang.String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
     field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television";
     field public static final java.lang.String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final java.lang.String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
@@ -11404,7 +11417,7 @@
     method public android.graphics.drawable.Drawable getProfileSwitchingIcon(android.os.UserHandle);
     method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
     method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
-    method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
   }
 
 }
@@ -15475,6 +15488,7 @@
     method public <T> T get(android.hardware.camera2.CameraCharacteristics.Key<T>);
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys();
     method public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys();
+    method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys();
     method public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys();
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
@@ -15573,6 +15587,7 @@
     method public abstract void close();
     method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
@@ -16116,6 +16131,20 @@
     field public static final int RED = 0; // 0x0
   }
 
+  public final class SessionConfiguration {
+    ctor public SessionConfiguration(int, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler);
+    method public android.os.Handler getHandler();
+    method public android.hardware.camera2.params.InputConfiguration getInputConfiguration();
+    method public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
+    method public android.hardware.camera2.CaptureRequest getSessionParameters();
+    method public int getSessionType();
+    method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback();
+    method public void setInputConfiguration(android.hardware.camera2.params.InputConfiguration);
+    method public void setSessionParameters(android.hardware.camera2.CaptureRequest);
+    field public static final int SESSION_HIGH_SPEED = 1; // 0x1
+    field public static final int SESSION_REGULAR = 0; // 0x0
+  }
+
   public final class StreamConfigurationMap {
     method public android.util.Size[] getHighResolutionOutputSizes(int);
     method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRanges();
@@ -16641,6 +16670,17 @@
     field public static final int KNOTTED_HEH = 21; // 0x15
     field public static final int LAM = 22; // 0x16
     field public static final int LAMADH = 23; // 0x17
+    field public static final int MALAYALAM_BHA = 89; // 0x59
+    field public static final int MALAYALAM_JA = 90; // 0x5a
+    field public static final int MALAYALAM_LLA = 91; // 0x5b
+    field public static final int MALAYALAM_LLLA = 92; // 0x5c
+    field public static final int MALAYALAM_NGA = 93; // 0x5d
+    field public static final int MALAYALAM_NNA = 94; // 0x5e
+    field public static final int MALAYALAM_NNNA = 95; // 0x5f
+    field public static final int MALAYALAM_NYA = 96; // 0x60
+    field public static final int MALAYALAM_RA = 97; // 0x61
+    field public static final int MALAYALAM_SSA = 98; // 0x62
+    field public static final int MALAYALAM_TTA = 99; // 0x63
     field public static final int MANICHAEAN_ALEPH = 58; // 0x3a
     field public static final int MANICHAEAN_AYIN = 59; // 0x3b
     field public static final int MANICHAEAN_BETH = 60; // 0x3c
@@ -16896,6 +16936,8 @@
     field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D_ID = 209; // 0xd1
     field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E;
     field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E_ID = 256; // 0x100
+    field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F;
+    field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F_ID = 274; // 0x112
     field public static final int CJK_UNIFIED_IDEOGRAPHS_ID = 71; // 0x47
     field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS;
     field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS_EXTENDED;
@@ -17041,6 +17083,8 @@
     field public static final int JAVANESE_ID = 181; // 0xb5
     field public static final android.icu.lang.UCharacter.UnicodeBlock KAITHI;
     field public static final int KAITHI_ID = 193; // 0xc1
+    field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_EXTENDED_A;
+    field public static final int KANA_EXTENDED_A_ID = 275; // 0x113
     field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_SUPPLEMENT;
     field public static final int KANA_SUPPLEMENT_ID = 203; // 0xcb
     field public static final android.icu.lang.UCharacter.UnicodeBlock KANBUN;
@@ -17113,6 +17157,8 @@
     field public static final int MANICHAEAN_ID = 234; // 0xea
     field public static final android.icu.lang.UCharacter.UnicodeBlock MARCHEN;
     field public static final int MARCHEN_ID = 268; // 0x10c
+    field public static final android.icu.lang.UCharacter.UnicodeBlock MASARAM_GONDI;
+    field public static final int MASARAM_GONDI_ID = 276; // 0x114
     field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_ALPHANUMERIC_SYMBOLS;
     field public static final int MATHEMATICAL_ALPHANUMERIC_SYMBOLS_ID = 93; // 0x5d
     field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_OPERATORS;
@@ -17172,6 +17218,8 @@
     field public static final android.icu.lang.UCharacter.UnicodeBlock NO_BLOCK;
     field public static final android.icu.lang.UCharacter.UnicodeBlock NUMBER_FORMS;
     field public static final int NUMBER_FORMS_ID = 45; // 0x2d
+    field public static final android.icu.lang.UCharacter.UnicodeBlock NUSHU;
+    field public static final int NUSHU_ID = 277; // 0x115
     field public static final android.icu.lang.UCharacter.UnicodeBlock OGHAM;
     field public static final int OGHAM_ID = 34; // 0x22
     field public static final android.icu.lang.UCharacter.UnicodeBlock OLD_HUNGARIAN;
@@ -17250,6 +17298,8 @@
     field public static final int SMALL_FORM_VARIANTS_ID = 84; // 0x54
     field public static final android.icu.lang.UCharacter.UnicodeBlock SORA_SOMPENG;
     field public static final int SORA_SOMPENG_ID = 218; // 0xda
+    field public static final android.icu.lang.UCharacter.UnicodeBlock SOYOMBO;
+    field public static final int SOYOMBO_ID = 278; // 0x116
     field public static final android.icu.lang.UCharacter.UnicodeBlock SPACING_MODIFIER_LETTERS;
     field public static final int SPACING_MODIFIER_LETTERS_ID = 6; // 0x6
     field public static final android.icu.lang.UCharacter.UnicodeBlock SPECIALS;
@@ -17282,6 +17332,8 @@
     field public static final int SYLOTI_NAGRI_ID = 143; // 0x8f
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC;
     field public static final int SYRIAC_ID = 13; // 0xd
+    field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC_SUPPLEMENT;
+    field public static final int SYRIAC_SUPPLEMENT_ID = 279; // 0x117
     field public static final android.icu.lang.UCharacter.UnicodeBlock TAGALOG;
     field public static final int TAGALOG_ID = 98; // 0x62
     field public static final android.icu.lang.UCharacter.UnicodeBlock TAGBANWA;
@@ -17342,6 +17394,8 @@
     field public static final int YI_RADICALS_ID = 73; // 0x49
     field public static final android.icu.lang.UCharacter.UnicodeBlock YI_SYLLABLES;
     field public static final int YI_SYLLABLES_ID = 72; // 0x48
+    field public static final android.icu.lang.UCharacter.UnicodeBlock ZANABAZAR_SQUARE;
+    field public static final int ZANABAZAR_SQUARE_ID = 280; // 0x118
   }
 
   public static abstract interface UCharacter.WordBreak {
@@ -17492,6 +17546,11 @@
     field public static final int DIACRITIC = 7; // 0x7
     field public static final int DOUBLE_START = 12288; // 0x3000
     field public static final int EAST_ASIAN_WIDTH = 4100; // 0x1004
+    field public static final int EMOJI = 57; // 0x39
+    field public static final int EMOJI_COMPONENT = 61; // 0x3d
+    field public static final int EMOJI_MODIFIER = 59; // 0x3b
+    field public static final int EMOJI_MODIFIER_BASE = 60; // 0x3c
+    field public static final int EMOJI_PRESENTATION = 58; // 0x3a
     field public static final int EXTENDER = 8; // 0x8
     field public static final int FULL_COMPOSITION_EXCLUSION = 9; // 0x9
     field public static final int GENERAL_CATEGORY = 4101; // 0x1005
@@ -17539,8 +17598,10 @@
     field public static final int POSIX_GRAPH = 46; // 0x2e
     field public static final int POSIX_PRINT = 47; // 0x2f
     field public static final int POSIX_XDIGIT = 48; // 0x30
+    field public static final int PREPENDED_CONCATENATION_MARK = 63; // 0x3f
     field public static final int QUOTATION_MARK = 25; // 0x19
     field public static final int RADICAL = 26; // 0x1a
+    field public static final int REGIONAL_INDICATOR = 62; // 0x3e
     field public static final int SCRIPT = 4106; // 0x100a
     field public static final int SCRIPT_EXTENSIONS = 28672; // 0x7000
     field public static final int SEGMENT_STARTER = 41; // 0x29
@@ -17682,6 +17743,7 @@
     field public static final int MANDAIC = 84; // 0x54
     field public static final int MANICHAEAN = 121; // 0x79
     field public static final int MARCHEN = 169; // 0xa9
+    field public static final int MASARAM_GONDI = 175; // 0xaf
     field public static final int MATHEMATICAL_NOTATION = 128; // 0x80
     field public static final int MAYAN_HIEROGLYPHS = 85; // 0x55
     field public static final int MEITEI_MAYEK = 115; // 0x73
@@ -17736,6 +17798,7 @@
     field public static final int SINDHI = 145; // 0x91
     field public static final int SINHALA = 33; // 0x21
     field public static final int SORA_SOMPENG = 152; // 0x98
+    field public static final int SOYOMBO = 176; // 0xb0
     field public static final int SUNDANESE = 113; // 0x71
     field public static final int SYLOTI_NAGRI = 58; // 0x3a
     field public static final int SYMBOLS = 129; // 0x81
@@ -17766,6 +17829,7 @@
     field public static final int WESTERN_SYRIAC = 96; // 0x60
     field public static final int WOLEAI = 155; // 0x9b
     field public static final int YI = 41; // 0x29
+    field public static final int ZANABAZAR_SQUARE = 177; // 0xb1
   }
 
   public static final class UScript.ScriptUsage extends java.lang.Enum {
@@ -18560,11 +18624,14 @@
     method public android.icu.util.Currency getCurrency();
     method public java.lang.String getCurrencySymbol();
     method public char getDecimalSeparator();
+    method public java.lang.String getDecimalSeparatorString();
     method public char getDigit();
+    method public java.lang.String[] getDigitStrings();
     method public char[] getDigits();
     method public java.lang.String getExponentMultiplicationSign();
     method public java.lang.String getExponentSeparator();
     method public char getGroupingSeparator();
+    method public java.lang.String getGroupingSeparatorString();
     method public java.lang.String getInfinity();
     method public static android.icu.text.DecimalFormatSymbols getInstance();
     method public static android.icu.text.DecimalFormatSymbols getInstance(java.util.Locale);
@@ -18572,37 +18639,52 @@
     method public java.lang.String getInternationalCurrencySymbol();
     method public java.util.Locale getLocale();
     method public char getMinusSign();
+    method public java.lang.String getMinusSignString();
     method public char getMonetaryDecimalSeparator();
+    method public java.lang.String getMonetaryDecimalSeparatorString();
     method public char getMonetaryGroupingSeparator();
+    method public java.lang.String getMonetaryGroupingSeparatorString();
     method public java.lang.String getNaN();
     method public char getPadEscape();
     method public java.lang.String getPatternForCurrencySpacing(int, boolean);
     method public char getPatternSeparator();
     method public char getPerMill();
+    method public java.lang.String getPerMillString();
     method public char getPercent();
+    method public java.lang.String getPercentString();
     method public char getPlusSign();
+    method public java.lang.String getPlusSignString();
     method public char getSignificantDigit();
     method public android.icu.util.ULocale getULocale();
     method public char getZeroDigit();
     method public void setCurrency(android.icu.util.Currency);
     method public void setCurrencySymbol(java.lang.String);
     method public void setDecimalSeparator(char);
+    method public void setDecimalSeparatorString(java.lang.String);
     method public void setDigit(char);
+    method public void setDigitStrings(java.lang.String[]);
     method public void setExponentMultiplicationSign(java.lang.String);
     method public void setExponentSeparator(java.lang.String);
     method public void setGroupingSeparator(char);
+    method public void setGroupingSeparatorString(java.lang.String);
     method public void setInfinity(java.lang.String);
     method public void setInternationalCurrencySymbol(java.lang.String);
     method public void setMinusSign(char);
+    method public void setMinusSignString(java.lang.String);
     method public void setMonetaryDecimalSeparator(char);
+    method public void setMonetaryDecimalSeparatorString(java.lang.String);
     method public void setMonetaryGroupingSeparator(char);
+    method public void setMonetaryGroupingSeparatorString(java.lang.String);
     method public void setNaN(java.lang.String);
     method public void setPadEscape(char);
     method public void setPatternForCurrencySpacing(int, boolean, java.lang.String);
     method public void setPatternSeparator(char);
     method public void setPerMill(char);
+    method public void setPerMillString(java.lang.String);
     method public void setPercent(char);
+    method public void setPercentString(java.lang.String);
     method public void setPlusSign(char);
+    method public void setPlusSignString(java.lang.String);
     method public void setSignificantDigit(char);
     method public void setZeroDigit(char);
     field public static final int CURRENCY_SPC_CURRENCY_MATCH = 0; // 0x0
@@ -18623,7 +18705,9 @@
     enum_constant public static final android.icu.text.DisplayContext DIALECT_NAMES;
     enum_constant public static final android.icu.text.DisplayContext LENGTH_FULL;
     enum_constant public static final android.icu.text.DisplayContext LENGTH_SHORT;
+    enum_constant public static final android.icu.text.DisplayContext NO_SUBSTITUTE;
     enum_constant public static final android.icu.text.DisplayContext STANDARD_NAMES;
+    enum_constant public static final android.icu.text.DisplayContext SUBSTITUTE;
   }
 
   public static final class DisplayContext.Type extends java.lang.Enum {
@@ -18632,6 +18716,7 @@
     enum_constant public static final android.icu.text.DisplayContext.Type CAPITALIZATION;
     enum_constant public static final android.icu.text.DisplayContext.Type DIALECT_HANDLING;
     enum_constant public static final android.icu.text.DisplayContext.Type DISPLAY_LENGTH;
+    enum_constant public static final android.icu.text.DisplayContext.Type SUBSTITUTE_HANDLING;
   }
 
   public abstract class IDNA {
@@ -18739,6 +18824,7 @@
     method public static android.icu.text.MeasureFormat getInstance(java.util.Locale, android.icu.text.MeasureFormat.FormatWidth, android.icu.text.NumberFormat);
     method public final android.icu.util.ULocale getLocale();
     method public android.icu.text.NumberFormat getNumberFormat();
+    method public java.lang.String getUnitDisplayName(android.icu.util.MeasureUnit);
     method public android.icu.text.MeasureFormat.FormatWidth getWidth();
     method public final int hashCode();
     method public android.icu.util.Measure parseObject(java.lang.String, java.text.ParsePosition);
@@ -20277,6 +20363,7 @@
     field public static final android.icu.util.MeasureUnit MILLILITER;
     field public static final android.icu.util.MeasureUnit MILLIMETER;
     field public static final android.icu.util.MeasureUnit MILLIMETER_OF_MERCURY;
+    field public static final android.icu.util.MeasureUnit MILLIMOLE_PER_LITER;
     field public static final android.icu.util.MeasureUnit MILLISECOND;
     field public static final android.icu.util.MeasureUnit MILLIWATT;
     field public static final android.icu.util.TimeUnit MINUTE;
@@ -20288,6 +20375,7 @@
     field public static final android.icu.util.MeasureUnit OUNCE;
     field public static final android.icu.util.MeasureUnit OUNCE_TROY;
     field public static final android.icu.util.MeasureUnit PARSEC;
+    field public static final android.icu.util.MeasureUnit PART_PER_MILLION;
     field public static final android.icu.util.MeasureUnit PICOMETER;
     field public static final android.icu.util.MeasureUnit PINT;
     field public static final android.icu.util.MeasureUnit PINT_METRIC;
@@ -20411,6 +20499,9 @@
   public static final class TimeZone.SystemTimeZoneType extends java.lang.Enum {
     method public static android.icu.util.TimeZone.SystemTimeZoneType valueOf(java.lang.String);
     method public static final android.icu.util.TimeZone.SystemTimeZoneType[] values();
+    enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType ANY;
+    enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType CANONICAL;
+    enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType CANONICAL_LOCATION;
   }
 
   public final class ULocale implements java.lang.Comparable java.io.Serializable {
@@ -20612,6 +20703,7 @@
     field public static final android.icu.util.VersionInfo ICU_VERSION;
     field public static final android.icu.util.VersionInfo UCOL_BUILDER_VERSION;
     field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION;
+    field public static final android.icu.util.VersionInfo UNICODE_10_0;
     field public static final android.icu.util.VersionInfo UNICODE_1_0;
     field public static final android.icu.util.VersionInfo UNICODE_1_0_1;
     field public static final android.icu.util.VersionInfo UNICODE_1_1_0;
@@ -21284,6 +21376,8 @@
     method public void clearTestProviderStatus(java.lang.String);
     method public java.util.List<java.lang.String> getAllProviders();
     method public java.lang.String getBestProvider(android.location.Criteria, boolean);
+    method public java.lang.String getGnssHardwareModelName();
+    method public int getGnssYearOfHardware();
     method public deprecated android.location.GpsStatus getGpsStatus(android.location.GpsStatus);
     method public android.location.Location getLastKnownLocation(java.lang.String);
     method public android.location.LocationProvider getProvider(java.lang.String);
@@ -21319,6 +21413,7 @@
     method public void unregisterGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback);
     method public void unregisterGnssNavigationMessageCallback(android.location.GnssNavigationMessage.Callback);
     method public void unregisterGnssStatusCallback(android.location.GnssStatus.Callback);
+    field public static final java.lang.String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
     field public static final java.lang.String GPS_PROVIDER = "gps";
     field public static final java.lang.String KEY_LOCATION_CHANGED = "location";
     field public static final java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled";
@@ -21571,7 +21666,9 @@
     method public int getRingerMode();
     method public deprecated int getRouting(int);
     method public int getStreamMaxVolume(int);
+    method public int getStreamMinVolume(int);
     method public int getStreamVolume(int);
+    method public float getStreamVolumeDb(int, int, int);
     method public deprecated int getVibrateSetting(int);
     method public deprecated boolean isBluetoothA2dpOn();
     method public boolean isBluetoothScoAvailableOffCall();
@@ -26154,7 +26251,7 @@
     method public static android.net.MacAddress fromString(java.lang.String);
     method public boolean isLocallyAssigned();
     method public byte[] toByteArray();
-    method public java.lang.String toSafeString();
+    method public java.lang.String toOuiString();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.net.MacAddress BROADCAST_ADDRESS;
     field public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
@@ -32192,10 +32289,12 @@
     method public deprecated void setUserRestrictions(android.os.Bundle);
     method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
+    method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
     field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
+    field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
     field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
@@ -32205,6 +32304,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
     field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
     field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+    field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -33713,6 +33813,7 @@
     field public static final java.lang.String FEATURES = "features";
     field public static final int FEATURES_HD_CALL = 4; // 0x4
     field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
+    field public static final int FEATURES_RTT = 16; // 0x10
     field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final int FEATURES_WIFI = 8; // 0x8
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
@@ -37620,6 +37721,22 @@
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews);
   }
 
+  public final class EditDistanceScorer implements android.os.Parcelable android.service.autofill.Scorer {
+    method public int describeContents();
+    method public static android.service.autofill.EditDistanceScorer getInstance();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR;
+  }
+
+  public final class FieldClassification {
+    method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
+  }
+
+  public static final class FieldClassification.Match {
+    method public java.lang.String getRemoteId();
+    method public float getScore();
+  }
+
   public final class FillCallback {
     method public void onFailure(java.lang.CharSequence);
     method public void onSuccess(android.service.autofill.FillResponse);
@@ -37645,6 +37762,7 @@
     method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Map<android.view.autofill.AutofillId, android.service.autofill.FieldClassification> getFieldsClassification();
     method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
     method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
     method public java.util.Set<java.lang.String> getSelectedDatasetIds();
@@ -37682,6 +37800,7 @@
     method public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFieldClassificationIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setFooter(android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setHeader(android.widget.RemoteViews);
@@ -37765,6 +37884,9 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
   }
 
+  public abstract interface Scorer {
+  }
+
   public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
     ctor public TextValueSanitizer(java.util.regex.Pattern, java.lang.String);
     method public int describeContents();
@@ -37775,6 +37897,22 @@
   public abstract interface Transformation {
   }
 
+  public final class UserData implements android.os.Parcelable {
+    method public int describeContents();
+    method public static int getMaxFieldClassificationIdsSize();
+    method public static int getMaxUserDataSize();
+    method public static int getMaxValueLength();
+    method public static int getMinValueLength();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.UserData> CREATOR;
+  }
+
+  public static final class UserData.Builder {
+    ctor public UserData.Builder(android.service.autofill.Scorer, java.lang.String, java.lang.String);
+    method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
+    method public android.service.autofill.UserData build();
+  }
+
   public abstract interface Validator {
   }
 
@@ -40239,6 +40377,7 @@
   public class CarrierConfigManager {
     method public android.os.PersistableBundle getConfig();
     method public android.os.PersistableBundle getConfigForSubId(int);
+    method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method public void notifyConfigChangedForSubId(int);
     field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
@@ -40569,19 +40708,19 @@
   }
 
   public class MbmsDownloadSession implements java.lang.AutoCloseable {
-    method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+    method public int cancelDownload(android.telephony.mbms.DownloadRequest);
     method public void close();
     method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
     method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
-    method public void download(android.telephony.mbms.DownloadRequest);
+    method public int download(android.telephony.mbms.DownloadRequest);
     method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
     method public java.io.File getTempFileRootDirectory();
     method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
-    method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+    method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
     method public void requestUpdateFileServices(java.util.List<java.lang.String>);
     method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
     method public void setTempFileRootDirectory(java.io.File);
-    method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
+    method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
     field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
     field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
     field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
@@ -40629,6 +40768,34 @@
     field public static final int UNKNOWN_RSSI = 99; // 0x63
   }
 
+  public class NetworkScan {
+    method public void stop() throws android.os.RemoteException;
+    field public static final int ERROR_INTERRUPTED = 10002; // 0x2712
+    field public static final int ERROR_INVALID_SCAN = 2; // 0x2
+    field public static final int ERROR_INVALID_SCANID = 10001; // 0x2711
+    field public static final int ERROR_MODEM_ERROR = 1; // 0x1
+    field public static final int ERROR_MODEM_UNAVAILABLE = 3; // 0x3
+    field public static final int ERROR_RADIO_INTERFACE_ERROR = 10000; // 0x2710
+    field public static final int ERROR_UNSUPPORTED = 4; // 0x4
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public final class NetworkScanRequest implements android.os.Parcelable {
+    ctor public NetworkScanRequest(int, android.telephony.RadioAccessSpecifier[], int, int, boolean, int, java.util.ArrayList<java.lang.String>);
+    method public int describeContents();
+    method public boolean getIncrementalResults();
+    method public int getIncrementalResultsPeriodicity();
+    method public int getMaxSearchTime();
+    method public java.util.ArrayList<java.lang.String> getPlmns();
+    method public int getScanType();
+    method public int getSearchPeriodicity();
+    method public android.telephony.RadioAccessSpecifier[] getSpecifiers();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.NetworkScanRequest> CREATOR;
+    field public static final int SCAN_TYPE_ONE_SHOT = 0; // 0x0
+    field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
+  }
+
   public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
     ctor public PhoneNumberFormattingTextWatcher();
     ctor public PhoneNumberFormattingTextWatcher(java.lang.String);
@@ -40721,6 +40888,121 @@
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
   }
 
+  public final class RadioAccessSpecifier implements android.os.Parcelable {
+    ctor public RadioAccessSpecifier(int, int[], int[]);
+    method public int describeContents();
+    method public int[] getBands();
+    method public int[] getChannels();
+    method public int getRadioAccessNetwork();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.RadioAccessSpecifier> CREATOR;
+  }
+
+  public final class RadioNetworkConstants {
+    ctor public RadioNetworkConstants();
+  }
+
+  public static final class RadioNetworkConstants.EutranBands {
+    ctor public RadioNetworkConstants.EutranBands();
+    field public static final int BAND_1 = 1; // 0x1
+    field public static final int BAND_10 = 10; // 0xa
+    field public static final int BAND_11 = 11; // 0xb
+    field public static final int BAND_12 = 12; // 0xc
+    field public static final int BAND_13 = 13; // 0xd
+    field public static final int BAND_14 = 14; // 0xe
+    field public static final int BAND_17 = 17; // 0x11
+    field public static final int BAND_18 = 18; // 0x12
+    field public static final int BAND_19 = 19; // 0x13
+    field public static final int BAND_2 = 2; // 0x2
+    field public static final int BAND_20 = 20; // 0x14
+    field public static final int BAND_21 = 21; // 0x15
+    field public static final int BAND_22 = 22; // 0x16
+    field public static final int BAND_23 = 23; // 0x17
+    field public static final int BAND_24 = 24; // 0x18
+    field public static final int BAND_25 = 25; // 0x19
+    field public static final int BAND_26 = 26; // 0x1a
+    field public static final int BAND_27 = 27; // 0x1b
+    field public static final int BAND_28 = 28; // 0x1c
+    field public static final int BAND_3 = 3; // 0x3
+    field public static final int BAND_30 = 30; // 0x1e
+    field public static final int BAND_31 = 31; // 0x1f
+    field public static final int BAND_33 = 33; // 0x21
+    field public static final int BAND_34 = 34; // 0x22
+    field public static final int BAND_35 = 35; // 0x23
+    field public static final int BAND_36 = 36; // 0x24
+    field public static final int BAND_37 = 37; // 0x25
+    field public static final int BAND_38 = 38; // 0x26
+    field public static final int BAND_39 = 39; // 0x27
+    field public static final int BAND_4 = 4; // 0x4
+    field public static final int BAND_40 = 40; // 0x28
+    field public static final int BAND_41 = 41; // 0x29
+    field public static final int BAND_42 = 42; // 0x2a
+    field public static final int BAND_43 = 43; // 0x2b
+    field public static final int BAND_44 = 44; // 0x2c
+    field public static final int BAND_45 = 45; // 0x2d
+    field public static final int BAND_46 = 46; // 0x2e
+    field public static final int BAND_47 = 47; // 0x2f
+    field public static final int BAND_48 = 48; // 0x30
+    field public static final int BAND_5 = 5; // 0x5
+    field public static final int BAND_6 = 6; // 0x6
+    field public static final int BAND_65 = 65; // 0x41
+    field public static final int BAND_66 = 66; // 0x42
+    field public static final int BAND_68 = 68; // 0x44
+    field public static final int BAND_7 = 7; // 0x7
+    field public static final int BAND_70 = 70; // 0x46
+    field public static final int BAND_8 = 8; // 0x8
+    field public static final int BAND_9 = 9; // 0x9
+  }
+
+  public static final class RadioNetworkConstants.GeranBands {
+    ctor public RadioNetworkConstants.GeranBands();
+    field public static final int BAND_450 = 3; // 0x3
+    field public static final int BAND_480 = 4; // 0x4
+    field public static final int BAND_710 = 5; // 0x5
+    field public static final int BAND_750 = 6; // 0x6
+    field public static final int BAND_850 = 8; // 0x8
+    field public static final int BAND_DCS1800 = 12; // 0xc
+    field public static final int BAND_E900 = 10; // 0xa
+    field public static final int BAND_ER900 = 14; // 0xe
+    field public static final int BAND_P900 = 9; // 0x9
+    field public static final int BAND_PCS1900 = 13; // 0xd
+    field public static final int BAND_R900 = 11; // 0xb
+    field public static final int BAND_T380 = 1; // 0x1
+    field public static final int BAND_T410 = 2; // 0x2
+    field public static final int BAND_T810 = 7; // 0x7
+  }
+
+  public static final class RadioNetworkConstants.RadioAccessNetworks {
+    ctor public RadioNetworkConstants.RadioAccessNetworks();
+    field public static final int EUTRAN = 3; // 0x3
+    field public static final int GERAN = 1; // 0x1
+    field public static final int UTRAN = 2; // 0x2
+  }
+
+  public static final class RadioNetworkConstants.UtranBands {
+    ctor public RadioNetworkConstants.UtranBands();
+    field public static final int BAND_1 = 1; // 0x1
+    field public static final int BAND_10 = 10; // 0xa
+    field public static final int BAND_11 = 11; // 0xb
+    field public static final int BAND_12 = 12; // 0xc
+    field public static final int BAND_13 = 13; // 0xd
+    field public static final int BAND_14 = 14; // 0xe
+    field public static final int BAND_19 = 19; // 0x13
+    field public static final int BAND_2 = 2; // 0x2
+    field public static final int BAND_20 = 20; // 0x14
+    field public static final int BAND_21 = 21; // 0x15
+    field public static final int BAND_22 = 22; // 0x16
+    field public static final int BAND_25 = 25; // 0x19
+    field public static final int BAND_26 = 26; // 0x1a
+    field public static final int BAND_3 = 3; // 0x3
+    field public static final int BAND_4 = 4; // 0x4
+    field public static final int BAND_5 = 5; // 0x5
+    field public static final int BAND_6 = 6; // 0x6
+    field public static final int BAND_7 = 7; // 0x7
+    field public static final int BAND_8 = 8; // 0x8
+    field public static final int BAND_9 = 9; // 0x9
+  }
+
   public class ServiceState implements android.os.Parcelable {
     ctor public ServiceState();
     ctor public ServiceState(android.telephony.ServiceState);
@@ -40961,6 +41243,7 @@
     method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
+    method public java.lang.String getNai();
     method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
     method public java.lang.String getNetworkCountryIso();
     method public java.lang.String getNetworkOperator();
@@ -40994,23 +41277,28 @@
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isConcurrentVoiceAndDataSupported();
-    method public boolean isDataEnabled();
+    method public deprecated boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
     method public deprecated boolean isTtyModeSupported();
+    method public boolean isUserMobileDataEnabled();
     method public boolean isVoiceCapable();
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(java.lang.String, int, java.lang.String, android.app.PendingIntent);
-    method public void setDataEnabled(boolean);
+    method public deprecated void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
+    method public void setNetworkSelectionModeAutomatic();
+    method public boolean setNetworkSelectionModeManual(java.lang.String, boolean);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
+    method public void setUserMobileDataEnabled(boolean);
     method public void setVisualVoicemailSmsFilterSettings(android.telephony.VisualVoicemailSmsFilterSettings);
     method public boolean setVoiceMailNumber(java.lang.String, java.lang.String);
     method public deprecated void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri);
@@ -41105,6 +41393,17 @@
     method public void onReceiveUssdResponseFailed(android.telephony.TelephonyManager, java.lang.String, int);
   }
 
+  public final class TelephonyScanManager {
+    ctor public TelephonyScanManager();
+  }
+
+  public static abstract class TelephonyScanManager.NetworkScanCallback {
+    ctor public TelephonyScanManager.NetworkScanCallback();
+    method public void onComplete();
+    method public void onError(int);
+    method public void onResults(java.util.List<android.telephony.CellInfo>);
+  }
+
   public abstract class VisualVoicemailService extends android.app.Service {
     ctor public VisualVoicemailService();
     method public android.os.IBinder onBind(android.content.Intent);
@@ -41406,373 +41705,6 @@
 
 }
 
-package android.test {
-
-  public deprecated class AndroidTestCase extends junit.framework.TestCase {
-    ctor public AndroidTestCase();
-    method public void assertActivityRequiresPermission(java.lang.String, java.lang.String, java.lang.String);
-    method public void assertReadingContentUriRequiresPermission(android.net.Uri, java.lang.String);
-    method public void assertWritingContentUriRequiresPermission(android.net.Uri, java.lang.String);
-    method public android.content.Context getContext();
-    method protected void scrubClass(java.lang.Class<?>) throws java.lang.IllegalAccessException;
-    method public void setContext(android.content.Context);
-    method public void testAndroidTestCaseSetupProperly();
-    field protected android.content.Context mContext;
-  }
-
-  public abstract deprecated class FlakyTest implements java.lang.annotation.Annotation {
-  }
-
-  public deprecated class InstrumentationTestCase extends junit.framework.TestCase {
-    ctor public InstrumentationTestCase();
-    method public android.app.Instrumentation getInstrumentation();
-    method public deprecated void injectInsrumentation(android.app.Instrumentation);
-    method public void injectInstrumentation(android.app.Instrumentation);
-    method public final <T extends android.app.Activity> T launchActivity(java.lang.String, java.lang.Class<T>, android.os.Bundle);
-    method public final <T extends android.app.Activity> T launchActivityWithIntent(java.lang.String, java.lang.Class<T>, android.content.Intent);
-    method public void runTestOnUiThread(java.lang.Runnable) throws java.lang.Throwable;
-    method public void sendKeys(java.lang.String);
-    method public void sendKeys(int...);
-    method public void sendRepeatedKeys(int...);
-  }
-
-  public deprecated class InstrumentationTestSuite extends junit.framework.TestSuite {
-    ctor public InstrumentationTestSuite(android.app.Instrumentation);
-    ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation);
-    ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation);
-    method public void addTestSuite(java.lang.Class);
-  }
-
-  public abstract deprecated interface PerformanceTestCase {
-    method public abstract boolean isPerformanceOnly();
-    method public abstract int startPerformance(android.test.PerformanceTestCase.Intermediates);
-  }
-
-  public static abstract interface PerformanceTestCase.Intermediates {
-    method public abstract void addIntermediate(java.lang.String);
-    method public abstract void addIntermediate(java.lang.String, long);
-    method public abstract void finishTiming(boolean);
-    method public abstract void setInternalIterations(int);
-    method public abstract void startTiming(boolean);
-  }
-
-  public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation {
-  }
-
-}
-
-package android.test.mock {
-
-  public deprecated class MockApplication extends android.app.Application {
-    ctor public MockApplication();
-  }
-
-  public class MockContentProvider extends android.content.ContentProvider {
-    ctor protected MockContentProvider();
-    ctor public MockContentProvider(android.content.Context);
-    ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
-    method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
-    method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
-    method public java.lang.String getType(android.net.Uri);
-    method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public boolean onCreate();
-    method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle);
-    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
-    method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
-  }
-
-  public class MockContentResolver extends android.content.ContentResolver {
-    ctor public MockContentResolver();
-    ctor public MockContentResolver(android.content.Context);
-    method public void addProvider(java.lang.String, android.content.ContentProvider);
-  }
-
-  public class MockContext extends android.content.Context {
-    ctor public MockContext();
-    method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
-    method public int checkCallingOrSelfPermission(java.lang.String);
-    method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
-    method public int checkCallingPermission(java.lang.String);
-    method public int checkCallingUriPermission(android.net.Uri, int);
-    method public int checkPermission(java.lang.String, int, int);
-    method public int checkSelfPermission(java.lang.String);
-    method public int checkUriPermission(android.net.Uri, int, int, int);
-    method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
-    method public void clearWallpaper();
-    method public android.content.Context createConfigurationContext(android.content.res.Configuration);
-    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Context createDeviceProtectedStorageContext();
-    method public android.content.Context createDisplayContext(android.view.Display);
-    method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.lang.String[] databaseList();
-    method public boolean deleteDatabase(java.lang.String);
-    method public boolean deleteFile(java.lang.String);
-    method public boolean deleteSharedPreferences(java.lang.String);
-    method public void enforceCallingOrSelfPermission(java.lang.String, java.lang.String);
-    method public void enforceCallingOrSelfUriPermission(android.net.Uri, int, java.lang.String);
-    method public void enforceCallingPermission(java.lang.String, java.lang.String);
-    method public void enforceCallingUriPermission(android.net.Uri, int, java.lang.String);
-    method public void enforcePermission(java.lang.String, int, int, java.lang.String);
-    method public void enforceUriPermission(android.net.Uri, int, int, int, java.lang.String);
-    method public void enforceUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int, java.lang.String);
-    method public java.lang.String[] fileList();
-    method public android.content.Context getApplicationContext();
-    method public android.content.pm.ApplicationInfo getApplicationInfo();
-    method public android.content.res.AssetManager getAssets();
-    method public java.io.File getCacheDir();
-    method public java.lang.ClassLoader getClassLoader();
-    method public java.io.File getCodeCacheDir();
-    method public android.content.ContentResolver getContentResolver();
-    method public java.io.File getDataDir();
-    method public java.io.File getDatabasePath(java.lang.String);
-    method public java.io.File getDir(java.lang.String, int);
-    method public java.io.File getExternalCacheDir();
-    method public java.io.File[] getExternalCacheDirs();
-    method public java.io.File getExternalFilesDir(java.lang.String);
-    method public java.io.File[] getExternalFilesDirs(java.lang.String);
-    method public java.io.File[] getExternalMediaDirs();
-    method public java.io.File getFileStreamPath(java.lang.String);
-    method public java.io.File getFilesDir();
-    method public android.os.Looper getMainLooper();
-    method public java.io.File getNoBackupFilesDir();
-    method public java.io.File getObbDir();
-    method public java.io.File[] getObbDirs();
-    method public java.lang.String getPackageCodePath();
-    method public android.content.pm.PackageManager getPackageManager();
-    method public java.lang.String getPackageName();
-    method public java.lang.String getPackageResourcePath();
-    method public android.content.res.Resources getResources();
-    method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
-    method public java.lang.Object getSystemService(java.lang.String);
-    method public java.lang.String getSystemServiceName(java.lang.Class<?>);
-    method public android.content.res.Resources.Theme getTheme();
-    method public android.graphics.drawable.Drawable getWallpaper();
-    method public int getWallpaperDesiredMinimumHeight();
-    method public int getWallpaperDesiredMinimumWidth();
-    method public void grantUriPermission(java.lang.String, android.net.Uri, int);
-    method public boolean isDeviceProtectedStorage();
-    method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
-    method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
-    method public java.io.FileInputStream openFileInput(java.lang.String) throws java.io.FileNotFoundException;
-    method public java.io.FileOutputStream openFileOutput(java.lang.String, int) throws java.io.FileNotFoundException;
-    method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory);
-    method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler);
-    method public android.graphics.drawable.Drawable peekWallpaper();
-    method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
-    method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, int);
-    method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
-    method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, int);
-    method public void removeStickyBroadcast(android.content.Intent);
-    method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public void revokeUriPermission(android.net.Uri, int);
-    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
-    method public void sendBroadcast(android.content.Intent);
-    method public void sendBroadcast(android.content.Intent, java.lang.String);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendStickyBroadcast(android.content.Intent);
-    method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void setTheme(int);
-    method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
-    method public void setWallpaper(java.io.InputStream) throws java.io.IOException;
-    method public void startActivities(android.content.Intent[]);
-    method public void startActivities(android.content.Intent[], android.os.Bundle);
-    method public void startActivity(android.content.Intent);
-    method public void startActivity(android.content.Intent, android.os.Bundle);
-    method public android.content.ComponentName startForegroundService(android.content.Intent);
-    method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
-    method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
-    method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
-    method public android.content.ComponentName startService(android.content.Intent);
-    method public boolean stopService(android.content.Intent);
-    method public void unbindService(android.content.ServiceConnection);
-    method public void unregisterReceiver(android.content.BroadcastReceiver);
-  }
-
-  public deprecated class MockCursor implements android.database.Cursor {
-    ctor public MockCursor();
-    method public void close();
-    method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
-    method public deprecated void deactivate();
-    method public byte[] getBlob(int);
-    method public int getColumnCount();
-    method public int getColumnIndex(java.lang.String);
-    method public int getColumnIndexOrThrow(java.lang.String);
-    method public java.lang.String getColumnName(int);
-    method public java.lang.String[] getColumnNames();
-    method public int getCount();
-    method public double getDouble(int);
-    method public android.os.Bundle getExtras();
-    method public float getFloat(int);
-    method public int getInt(int);
-    method public long getLong(int);
-    method public android.net.Uri getNotificationUri();
-    method public int getPosition();
-    method public short getShort(int);
-    method public java.lang.String getString(int);
-    method public int getType(int);
-    method public boolean getWantsAllOnMoveCalls();
-    method public boolean isAfterLast();
-    method public boolean isBeforeFirst();
-    method public boolean isClosed();
-    method public boolean isFirst();
-    method public boolean isLast();
-    method public boolean isNull(int);
-    method public boolean move(int);
-    method public boolean moveToFirst();
-    method public boolean moveToLast();
-    method public boolean moveToNext();
-    method public boolean moveToPosition(int);
-    method public boolean moveToPrevious();
-    method public void registerContentObserver(android.database.ContentObserver);
-    method public void registerDataSetObserver(android.database.DataSetObserver);
-    method public deprecated boolean requery();
-    method public android.os.Bundle respond(android.os.Bundle);
-    method public void setExtras(android.os.Bundle);
-    method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
-    method public void unregisterContentObserver(android.database.ContentObserver);
-    method public void unregisterDataSetObserver(android.database.DataSetObserver);
-  }
-
-  public deprecated class MockDialogInterface implements android.content.DialogInterface {
-    ctor public MockDialogInterface();
-    method public void cancel();
-    method public void dismiss();
-  }
-
-  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
-    ctor public MockPackageManager();
-    method public void addPackageToPreferred(java.lang.String);
-    method public boolean addPermission(android.content.pm.PermissionInfo);
-    method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
-    method public void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
-    method public boolean canRequestPackageInstalls();
-    method public java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
-    method public int checkPermission(java.lang.String, java.lang.String);
-    method public int checkSignatures(java.lang.String, java.lang.String);
-    method public int checkSignatures(int, int);
-    method public void clearInstantAppCookie();
-    method public void clearPackagePreferredActivities(java.lang.String);
-    method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
-    method public void extendVerificationTimeout(int, int, long);
-    method public android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.graphics.drawable.Drawable getActivityIcon(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ActivityInfo getActivityInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.graphics.drawable.Drawable getActivityLogo(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.graphics.drawable.Drawable getActivityLogo(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
-    method public java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
-    method public android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getApplicationEnabledSetting(java.lang.String);
-    method public android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable getApplicationIcon(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ChangedPackages getChangedPackages(int);
-    method public int getComponentEnabledSetting(android.content.ComponentName);
-    method public android.graphics.drawable.Drawable getDefaultActivityIcon();
-    method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
-    method public java.lang.String getInstallerPackageName(java.lang.String);
-    method public byte[] getInstantAppCookie();
-    method public int getInstantAppCookieMaxBytes();
-    method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
-    method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
-    method public java.lang.String getNameForUid(int);
-    method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInstaller getPackageInstaller();
-    method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.lang.String[] getPackagesForUid(int);
-    method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
-    method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
-    method public java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
-    method public android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
-    method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
-    method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
-    method public java.lang.String[] getSystemSharedLibraryNames();
-    method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
-    method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
-    method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
-    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 isInstantApp();
-    method public boolean isInstantApp(java.lang.String);
-    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);
-    method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
-    method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
-    method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void removePackageFromPreferred(java.lang.String);
-    method public void removePermission(java.lang.String);
-    method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
-    method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
-    method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
-    method public void setApplicationCategoryHint(java.lang.String, int);
-    method public void setApplicationEnabledSetting(java.lang.String, int, int);
-    method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
-    method public void setInstallerPackageName(java.lang.String, java.lang.String);
-    method public void updateInstantAppCookie(byte[]);
-    method public void verifyPendingInstall(int, int);
-  }
-
-  public deprecated class MockResources extends android.content.res.Resources {
-    ctor public MockResources();
-    method public int getColor(int) throws android.content.res.Resources.NotFoundException;
-    method public android.content.res.ColorStateList getColorStateList(int) throws android.content.res.Resources.NotFoundException;
-    method public android.graphics.drawable.Drawable getDrawable(int) throws android.content.res.Resources.NotFoundException;
-    method public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
-  }
-
-}
-
-package android.test.suitebuilder.annotation {
-
-  public abstract deprecated class LargeTest implements java.lang.annotation.Annotation {
-  }
-
-  public abstract deprecated class MediumTest implements java.lang.annotation.Annotation {
-  }
-
-  public abstract deprecated class SmallTest implements java.lang.annotation.Annotation {
-  }
-
-  public abstract deprecated class Smoke implements java.lang.annotation.Annotation {
-  }
-
-  public abstract deprecated class Suppress implements java.lang.annotation.Annotation {
-  }
-
-}
-
 package android.text {
 
   public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -41876,9 +41808,9 @@
   }
 
   public class DynamicLayout extends android.text.Layout {
-    ctor public DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
     method public int getEllipsisStart(int);
@@ -42265,9 +42197,9 @@
   }
 
   public class StaticLayout extends android.text.Layout {
-    ctor public StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
     method public int getEllipsisStart(int);
@@ -44348,6 +44280,7 @@
     method public int indexOfValue(boolean);
     method public int keyAt(int);
     method public void put(int, boolean);
+    method public void removeAt(int);
     method public int size();
     method public boolean valueAt(int);
   }
@@ -44398,6 +44331,12 @@
     field public static final int[] WILD_CARD;
   }
 
+  public final class StatsLog {
+    method public static boolean logEvent(int);
+    method public static boolean logStart(int);
+    method public static boolean logStop(int);
+  }
+
   public class StringBuilderPrinter implements android.util.Printer {
     ctor public StringBuilderPrinter(java.lang.StringBuilder);
     method public void println(java.lang.String);
@@ -44678,6 +44617,14 @@
     field public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
   }
 
+  public final class DisplayCutout {
+    method public android.graphics.Region getBounds();
+    method public int getSafeInsetBottom();
+    method public int getSafeInsetLeft();
+    method public int getSafeInsetRight();
+    method public int getSafeInsetTop();
+  }
+
   public final class DragAndDropPermissions implements android.os.Parcelable {
     method public int describeContents();
     method public void release();
@@ -47631,8 +47578,10 @@
 
   public final class WindowInsets {
     ctor public WindowInsets(android.view.WindowInsets);
+    method public android.view.WindowInsets consumeDisplayCutout();
     method public android.view.WindowInsets consumeStableInsets();
     method public android.view.WindowInsets consumeSystemWindowInsets();
+    method public android.view.DisplayCutout getDisplayCutout();
     method public int getStableInsetBottom();
     method public int getStableInsetLeft();
     method public int getStableInsetRight();
@@ -47692,6 +47641,7 @@
     field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
     field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
     field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
+    field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47786,6 +47736,7 @@
     field public float buttonBrightness;
     field public float dimAmount;
     field public int flags;
+    field public long flags2;
     field public int format;
     field public int gravity;
     field public float horizontalMargin;
@@ -48578,9 +48529,12 @@
     method public void cancel();
     method public void commit();
     method public void disableAutofillServices();
+    method public android.content.ComponentName getAutofillServiceComponentName();
+    method public android.service.autofill.UserData getUserData();
     method public boolean hasEnabledAutofillServices();
     method public boolean isAutofillSupported();
     method public boolean isEnabled();
+    method public boolean isFieldClassificationEnabled();
     method public void notifyValueChanged(android.view.View);
     method public void notifyValueChanged(android.view.View, int, android.view.autofill.AutofillValue);
     method public void notifyViewEntered(android.view.View);
@@ -48592,6 +48546,7 @@
     method public void registerCallback(android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(android.view.View);
     method public void requestAutofill(android.view.View, int, android.graphics.Rect);
+    method public void setUserData(android.service.autofill.UserData);
     method public void unregisterCallback(android.view.autofill.AutofillManager.AutofillCallback);
     field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
     field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
@@ -49079,9 +49034,13 @@
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
     method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
     method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
+    method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+    field public static final int ENTITY_PRESET_ALL = 0; // 0x0
+    field public static final int ENTITY_PRESET_BASE = 2; // 0x2
+    field public static final int ENTITY_PRESET_NONE = 1; // 0x1
     field public static final android.view.textclassifier.TextClassifier NO_OP;
     field public static final java.lang.String TYPE_ADDRESS = "address";
     field public static final java.lang.String TYPE_EMAIL = "email";
@@ -49091,6 +49050,13 @@
     field public static final java.lang.String TYPE_URL = "url";
   }
 
+  public static final class TextClassifier.EntityConfig {
+    ctor public TextClassifier.EntityConfig(int);
+    method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...);
+    method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier);
+    method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...);
+  }
+
   public final class TextLinks {
     method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
     method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
@@ -49105,7 +49071,9 @@
   public static final class TextLinks.Options {
     ctor public TextLinks.Options();
     method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
     method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList);
+    method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig);
   }
 
   public static final class TextLinks.TextLink {
@@ -49806,6 +49774,7 @@
     method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
     method public android.webkit.WebMessagePort[] createWebMessageChannel();
     method public void destroy();
+    method public static void disableWebView();
     method public void documentHasImages(android.os.Message);
     method public static void enableSlowWholeDocumentDraw();
     method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>);
@@ -49866,6 +49835,7 @@
     method public void saveWebArchive(java.lang.String);
     method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
     method public deprecated void setCertificate(android.net.http.SslCertificate);
+    method public static void setDataDirectorySuffix(java.lang.String);
     method public void setDownloadListener(android.webkit.DownloadListener);
     method public void setFindListener(android.webkit.WebView.FindListener);
     method public deprecated void setHorizontalScrollbarOverlay(boolean);
diff --git a/api/removed.txt b/api/removed.txt
index be4d5be..2aab223 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -429,21 +429,6 @@
 
 }
 
-package android.test.mock {
-
-  public class MockContext extends android.content.Context {
-    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
-    method public java.io.File getSharedPreferencesPath(java.lang.String);
-  }
-
-  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
-    method public deprecated java.lang.String getDefaultBrowserPackageName(int);
-    method public deprecated boolean setDefaultBrowserPackageName(java.lang.String, int);
-    method public boolean setInstantAppCookie(byte[]);
-  }
-
-}
-
 package android.text.format {
 
   public class DateFormat {
@@ -506,14 +491,6 @@
 
 }
 
-package android.view.accessibility {
-
-  public final class AccessibilityWindowInfo implements android.os.Parcelable {
-    method public boolean inPictureInPicture();
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 30e4cbc..3e78167 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
     field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
     field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+    field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
     field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
     field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
@@ -1339,7 +1340,7 @@
 
 package android.hardware.location {
 
-  public class ContextHubInfo {
+  public class ContextHubInfo implements android.os.Parcelable {
     ctor public ContextHubInfo();
     method public int describeContents();
     method public int getId();
@@ -3531,6 +3532,11 @@
   public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
     method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String, boolean);
     method public static void resetToDefaults(android.content.ContentResolver, java.lang.String);
+    field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE = "autofill_user_data_max_field_classification_size";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE = "autofill_user_data_max_user_data_size";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
     field public static final java.lang.String INSTANT_APPS_ENABLED = "instant_apps_enabled";
   }
 
@@ -4138,11 +4144,13 @@
     method public java.lang.String getCdmaPrlVersion();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
+    method public int getDataActivationState();
     method public deprecated boolean getDataEnabled();
     method public deprecated boolean getDataEnabled(int);
     method public boolean getEmergencyCallbackMode();
     method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public android.os.Bundle getVisualVoicemailSettings();
+    method public int getVoiceActivationState();
     method public boolean handlePinMmi(java.lang.String);
     method public boolean handlePinMmiForSubscriber(int, java.lang.String);
     method public boolean isDataConnectivityPossible();
@@ -4154,10 +4162,12 @@
     method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
     method public boolean needsOtaServiceProvisioning();
     method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
-    method public void setDataEnabled(int, boolean);
+    method public void setDataActivationState(int);
+    method public deprecated void setDataEnabled(int, boolean);
     method public boolean setRadio(boolean);
     method public boolean setRadioPower(boolean);
     method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
+    method public void setVoiceActivationState(int);
     method public deprecated void silenceRinger();
     method public boolean supplyPin(java.lang.String);
     method public int[] supplyPinReportResult(java.lang.String);
@@ -4171,6 +4181,11 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
     field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
     field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
+    field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2
+    field public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; // 0x1
+    field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3
+    field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4
+    field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0
   }
 
   public abstract class VisualVoicemailService extends android.app.Service {
@@ -4342,44 +4357,6 @@
 
 }
 
-package android.test.mock {
-
-  public class MockContext extends android.content.Context {
-    method public android.content.Context createCredentialProtectedStorageContext();
-    method public java.io.File getPreloadsFileCache();
-    method public boolean isCredentialProtectedStorage();
-    method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-  }
-
-  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
-    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
-    method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
-    method public int getIntentVerificationStatusAsUser(java.lang.String, int);
-    method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
-    method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
-    method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
-    method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
-    method public void setUpdateAvailable(java.lang.String, boolean);
-    method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
-    method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
-  }
-
-}
-
 package android.util {
 
   public class EventLog {
@@ -4600,6 +4577,7 @@
     method public boolean canInvokeDrawGlFunctor(android.view.View);
     method public void detachDrawGlFunctor(android.view.View, long);
     method public android.app.Application getApplication();
+    method public java.lang.String getDataDirectorySuffix();
     method public java.lang.String getErrorString(android.content.Context, int);
     method public int getPackageId(android.content.res.Resources, java.lang.String);
     method public void invokeDrawGlFunctor(android.view.View, long, boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3fc5cd6..d56b0856 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -114,6 +114,10 @@
     field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
   }
 
+  public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
+    ctor public SecurityLog.SecurityEvent(long, byte[]);
+  }
+
 }
 
 package android.app.usage {
@@ -314,10 +318,6 @@
     method public void setType(int);
   }
 
-  public class LocationManager {
-    method public int getGnssYearOfHardware();
-  }
-
 }
 
 package android.net {
@@ -326,6 +326,13 @@
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
+  public class TrafficStats {
+    method public static long getLoopbackRxBytes();
+    method public static long getLoopbackRxPackets();
+    method public static long getLoopbackTxBytes();
+    method public static long getLoopbackTxPackets();
+  }
+
 }
 
 package android.os {
@@ -432,6 +439,10 @@
     field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
     field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
     field public static final java.lang.String AUTOFILL_SERVICE = "autofill_service";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE = "autofill_user_data_max_field_classification_size";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE = "autofill_user_data_max_user_data_size";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
+    field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
     field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services";
     field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
     field public static final java.lang.String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
@@ -463,34 +474,11 @@
   }
 
   public final class EditDistanceScorer extends android.service.autofill.InternalScorer implements android.os.Parcelable android.service.autofill.Scorer {
-    method public int describeContents();
-    method public static android.service.autofill.EditDistanceScorer getInstance();
     method public float getScore(android.view.autofill.AutofillValue, java.lang.String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR;
   }
 
-  public final class FieldClassification implements android.os.Parcelable {
-    method public int describeContents();
-    method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.FieldClassification> CREATOR;
-  }
-
-  public static final class FieldClassification.Match implements android.os.Parcelable {
-    method public int describeContents();
-    method public java.lang.String getRemoteId();
-    method public float getScore();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.FieldClassification.Match> CREATOR;
-  }
-
-  public static final class FillEventHistory.Event {
-    method public java.util.Map<android.view.autofill.AutofillId, android.service.autofill.FieldClassification> getFieldsClassification();
-  }
-
-  public static final class FillResponse.Builder {
-    method public android.service.autofill.FillResponse.Builder setFieldClassificationIds(android.view.autofill.AutofillId...);
+  public final class FillResponse implements android.os.Parcelable {
+    method public int getFlags();
   }
 
   public final class ImageTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -523,29 +511,10 @@
     method public boolean isValid(android.service.autofill.ValueFinder);
   }
 
-  public abstract interface Scorer {
-  }
-
   public final class TextValueSanitizer extends android.service.autofill.InternalSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
     method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
   }
 
-  public final class UserData implements android.os.Parcelable {
-    method public int describeContents();
-    method public static int getMaxFieldClassificationIdsSize();
-    method public static int getMaxUserDataSize();
-    method public static int getMaxValueLength();
-    method public static int getMinValueLength();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.UserData> CREATOR;
-  }
-
-  public static final class UserData.Builder {
-    ctor public UserData.Builder(android.service.autofill.Scorer, java.lang.String, java.lang.String);
-    method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
-    method public android.service.autofill.UserData build();
-  }
-
   public abstract interface ValueFinder {
     method public abstract java.lang.String findByAutofillId(android.view.autofill.AutofillId);
   }
@@ -720,21 +689,6 @@
 
 }
 
-package android.test.mock {
-
-  public class MockContext extends android.content.Context {
-    method public int getUserId();
-  }
-
-  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
-    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
-    method public int getInstallReason(java.lang.String, android.os.UserHandle);
-    method public java.lang.String getPermissionControllerPackageName();
-    method public boolean isPermissionReviewModeEnabled();
-  }
-
-}
-
 package android.text {
 
   public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
@@ -993,12 +947,6 @@
     ctor public AutofillId(int);
   }
 
-  public final class AutofillManager {
-    method public android.service.autofill.UserData getUserData();
-    method public boolean isFieldClassificationEnabled();
-    method public void setUserData(android.service.autofill.UserData);
-  }
-
 }
 
 package android.widget {
@@ -1025,6 +973,10 @@
     method public boolean isPopupShowing();
   }
 
+  public class TextClock extends android.widget.TextView {
+    method public void disableClockTick();
+  }
+
   public class TimePicker extends android.widget.FrameLayout {
     method public android.view.View getAmView();
     method public android.view.View getHourView();
diff --git a/cmds/ime/ime b/cmds/ime/ime
index 180dc76..7d2f72f 100755
--- a/cmds/ime/ime
+++ b/cmds/ime/ime
@@ -1,2 +1,2 @@
 #!/system/bin/sh
-exec cmd input_method "$@"
+exec cmd input_method ime "$@"
diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
index 23393da0..7b0ac0b 100644
--- a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
+++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
@@ -149,12 +149,25 @@
                    SystemPropertiesProto::Ro::Product::_FIELD_COUNT);
     Message product(&productTable);
 
-    Table vendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
+    Table pVendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
             SystemPropertiesProto::Ro::Product::Vendor::_FIELD_IDS,
             SystemPropertiesProto::Ro::Product::Vendor::_FIELD_COUNT);
-    Message vendor(&vendorTable);
-    product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &vendor);
+    Message pVendor(&pVendorTable);
+    product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &pVendor);
     ro.addSubMessage(SystemPropertiesProto::Ro::PRODUCT, &product);
+
+    Table telephonyTable(SystemPropertiesProto::Ro::Telephony::_FIELD_NAMES,
+                   SystemPropertiesProto::Ro::Telephony::_FIELD_IDS,
+                   SystemPropertiesProto::Ro::Telephony::_FIELD_COUNT);
+    Message telephony(&telephonyTable);
+    ro.addSubMessage(SystemPropertiesProto::Ro::TELEPHONY, &telephony);
+
+    Table vendorTable(SystemPropertiesProto::Ro::Vendor::_FIELD_NAMES,
+                   SystemPropertiesProto::Ro::Vendor::_FIELD_IDS,
+                   SystemPropertiesProto::Ro::Vendor::_FIELD_COUNT);
+    Message vendor(&vendorTable);
+    ro.addSubMessage(SystemPropertiesProto::Ro::VENDOR, &vendor);
+
     sysProp.addSubMessage(SystemPropertiesProto::RO, &ro);
 
     Table sysTable(SystemPropertiesProto::Sys::_FIELD_NAMES,
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index addba8c..f98ee3d 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -54,7 +54,7 @@
     src/storage/StorageManager.cpp \
     src/StatsLogProcessor.cpp \
     src/StatsService.cpp \
-    src/stats_util.cpp \
+    src/HashableDimensionKey.cpp \
     src/guardrail/MemoryLeakTrackUtil.cpp \
     src/guardrail/StatsdStats.cpp
 
@@ -173,7 +173,9 @@
     tests/metrics/DurationMetricProducer_test.cpp \
     tests/metrics/EventMetricProducer_test.cpp \
     tests/metrics/ValueMetricProducer_test.cpp \
-    tests/guardrail/StatsdStats_test.cpp
+    tests/metrics/GaugeMetricProducer_test.cpp \
+    tests/guardrail/StatsdStats_test.cpp \
+    tests/metrics/metrics_test_helper.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
new file mode 100644
index 0000000..0b6f8f2
--- /dev/null
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+
+string HashableDimensionKey::toString() const {
+    string flattened;
+    for (const auto& pair : mKeyValuePairs) {
+        flattened += std::to_string(pair.key());
+        flattened += ":";
+        switch (pair.value_case()) {
+            case KeyValuePair::ValueCase::kValueStr:
+                flattened += pair.value_str();
+                break;
+            case KeyValuePair::ValueCase::kValueInt:
+                flattened += std::to_string(pair.value_int());
+                break;
+            case KeyValuePair::ValueCase::kValueLong:
+                flattened += std::to_string(pair.value_long());
+                break;
+            case KeyValuePair::ValueCase::kValueBool:
+                flattened += std::to_string(pair.value_bool());
+                break;
+            case KeyValuePair::ValueCase::kValueFloat:
+                flattened += std::to_string(pair.value_float());
+                break;
+            default:
+                break;
+        }
+        flattened += "|";
+    }
+    return flattened;
+}
+
+bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
+    const auto& keyValue2 = that.getKeyValuePairs();
+    if (mKeyValuePairs.size() != keyValue2.size()) {
+        return false;
+    }
+
+    for (size_t i = 0; i < keyValue2.size(); i++) {
+        const auto& kv1 = mKeyValuePairs[i];
+        const auto& kv2 = keyValue2[i];
+        if (kv1.key() != kv2.key()) {
+            return false;
+        }
+
+        if (kv1.value_case() != kv2.value_case()) {
+            return false;
+        }
+
+        switch (kv1.value_case()) {
+            case KeyValuePair::ValueCase::kValueStr:
+                if (kv1.value_str() != kv2.value_str()) {
+                    return false;
+                }
+                break;
+            case KeyValuePair::ValueCase::kValueInt:
+                if (kv1.value_int() != kv2.value_int()) {
+                    return false;
+                }
+                break;
+            case KeyValuePair::ValueCase::kValueLong:
+                if (kv1.value_long() != kv2.value_long()) {
+                    return false;
+                }
+                break;
+            case KeyValuePair::ValueCase::kValueBool:
+                if (kv1.value_bool() != kv2.value_bool()) {
+                    return false;
+                }
+                break;
+            case KeyValuePair::ValueCase::kValueFloat: {
+                if (kv1.value_float() != kv2.value_float()) {
+                    return false;
+                }
+                break;
+            }
+            case KeyValuePair::ValueCase::VALUE_NOT_SET:
+                break;
+        }
+    }
+    return true;
+};
+
+bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
+    return toString().compare(that.toString()) < 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
new file mode 100644
index 0000000..85215552
--- /dev/null
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <utils/JenkinsHash.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class HashableDimensionKey {
+public:
+    explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs)
+        : mKeyValuePairs(keyValuePairs){};
+
+    HashableDimensionKey(){};
+
+    HashableDimensionKey(const HashableDimensionKey& that)
+        : mKeyValuePairs(that.getKeyValuePairs()){};
+
+    HashableDimensionKey& operator=(const HashableDimensionKey& from) = default;
+
+    std::string toString() const;
+
+    inline const std::vector<KeyValuePair>& getKeyValuePairs() const {
+        return mKeyValuePairs;
+    }
+
+    bool operator==(const HashableDimensionKey& that) const;
+
+    bool operator<(const HashableDimensionKey& that) const;
+
+    inline const char* c_str() const {
+        return toString().c_str();
+    }
+
+private:
+    std::vector<KeyValuePair> mKeyValuePairs;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+namespace std {
+
+using android::os::statsd::HashableDimensionKey;
+using android::os::statsd::KeyValuePair;
+
+template <>
+struct hash<HashableDimensionKey> {
+    std::size_t operator()(const HashableDimensionKey& key) const {
+        android::hash_t hash = 0;
+        for (const auto& pair : key.getKeyValuePairs()) {
+            hash = android::JenkinsHashMix(hash, android::hash_type(pair.key()));
+            hash = android::JenkinsHashMix(
+                    hash, android::hash_type(static_cast<int32_t>(pair.value_case())));
+            switch (pair.value_case()) {
+                case KeyValuePair::ValueCase::kValueStr:
+                    hash = android::JenkinsHashMix(
+                            hash,
+                            static_cast<uint32_t>(std::hash<std::string>()(pair.value_str())));
+                    break;
+                case KeyValuePair::ValueCase::kValueInt:
+                    hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int()));
+                    break;
+                case KeyValuePair::ValueCase::kValueLong:
+                    hash = android::JenkinsHashMix(
+                            hash, android::hash_type(static_cast<int64_t>(pair.value_long())));
+                    break;
+                case KeyValuePair::ValueCase::kValueBool:
+                    hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool()));
+                    break;
+                case KeyValuePair::ValueCase::kValueFloat: {
+                    float floatVal = pair.value_float();
+                    hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
+                    break;
+                }
+                case KeyValuePair::ValueCase::VALUE_NOT_SET:
+                    break;
+            }
+        }
+        return hash;
+    }
+};
+
+}  // namespace std
diff --git a/cmds/statsd/src/Log.h b/cmds/statsd/src/Log.h
index 7852709..87f4cba 100644
--- a/cmds/statsd/src/Log.h
+++ b/cmds/statsd/src/Log.h
@@ -26,5 +26,8 @@
 
 #include <log/log.h>
 
+// Use the local value to turn on/off debug logs instead of using log.tag. properties.
+// The advantage is that in production compiler can remove the logging code if the local
+// DEBUG/VERBOSE is false.
 #define VLOG(...) \
     if (DEBUG) ALOGD(__VA_ARGS__);
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index a3e39b6..0c078d5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -24,6 +24,7 @@
 #include "android-base/stringprintf.h"
 #include "guardrail/StatsdStats.h"
 #include "metrics/CountMetricProducer.h"
+#include "external/StatsPullerManager.h"
 #include "stats_util.h"
 #include "storage/StorageManager.h"
 
@@ -63,10 +64,15 @@
 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
                                      const sp<AnomalyMonitor>& anomalyMonitor,
                                      const std::function<void(const ConfigKey&)>& sendBroadcast)
-    : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast) {
+    : mUidMap(uidMap),
+      mAnomalyMonitor(anomalyMonitor),
+      mSendBroadcast(sendBroadcast),
+      mTimeBaseSec(time(nullptr)) {
     // On each initialization of StatsLogProcessor, check stats-data directory to see if there is
     // any left over data to be read.
     StorageManager::sendBroadcast(STATS_DATA_DIR, mSendBroadcast);
+    StatsPullerManager statsPullerManager;
+    statsPullerManager.SetTimeBaseSec(mTimeBaseSec);
 }
 
 StatsLogProcessor::~StatsLogProcessor() {
@@ -108,7 +114,8 @@
 
 void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
     ALOGD("Updated configuration for key %s", key.ToString().c_str());
-    unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(key, config);
+
+    sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
 
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
@@ -119,7 +126,12 @@
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
         newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
-        mMetricsManagers[key] = std::move(newMetricsManager);
+        if (config.log_source().package().size() > 0) {
+            // We have to add listener after the MetricsManager is constructed because it's
+            // not safe to create wp or sp from this pointer inside its constructor.
+            mUidMap->addListener(newMetricsManager.get());
+        }
+        mMetricsManagers[key] = newMetricsManager;
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
         VLOG("StatsdConfig valid");
     } else {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index a4df23a..1e5c426 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -56,7 +56,7 @@
 private:
     mutable mutex mBroadcastTimesMutex;
 
-    std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers;
+    std::unordered_map<ConfigKey, sp<MetricsManager>> mMetricsManagers;
 
     std::unordered_map<ConfigKey, long> mLastBroadcastTimes;
 
@@ -76,6 +76,8 @@
     // to retrieve the stored data.
     std::function<void(const ConfigKey& key)> mSendBroadcast;
 
+    const long mTimeBaseSec;
+
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 4dd2539..dab3880 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -201,7 +201,7 @@
         }
 
         if (!args[0].compare(String8("print-uid-map"))) {
-            return cmd_print_uid_map(out);
+            return cmd_print_uid_map(out, args);
         }
 
         if (!args[0].compare(String8("dump-report"))) {
@@ -248,9 +248,10 @@
     fprintf(out, "   # adb shell start\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
+    fprintf(out, "usage: adb shell cmd stats print-uid-map [PKG]\n");
     fprintf(out, "\n");
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
+    fprintf(out, "  PKG           Optional package name to print the uids of the package\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
@@ -497,8 +498,19 @@
     return NO_ERROR;
 }
 
-status_t StatsService::cmd_print_uid_map(FILE* out) {
-    mUidMap->printUidMap(out);
+status_t StatsService::cmd_print_uid_map(FILE* out, const Vector<String8>& args) {
+    if (args.size() > 1) {
+        string pkg;
+        pkg.assign(args[1].c_str(), args[1].size());
+        auto uids = mUidMap->getAppUid(pkg);
+        fprintf(out, "%s -> [ ", pkg.c_str());
+        for (const auto& uid : uids) {
+            fprintf(out, "%d ", uid);
+        }
+        fprintf(out, "]\n");
+    } else {
+        mUidMap->printUidMap(out);
+    }
     return NO_ERROR;
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e434f65..08fcdac 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -158,7 +158,7 @@
     /**
      * Print the mapping of uids to package names.
      */
-    status_t cmd_print_uid_map(FILE* out);
+    status_t cmd_print_uid_map(FILE* out, const Vector<String8>& args);
 
     /**
      * Flush the data to disk.
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 4777dcd..162a34b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -23,6 +23,7 @@
 #include <android/os/IIncidentManager.h>
 #include <android/os/IncidentReportArgs.h>
 #include <binder/IServiceManager.h>
+#include <statslog.h>
 #include <time.h>
 
 namespace android {
@@ -224,6 +225,9 @@
     }
 
     StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
+
+    android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
+                               mConfigKey.GetName().c_str(), mAlert.name().c_str());
 }
 
 void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 716fee6..1c6d9b0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -79,37 +79,49 @@
         IsolatedUidChanged isolated_uid_changed = 43;
         PacketWakeupOccurred packet_wakeup_occurred = 44;
         DropboxErrorChanged dropbox_error_changed = 45;
+        AnomalyDetected anomaly_detected = 46;
+        AppHook app_hook = 47;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
     // Pulled events will start at field 1000.
     oneof pulled {
-        WifiBytesTransferred wifi_bytes_transferred = 1000;
-        WifiBytesTransferredByFgBg wifi_bytes_transferred_by_fg_bg = 1001;
-        MobileBytesTransferred mobile_bytes_transferred = 1002;
-        MobileBytesTransferredByFgBg mobile_bytes_transferred_by_fg_bg = 1003;
-        KernelWakelockPulled kernel_wakelock_pulled = 1004;
-        PowerStatePlatformSleepStatePulled power_state_platform_sleep_state_pulled = 1005;
-        PowerStateVoterPulled power_state_voter_pulled = 1006;
-        PowerStateSubsystemSleepStatePulled power_state_subsystem_sleep_state_pulled = 1007;
-        CpuTimePerFreqPulled cpu_time_per_freq_pulled = 1008;
-        CpuTimePerUidPulled cpu_time_per_uid_pulled = 1009;
-        CpuTimePerUidFreqPulled cpu_time_per_uid_freq_pulled = 1010;
-        WifiActivityEnergyInfoPulled wifi_activity_energy_info_pulled = 1011;
-        ModemActivityInfoPulled modem_activity_info_pulled = 1012;
+        WifiBytesTransfer wifi_bytes_transfer = 1000;
+        WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 1001;
+        MobileBytesTransfer mobile_bytes_transfer = 1002;
+        MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 1003;
+        KernelWakelock kernel_wakelock = 1004;
+        PlatformSleepState platform_sleep_state = 1005;
+        SleepStateVoter sleep_state_voter = 1006;
+        SubsystemSleepState subsystem_sleep_state = 1007;
+        CpuTimePerFreq cpu_time_per_freq = 1008;
+        CpuTimePerUid cpu_time_per_uid = 1009;
+        CpuTimePerUidFreq cpu_time_per_uid_freq = 1010;
+        WifiActivityEnergyInfo wifi_activity_energy_info = 1011;
+        ModemActivityInfo modem_activity_info = 1012;
+        AttributionChainDummyAtom attribution_chain_dummy_atom = 10000;
     }
 }
 
 /**
- * A WorkSource represents the chained attribution of applications that
- * resulted in a particular bit of work being done.
+ * An attribution represents an application or module that is part of process where a particular bit
+ * of work is done.
  */
-message WorkSource {
-    // The uid for a given element in the attribution chain.
-    repeated int32 uid = 1;
-    // The (optional) string tag for an element in the attribution chain. If the
-    // element has no tag, it is encoded as an empty string.
-    repeated string tag = 2;
+message Attribution {
+    // The uid for an application or module.
+    optional int32 uid = 1;
+    // The string tag for the attribution node.
+    optional string tag = 2;
+}
+
+/**
+ * An attribution chain represents the chained attributions of applications or modules that
+ * resulted in a particular bit of work being done.
+ * The ordering of the attributions is that of calls, that is uid = [A, B, C] if A calls B that
+ * calls C.
+ */
+message AttributionChain {
+    repeated Attribution attribution = 1;
 }
 
 /*
@@ -127,7 +139,7 @@
  *   - The CamelCase name of the message type should match the
  *     underscore_separated name as defined in Atom.
  *   - If an atom represents work that can be attributed to an app, there can
- *     be exactly one WorkSource field. It must be field number 1.
+ *     be exactly one AttributionChain field. It must be field number 1.
  *   - A field that is a uid should be a string field, tagged with the [xxx]
  *     annotation. The generated code on android will be represented by UIDs,
  *     and those UIDs will be translated in xxx to those strings.
@@ -138,6 +150,11 @@
  * *****************************************************************************
  */
 
+message AttributionChainDummyAtom {
+    optional AttributionChain attribution_chain = 1;
+    optional int32 value = 2;
+}
+
 /**
  * Logs when the screen state changes.
  *
@@ -801,13 +818,53 @@
     optional int32 is_foreground = 7;
 }
 
+/*
+ * Allows other apps to push events into statsd.
+ * Logged from:
+ *      frameworks/base/core/java/android/util/StatsLog.java
+ */
+message AppHook {
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1;
+
+    // An arbitrary label chosen by the developer. For Android P, the label should be in [0, 16).
+    optional int32 label = 2;
+
+    // Allows applications to easily use a custom event as start/stop boundaries (ie, define custom
+    // predicates for the metrics).
+    enum State {
+        UNKNOWN = 0;
+        UNSPECIFIED = 1;  // For events that are known to not represent START/STOP.
+        STOP = 2;
+        START = 3;
+    }
+    optional State state = 3;
+}
+
+/**
+ * Logs when statsd detects an anomaly.
+ *
+ * Logged from:
+ *   frameworks/base/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+ */
+message AnomalyDetected {
+    // Uid that owns the config whose anomaly detection alert fired.
+    optional int32 config_uid = 1;
+
+    // Name of the config whose anomaly detection alert fired.
+    optional string config_name = 2;
+
+    // Name of the alert (i.e. name of the anomaly that was detected).
+    optional string alert_name = 3;
+}
+
 /**
  * Pulls bytes transferred via wifi (Sum of foreground and background usage).
  *
  * Pulled from:
  *   StatsCompanionService (using BatteryStats to get which interfaces are wifi)
  */
-message WifiBytesTransferred {
+message WifiBytesTransfer {
     optional int32 uid = 1;
 
     optional int64 rx_bytes = 2;
@@ -825,7 +882,7 @@
  * Pulled from:
  *   StatsCompanionService (using BatteryStats to get which interfaces are wifi)
  */
-message WifiBytesTransferredByFgBg {
+message WifiBytesTransferByFgBg {
     optional int32 uid = 1;
 
     // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
@@ -846,7 +903,7 @@
  * Pulled from:
  *   StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
  */
-message MobileBytesTransferred {
+message MobileBytesTransfer {
     optional int32 uid = 1;
 
     optional int64 rx_bytes = 2;
@@ -864,7 +921,7 @@
  * Pulled from:
  *   StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
  */
-message MobileBytesTransferredByFgBg {
+message MobileBytesTransferByFgBg {
     optional int32 uid = 1;
 
     // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
@@ -886,7 +943,7 @@
  * Pulled from:
  *   StatsCompanionService using KernelWakelockReader.
  */
-message KernelWakelockPulled {
+message KernelWakelock {
     optional string name = 1;
 
     optional int32 count = 2;
@@ -902,7 +959,7 @@
  * Definition here:
  *   hardware/interfaces/power/1.0/types.hal
  */
-message PowerStatePlatformSleepStatePulled {
+message PlatformSleepState {
     optional string name = 1;
     optional uint64 residency_in_msec_since_boot = 2;
     optional uint64 total_transitions = 3;
@@ -915,9 +972,9 @@
  * Definition here:
  *   hardware/interfaces/power/1.0/types.hal
  */
-message PowerStateVoterPulled {
-    optional string power_state_platform_sleep_state_name = 1;
-    optional string power_state_voter_name = 2;
+message SleepStateVoter {
+    optional string platform_sleep_state_name = 1;
+    optional string voter_name = 2;
     optional uint64 total_time_in_msec_voted_for_since_boot = 3;
     optional uint64 total_number_of_times_voted_since_boot = 4;
 }
@@ -928,9 +985,9 @@
  * Definition here:
  *   hardware/interfaces/power/1.1/types.hal
  */
-message PowerStateSubsystemSleepStatePulled {
-    optional string power_state_subsystem_name = 1;
-    optional string power_state_subsystem_sleep_state_name = 2;
+message SubsystemSleepState {
+    optional string subsystem_name = 1;
+    optional string subsystem_sleep_state_name = 2;
     optional uint64 residency_in_msec_since_boot = 3;
     optional uint64 total_transitions = 4;
     optional uint64 last_entry_timestamp_ms = 5;
@@ -967,17 +1024,17 @@
  * if current time is smaller than last value, there must be a cpu
  * hotplug event, and the current time is taken as delta.
  */
-message CpuTimePerFreqPulled {
+message CpuTimePerFreq {
     optional uint32 cluster = 1;
     optional uint32 freq_index = 2;
-    optional uint64 time = 3;
+    optional uint64 time_ms = 3;
 }
 
 /**
  * Pulls Cpu Time Per Uid.
  * Note that isolated process uid time should be attributed to host uids.
  */
-message CpuTimePerUidPulled {
+message CpuTimePerUid {
     optional uint64 uid = 1;
     optional uint64 user_time_ms = 2;
     optional uint64 sys_time_ms = 3;
@@ -988,7 +1045,7 @@
  * Note that isolated process uid time should be attributed to host uids.
  * For each uid, we order the time by descending frequencies.
  */
-message CpuTimePerUidFreqPulled {
+message CpuTimePerUidFreq {
     optional uint64 uid = 1;
     optional uint64 freq_idx = 2;
     optional uint64 time_ms = 3;
@@ -1026,7 +1083,7 @@
 /**
  * Pulls Wifi Controller Activity Energy Info
  */
-message WifiActivityEnergyInfoPulled {
+message WifiActivityEnergyInfo {
     // timestamp(wall clock) of record creation
     optional uint64 timestamp_ms = 1;
     // stack reported state
@@ -1045,7 +1102,7 @@
 /**
  * Pulls Modem Activity Energy Info
  */
-message ModemActivityInfoPulled {
+message ModemActivityInfo {
     // timestamp(wall clock) of record creation
     optional uint64 timestamp_ms = 1;
     // sleep time in ms.
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 02aca1a..bb4b817 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 #include "CombinationConditionTracker.h"
 
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 18b93ee..a63bc04 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -279,7 +279,7 @@
     }
 
     // outputKey is the output key values. e.g, uid:1234
-    const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
+    const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension));
     handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
 }
 
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index ff0e3bc..53ef9d5 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -109,7 +109,7 @@
         kv.set_key(link.key_in_condition(i).key());
     }
 
-    return getHashableKey(dimensionKey);
+    return HashableDimensionKey(dimensionKey);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 164f88f..cb3f3d6 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -55,8 +55,9 @@
     // for (const auto& pair : configsFromDisk) {
     //    UpdateConfig(pair.first, pair.second);
     //}
-    // this should be called from StatsService when it receives a statsd_config
-    UpdateConfig(ConfigKey(1000, "fake"), build_fake_config());
+
+    // Uncomment the following line and use the hard coded config for development.
+    // UpdateConfig(ConfigKey(1000, "fake"), build_fake_config());
 }
 
 void ConfigManager::AddListener(const sp<ConfigListener>& listener) {
@@ -247,6 +248,12 @@
     details->add_section(12);
     details->add_section(13);*/
 
+    AllowedLogSource* logSource = config.mutable_log_source();
+    logSource->add_uid(1000);
+    logSource->add_uid(0);
+    logSource->add_package("com.android.statsd.dogfood");
+    logSource->add_package("com.android.bluetooth");
+
     // Count process state changes, slice by uid.
     metric = config.add_count_metric();
     metric->set_name("METRIC_2");
@@ -369,7 +376,7 @@
     GaugeMetric* gaugeMetric = config.add_gauge_metric();
     gaugeMetric->set_name("METRIC_10");
     gaugeMetric->set_what("DEVICE_TEMPERATURE");
-    gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
+    gaugeMetric->mutable_gauge_fields()->add_field_num(DEVICE_TEMPERATURE_KEY);
     gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
 
     // Event matchers............
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
index e2745d2..9738760 100644
--- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
@@ -70,7 +70,7 @@
     int idx = 0;
     do {
       timeMs = std::stoull(pch);
-      auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ_PULLED, timestamp);
+      auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ, timestamp);
       ptr->write(uid);
       ptr->write(idx);
       ptr->write(timeMs);
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
index e0572dc..f69b9b5 100644
--- a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
@@ -65,7 +65,7 @@
     pch = strtok(buf, " ");
     uint64_t sysTimeMs = std::stoull(pch);
 
-    auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_PULLED, timestamp);
+    auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID, timestamp);
     ptr->write(uid);
     ptr->write(userTimeMs);
     ptr->write(sysTimeMs);
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index 3ee636d..cb9f1cc 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -92,7 +92,7 @@
                     const PowerStatePlatformSleepState& state = states[i];
 
                     auto statePtr = make_shared<LogEvent>(
-                            android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, timestamp);
+                            android::util::PLATFORM_SLEEP_STATE, timestamp);
                     statePtr->write(state.name);
                     statePtr->write(state.residencyInMsecSinceBoot);
                     statePtr->write(state.totalTransitions);
@@ -104,7 +104,7 @@
                          (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0);
                     for (auto voter : state.voters) {
                         auto voterPtr =
-                                make_shared<LogEvent>(android::util::POWER_STATE_VOTER_PULLED, timestamp);
+                                make_shared<LogEvent>(android::util::SLEEP_STATE_VOTER, timestamp);
                         voterPtr->write(state.name);
                         voterPtr->write(voter.name);
                         voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
@@ -138,7 +138,7 @@
                             for (size_t j = 0; j < subsystem.states.size(); j++) {
                                 const PowerStateSubsystemSleepState& state = subsystem.states[j];
                                 auto subsystemStatePtr = make_shared<LogEvent>(
-                                        android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, timestamp);
+                                        android::util::SUBSYSTEM_SLEEP_STATE, timestamp);
                                 subsystemStatePtr->write(subsystem.name);
                                 subsystemStatePtr->write(state.name);
                                 subsystemStatePtr->write(state.residencyInMsecSinceBoot);
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 2e803c9..00a1475 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -22,33 +22,41 @@
 namespace os {
 namespace statsd {
 
-class StatsPullerManager{
+class StatsPullerManager {
  public:
-  virtual ~StatsPullerManager() {}
+    virtual ~StatsPullerManager() {}
 
-  virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, long intervalMs) {
-    mPullerManager.RegisterReceiver(tagId, receiver, intervalMs);
-  };
+    virtual void RegisterReceiver(int tagId,
+                                  wp <PullDataReceiver> receiver,
+                                  long intervalMs) {
+        mPullerManager.RegisterReceiver(tagId, receiver, intervalMs);
+    };
 
-  virtual void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver) {
-    mPullerManager.UnRegisterReceiver(tagId, receiver);
-  };
+    virtual void UnRegisterReceiver(int tagId, wp <PullDataReceiver> receiver) {
+        mPullerManager.UnRegisterReceiver(tagId, receiver);
+    };
 
-  // Verify if we know how to pull for this matcher
-  bool PullerForMatcherExists(int tagId) {
-    return mPullerManager.PullerForMatcherExists(tagId);
-  }
+    // Verify if we know how to pull for this matcher
+    bool PullerForMatcherExists(int tagId) {
+        return mPullerManager.PullerForMatcherExists(tagId);
+    }
 
-  void OnAlarmFired() {
-    mPullerManager.OnAlarmFired();
-  }
+    void OnAlarmFired() {
+        mPullerManager.OnAlarmFired();
+    }
 
-  virtual bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) {
-    return mPullerManager.Pull(tagId, data);
-  }
+    virtual bool
+    Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+        return mPullerManager.Pull(tagId, data);
+    }
+
+    virtual void SetTimeBaseSec(const long timeBaseSec) {
+        mPullerManager.SetTimeBaseSec(timeBaseSec);
+    }
 
  private:
-  StatsPullerManagerImpl& mPullerManager = StatsPullerManagerImpl::GetInstance();
+    StatsPullerManagerImpl
+        & mPullerManager = StatsPullerManagerImpl::GetInstance();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index c4688a2..d707f85 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -44,30 +44,30 @@
 namespace statsd {
 
 StatsPullerManagerImpl::StatsPullerManagerImpl()
-    : mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
+    : mCurrentPullingInterval(LONG_MAX) {
     shared_ptr<StatsPuller> statsCompanionServicePuller = make_shared<StatsCompanionServicePuller>();
     shared_ptr<StatsPuller> resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
     shared_ptr<StatsPuller> cpuTimePerUidPuller = make_shared<CpuTimePerUidPuller>();
     shared_ptr<StatsPuller> cpuTimePerUidFreqPuller = make_shared<CpuTimePerUidFreqPuller>();
 
-    mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED,
+    mPullers.insert({android::util::KERNEL_WAKELOCK,
                      statsCompanionServicePuller});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED,
+    mPullers.insert({android::util::WIFI_BYTES_TRANSFER,
                      statsCompanionServicePuller});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED,
+    mPullers.insert({android::util::MOBILE_BYTES_TRANSFER,
                      statsCompanionServicePuller});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG,
+    mPullers.insert({android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
                      statsCompanionServicePuller});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG,
+    mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
                      statsCompanionServicePuller});
-    mPullers.insert({android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED,
+    mPullers.insert({android::util::PLATFORM_SLEEP_STATE,
                      resourcePowerManagerPuller});
-    mPullers.insert({android::util::POWER_STATE_VOTER_PULLED,
+    mPullers.insert({android::util::SLEEP_STATE_VOTER,
                      resourcePowerManagerPuller});
-    mPullers.insert({android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED,
+    mPullers.insert({android::util::SUBSYSTEM_SLEEP_STATE,
                      resourcePowerManagerPuller});
-    mPullers.insert({android::util::CPU_TIME_PER_UID_PULLED, cpuTimePerUidPuller});
-    mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ_PULLED, cpuTimePerUidFreqPuller});
+    mPullers.insert({android::util::CPU_TIME_PER_UID, cpuTimePerUidPuller});
+    mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, cpuTimePerUidFreqPuller});
 
     mStatsCompanionService = StatsService::getStatsCompanionService();
 }
@@ -94,11 +94,6 @@
     return mPullers.find(tagId) != mPullers.end();
 }
 
-long StatsPullerManagerImpl::get_pull_start_time_ms() const {
-    // TODO: limit and align pull intervals to 10min boundaries if this turns out to be a problem
-    return time(nullptr) * 1000;
-}
-
 void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver,
                                               long intervalMs) {
     AutoMutex _l(mReceiversLock);
@@ -114,12 +109,17 @@
     receiverInfo.timeInfo.first = intervalMs;
     receivers.push_back(receiverInfo);
 
+    // Round it to the nearest minutes. This is the limit of alarm manager.
+    // In practice, we should limit it higher.
+    long roundedIntervalMs = intervalMs/1000/60 * 1000 * 60;
     // There is only one alarm for all pulled events. So only set it to the smallest denom.
-    if (intervalMs < mCurrentPullingInterval) {
+    if (roundedIntervalMs < mCurrentPullingInterval) {
         VLOG("Updating pulling interval %ld", intervalMs);
-        mCurrentPullingInterval = intervalMs;
+        mCurrentPullingInterval = roundedIntervalMs;
+        long currentTimeMs = time(nullptr) * 1000;
+        long nextAlarmTimeMs = currentTimeMs + mCurrentPullingInterval - (currentTimeMs - mTimeBaseSec * 1000) % mCurrentPullingInterval;
         if (mStatsCompanionService != nullptr) {
-            mStatsCompanionService->setPullingAlarms(mPullStartTimeMs, mCurrentPullingInterval);
+            mStatsCompanionService->setPullingAlarms(nextAlarmTimeMs, mCurrentPullingInterval);
         } else {
             VLOG("Failed to update pulling interval");
         }
@@ -146,7 +146,7 @@
 void StatsPullerManagerImpl::OnAlarmFired() {
     AutoMutex _l(mReceiversLock);
 
-    uint64_t currentTimeMs = time(nullptr) * 1000;
+    uint64_t currentTimeMs = time(nullptr) /60 * 60 * 1000;
 
     vector<pair<int, vector<ReceiverInfo*>>> needToPull =
             vector<pair<int, vector<ReceiverInfo*>>>();
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
index 306cc32..7c59f66 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.h
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -47,6 +47,8 @@
 
     bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data);
 
+    void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;};
+
 private:
     StatsPullerManagerImpl();
 
@@ -73,11 +75,8 @@
 
     // for pulled metrics, it is important for the buckets to be aligned to multiple of smallest
     // bucket size. All pulled metrics start pulling based on this time, so that they can be
-    // correctly attributed to the correct buckets. Pulled data attach a timestamp which is the
-    // request time.
-    const long mPullStartTimeMs;
-
-    long get_pull_start_time_ms() const;
+    // correctly attributed to the correct buckets.
+    long mTimeBaseSec;
 
     LogEvent parse_pulled_data(String16 data);
 };
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 4cf168e..cb868e1f 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -46,6 +46,8 @@
 
     const static int kMaxTimestampCount = 20;
 
+    const static int kMaxLogSourceCount = 50;
+
     // Max memory allowed for storing metrics per configuration. When this limit is approached,
     // statsd will send a broadcast so that the client can fetch the data and clear this memory.
     static const size_t kMaxMetricsBytesPerConfig = 128 * 1024;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 1032138..d660b5f 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -33,6 +33,7 @@
     mContext =
             create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
     mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec;
+    mLogUid = msg.entry_v4.uid;
     init(mContext);
 }
 
@@ -69,6 +70,13 @@
     return false;
 }
 
+bool LogEvent::write(int64_t value) {
+    if (mContext) {
+        return android_log_write_int64(mContext, value) >= 0;
+    }
+    return false;
+}
+
 bool LogEvent::write(uint64_t value) {
     if (mContext) {
         return android_log_write_int64(mContext, value) >= 0;
@@ -224,7 +232,7 @@
     if (elem.type == EVENT_TYPE_INT) {
         pair.set_value_int(elem.data.int32);
     } else if (elem.type == EVENT_TYPE_LONG) {
-        pair.set_value_int(elem.data.int64);
+        pair.set_value_long(elem.data.int64);
     } else if (elem.type == EVENT_TYPE_STRING) {
         pair.set_value_str(elem.data.string);
     } else if (elem.type == EVENT_TYPE_FLOAT) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 176e16e..d3f38de 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -62,6 +62,10 @@
      */
     int GetTagId() const { return mTagId; }
 
+    uint32_t GetUid() const {
+        return mLogUid;
+    }
+
     /**
      * Get the nth value, starting at 1.
      *
@@ -110,6 +114,10 @@
      */
     void setTimestampNs(uint64_t timestampNs) {mTimestampNs = timestampNs;}
 
+    int size() const {
+        return mElements.size();
+    }
+
 private:
     /**
      * Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -129,6 +137,8 @@
     uint64_t mTimestampNs;
 
     int mTagId;
+
+    uint32_t mLogUid;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 41b24bc..4ce4768 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -105,7 +105,7 @@
 
     // Set up the binder
     sp<ProcessState> ps(ProcessState::self());
-    ps->setThreadPoolMaxThreadCount(1);  // everything is oneway, let it queue and save ram
+    ps->setThreadPoolMaxThreadCount(9);
     ps->startThreadPool();
     ps->giveThreadPoolName();
     IPCThreadState::self()->disableBackgroundScheduling(true);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 7b865c2..9031ed0 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "CountMetricProducer.h"
@@ -107,17 +107,14 @@
 
     for (const auto& counter : mPastBuckets) {
         const HashableDimensionKey& hashableKey = counter.first;
+        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         VLOG("  dimension key %s", hashableKey.c_str());
-        auto it = mDimensionKeyMap.find(hashableKey);
-        if (it == mDimensionKeyMap.end()) {
-            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
-            continue;
-        }
+
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
-        for (const auto& kv : it->second) {
+        for (const auto& kv : kvs) {
             long long dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 59995d2..e32fc06 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -48,12 +48,6 @@
 
     virtual ~CountMetricProducer();
 
-    // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
-            override{};
-    // TODO: Implement this later.
-    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 6afbe45..1c8f422 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true
+#define DEBUG false
 
 #include "Log.h"
 #include "DurationMetricProducer.h"
@@ -159,18 +159,14 @@
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
+        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         VLOG("  dimension key %s", hashableKey.c_str());
-        auto it = mDimensionKeyMap.find(hashableKey);
-        if (it == mDimensionKeyMap.end()) {
-            ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
-            continue;
-        }
 
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
-        for (const auto& kv : it->second) {
+        for (const auto& kv : kvs) {
             long long dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
@@ -260,7 +256,7 @@
         return;
     }
 
-    HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+    HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension));
 
     if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
         if (hitGuardRailLocked(eventKey)) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index e7aca7f..7044b4b 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -47,12 +47,6 @@
 
     virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override;
 
-    // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
-            override{};
-    // TODO: Implement this later.
-    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 4752997..6a072b0 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "EventMetricProducer.h"
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index d720ead..6120ad8 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -40,12 +40,6 @@
 
     virtual ~EventMetricProducer();
 
-    // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
-            override{};
-    // TODO: Implement this later.
-    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
 protected:
     void startNewProtoOutputStreamLocked();
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index ae9b86f..47cca0e 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -14,16 +14,13 @@
 * limitations under the License.
 */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "GaugeMetricProducer.h"
 #include "guardrail/StatsdStats.h"
-#include "stats_util.h"
 
 #include <cutils/log.h>
-#include <limits.h>
-#include <stdlib.h>
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
@@ -37,6 +34,8 @@
 using std::string;
 using std::unordered_map;
 using std::vector;
+using std::make_shared;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -61,21 +60,27 @@
 // for GaugeBucketInfo
 const int FIELD_ID_START_BUCKET_NANOS = 1;
 const int FIELD_ID_END_BUCKET_NANOS = 2;
-const int FIELD_ID_GAUGE = 3;
+const int FIELD_ID_ATOM = 3;
 
 GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
                                          const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard, const int pullTagId,
-                                         const int64_t startTimeNs)
+                                         const sp<ConditionWizard>& wizard, const int atomTagId,
+                                         const int pullTagId, const uint64_t startTimeNs,
+                                         shared_ptr<StatsPullerManager> statsPullerManager)
     : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
-      mGaugeField(metric.gauge_field()),
-      mPullTagId(pullTagId) {
+      mStatsPullerManager(statsPullerManager),
+      mPullTagId(pullTagId),
+      mAtomTagId(atomTagId) {
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
         mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
     } else {
         mBucketSizeNs = kDefaultGaugemBucketSizeNs;
     }
 
+    for (int i = 0; i < metric.gauge_fields().field_num_size(); i++) {
+        mGaugeFields.push_back(metric.gauge_fields().field_num(i));
+    }
+
     // TODO: use UidMap if uid->pkg_name is required
     mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
 
@@ -87,7 +92,7 @@
 
     // Kicks off the puller immediately.
     if (mPullTagId != -1) {
-        mStatsPullerManager.RegisterReceiver(mPullTagId, this,
+        mStatsPullerManager->RegisterReceiver(mPullTagId, this,
                                              metric.bucket().bucket_size_millis());
     }
 
@@ -95,10 +100,19 @@
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
+// for testing
+GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
+                                         const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard, const int pullTagId,
+                                         const int atomTagId, const int64_t startTimeNs)
+    : GaugeMetricProducer(key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs,
+                          make_shared<StatsPullerManager>()) {
+}
+
 GaugeMetricProducer::~GaugeMetricProducer() {
     VLOG("~GaugeMetricProducer() called");
     if (mPullTagId != -1) {
-        mStatsPullerManager.UnRegisterReceiver(mPullTagId, this);
+        mStatsPullerManager->UnRegisterReceiver(mPullTagId, this);
     }
 }
 
@@ -114,18 +128,14 @@
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
-        auto it = mDimensionKeyMap.find(hashableKey);
-        if (it == mDimensionKeyMap.end()) {
-            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
-            continue;
-        }
+        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
 
         VLOG("  dimension key %s", hashableKey.c_str());
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
-        for (const auto& kv : it->second) {
+        for (const auto& kv : kvs) {
             long long dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
@@ -149,10 +159,26 @@
                                (long long)bucket.mBucketStartNs);
             protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
                                (long long)bucket.mBucketEndNs);
-            protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_GAUGE, (long long)bucket.mGauge);
+            long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM);
+            long long eventToken = protoOutput->start(FIELD_TYPE_MESSAGE | mAtomTagId);
+            for (const auto& pair : bucket.mEvent->kv) {
+                if (pair.has_value_int()) {
+                    protoOutput->write(FIELD_TYPE_INT32 | pair.key(), pair.value_int());
+                } else if (pair.has_value_long()) {
+                    protoOutput->write(FIELD_TYPE_INT64 | pair.key(), pair.value_long());
+                } else if (pair.has_value_str()) {
+                    protoOutput->write(FIELD_TYPE_STRING | pair.key(), pair.value_str());
+                } else if (pair.has_value_long()) {
+                    protoOutput->write(FIELD_TYPE_FLOAT | pair.key(), pair.value_float());
+                } else if (pair.has_value_bool()) {
+                    protoOutput->write(FIELD_TYPE_BOOL | pair.key(), pair.value_bool());
+                }
+            }
+            protoOutput->end(eventToken);
+            protoOutput->end(atomToken);
             protoOutput->end(bucketInfoToken);
-            VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
-                 (long long)bucket.mBucketEndNs, (long long)bucket.mGauge);
+            VLOG("\t bucket [%lld - %lld] content: %s", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, bucket.mEvent->ToString().c_str());
         }
         protoOutput->end(wrapperToken);
     }
@@ -174,6 +200,7 @@
     if (mPullTagId == -1) {
         return;
     }
+    // No need to pull again. Either scheduled pull or condition on true happened
     if (!mCondition) {
         return;
     }
@@ -182,7 +209,7 @@
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
-    if (!mStatsPullerManager.Pull(mPullTagId, &allData)) {
+    if (!mStatsPullerManager->Pull(mPullTagId, &allData)) {
         ALOGE("Stats puller failed for tag: %d", mPullTagId);
         return;
     }
@@ -196,20 +223,25 @@
     VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
 }
 
-int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
-    status_t err = NO_ERROR;
-    int64_t val = event.GetLong(mGaugeField, &err);
-    if (err == NO_ERROR) {
-        return val;
+shared_ptr<EventKV> GaugeMetricProducer::getGauge(const LogEvent& event) {
+    shared_ptr<EventKV> ret = make_shared<EventKV>();
+    if (mGaugeFields.size() == 0) {
+        for (int i = 1; i <= event.size(); i++) {
+            ret->kv.push_back(event.GetKeyValueProto(i));
+        }
     } else {
-        VLOG("Can't find value in message.");
-        return -1;
+        for (int i = 0; i < (int)mGaugeFields.size(); i++) {
+            ret->kv.push_back(event.GetKeyValueProto(mGaugeFields[i]));
+        }
     }
+    return ret;
 }
 
 void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
     std::lock_guard<std::mutex> lock(mMutex);
-
+    if (allData.size() == 0) {
+        return;
+    }
     for (const auto& data : allData) {
         onMatchedLogEventLocked(0, *data);
     }
@@ -247,25 +279,48 @@
              (long long)mCurrentBucketStartTimeNs);
         return;
     }
-
-    // When the event happens in a new bucket, flush the old buckets.
-    if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
-        flushIfNeededLocked(eventTimeNs);
-    }
+    flushIfNeededLocked(eventTimeNs);
 
     // For gauge metric, we just simply use the first gauge in the given bucket.
-    if (!mCurrentSlicedBucket->empty()) {
+    if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) {
         return;
     }
-    const long gauge = getGauge(event);
-    if (gauge >= 0) {
-        if (hitGuardRailLocked(eventKey)) {
-            return;
-        }
-        (*mCurrentSlicedBucket)[eventKey] = gauge;
+    shared_ptr<EventKV> gauge = getGauge(event);
+    if (hitGuardRailLocked(eventKey)) {
+        return;
     }
-    for (auto& tracker : mAnomalyTrackers) {
-        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
+    (*mCurrentSlicedBucket)[eventKey] = gauge;
+    // Anomaly detection on gauge metric only works when there is one numeric
+    // field specified.
+    if (mAnomalyTrackers.size() > 0) {
+        if (gauge->kv.size() == 1) {
+            KeyValuePair pair = gauge->kv[0];
+            long gaugeVal = 0;
+            if (pair.has_value_int()) {
+                gaugeVal = (long)pair.value_int();
+            } else if (pair.has_value_long()) {
+                gaugeVal = pair.value_long();
+            }
+            for (auto& tracker : mAnomalyTrackers) {
+                tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
+                                                 gaugeVal);
+            }
+        }
+    }
+}
+
+void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() {
+    mCurrentSlicedBucketForAnomaly->clear();
+    status_t err = NO_ERROR;
+    for (const auto& slice : *mCurrentSlicedBucket) {
+        KeyValuePair pair = slice.second->kv[0];
+        long gaugeVal = 0;
+        if (pair.has_value_int()) {
+            gaugeVal = (long)pair.value_int();
+        } else if (pair.has_value_long()) {
+            gaugeVal = pair.value_long();
+        }
+        (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal;
     }
 }
 
@@ -276,6 +331,8 @@
 // the GaugeMetricProducer while holding the lock.
 void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
     if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
+        VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
         return;
     }
 
@@ -285,19 +342,22 @@
     info.mBucketNum = mCurrentBucketNum;
 
     for (const auto& slice : *mCurrentSlicedBucket) {
-        info.mGauge = slice.second;
+        info.mEvent = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        VLOG("gauge metric %s, dump key value: %s -> %lld", mName.c_str(), slice.first.c_str(),
-             (long long)slice.second);
+        VLOG("gauge metric %s, dump key value: %s -> %s", mName.c_str(),
+             slice.first.c_str(), slice.second->ToString().c_str());
     }
 
     // Reset counters
-    for (auto& tracker : mAnomalyTrackers) {
-        tracker->addPastBucket(mCurrentSlicedBucket, mCurrentBucketNum);
+    if (mAnomalyTrackers.size() > 0) {
+        updateCurrentSlicedBucketForAnomaly();
+        for (auto& tracker : mAnomalyTrackers) {
+            tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
+        }
     }
 
-    mCurrentSlicedBucket = std::make_shared<DimToValMap>();
+    mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
 
     // Adjusts the bucket start time
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 6e6f2bb..19d51e8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -26,7 +26,7 @@
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
+#include "../stats_util.h"
 
 namespace android {
 namespace os {
@@ -35,7 +35,7 @@
 struct GaugeBucket {
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
-    int64_t mGauge;
+    std::shared_ptr<EventKV> mEvent;
     uint64_t mBucketNum;
 };
 
@@ -45,23 +45,15 @@
 // producer always reports the guage at the earliest time of the bucket when the condition is met.
 class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    // TODO: Pass in the start time from MetricsManager, it should be consistent
-    // for all metrics.
     GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric,
                         const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int pullTagId, const int64_t startTimeNs);
+                        const int pullTagId, const int atomTagId, const int64_t startTimeNs);
 
     virtual ~GaugeMetricProducer();
 
     // Handles when the pulled data arrives.
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
 
-    // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
-            override{};
-    // TODO: Implement this later.
-    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
@@ -72,6 +64,12 @@
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
 
+    // for testing
+    GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const int pullTagId, const int atomTagId, const uint64_t startTimeNs,
+                        std::shared_ptr<StatsPullerManager> statsPullerManager);
+
     // Internal interface to handle condition change.
     void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
 
@@ -84,12 +82,10 @@
     // Util function to flush the old packet.
     void flushIfNeededLocked(const uint64_t& eventTime);
 
-    // The default bucket size for gauge metric is 1 second.
-    static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
+    // The default bucket size for gauge metric is 1 hr.
+    static const uint64_t kDefaultGaugemBucketSizeNs = 60ULL * 60 * 1000 * 1000 * 1000;
 
-    const int32_t mGaugeField;
-
-    StatsPullerManager mStatsPullerManager;
+    std::shared_ptr<StatsPullerManager> mStatsPullerManager;
     // tagId for pulled data. -1 if this is not pulled
     const int mPullTagId;
 
@@ -98,9 +94,21 @@
     std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
-    std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
+    std::shared_ptr<DimToEventKVMap> mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
 
-    int64_t getGauge(const LogEvent& event);
+    // The current bucket for anomaly detection.
+    std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
+
+    // Translate Atom based bucket to single numeric value bucket for anomaly
+    void updateCurrentSlicedBucketForAnomaly();
+
+    int mAtomTagId;
+
+    // Whitelist of fields to report. Empty means all are reported.
+    std::vector<int> mGaugeFields;
+
+    // apply a whitelist on the original input
+    std::shared_ptr<EventKV> getGauge(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index f38f3df..5286908 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -32,12 +32,7 @@
 
     if (mDimension.size() > 0) {
         vector<KeyValuePair> key = getDimensionKey(event, mDimension);
-        eventKey = getHashableKey(key);
-        // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
-        // expects vector<KeyValuePair>.
-        if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
-            mDimensionKeyMap[eventKey] = key;
-        }
+        eventKey = HashableDimensionKey(key);
     } else {
         eventKey = DEFAULT_DIMENSION_KEY;
     }
@@ -58,7 +53,6 @@
     } else {
         condition = mCondition;
     }
-
     onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event);
 }
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index d4a2195..85ef4ad 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -27,6 +27,7 @@
 
 #include <log/logprint.h>
 #include <utils/RefBase.h>
+#include <unordered_map>
 
 namespace android {
 namespace os {
@@ -48,12 +49,21 @@
           mCondition(conditionIndex >= 0 ? false : true),
           mConditionSliced(false),
           mWizard(wizard),
-          mConditionTrackerIndex(conditionIndex) {
-        // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
-        mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
-    };
+          mConditionTrackerIndex(conditionIndex){};
     virtual ~MetricProducer(){};
 
+    void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
+            // TODO: Implement me.
+    };
+
+    void notifyAppRemoved(const string& apk, const int uid) override{
+            // TODO: Implement me.
+    };
+
+    void onUidMapReceived() override{
+            // TODO: Implement me.
+    };
+
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
@@ -132,10 +142,6 @@
 
     std::vector<KeyMatcher> mDimension;  // The dimension defined in statsd_config
 
-    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
-    // that StatsLogReport wants.
-    std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
-
     std::vector<MetricConditionLink> mConditionLinks;
 
     std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 3d0e20c..231bd8e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -16,6 +16,7 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 #include "MetricsManager.h"
+#include "statslog.h"
 
 #include "CountMetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
@@ -44,12 +45,37 @@
 
 const int FIELD_ID_METRICS = 1;
 
-MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config) : mConfigKey(key) {
+MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
+                               const long timeBaseSec, sp<UidMap> uidMap)
+    : mConfigKey(key), mUidMap(uidMap) {
     mConfigValid =
-            initStatsdConfig(key, config, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
+            initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
                              mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
                              mTrackerToMetricMap, mTrackerToConditionMap);
 
+    if (!config.has_log_source()) {
+        // TODO(b/70794411): uncomment the following line and remove the hard coded log source
+        // after all configs have the log source added.
+        // mConfigValid = false;
+        // ALOGE("Log source white list is empty! This config won't get any data.");
+
+        mAllowedUid.push_back(1000);
+        mAllowedUid.push_back(0);
+        mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
+    } else {
+        mAllowedUid.insert(mAllowedUid.begin(), config.log_source().uid().begin(),
+                           config.log_source().uid().end());
+        mAllowedPkg.insert(mAllowedPkg.begin(), config.log_source().package().begin(),
+                           config.log_source().package().end());
+
+        if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
+            ALOGE("Too many log sources. This is likely to be an error in the config.");
+            mConfigValid = false;
+        } else {
+            initLogSourceWhiteList();
+        }
+    }
+
     // Guardrail. Reject the config if it's too big.
     if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
         mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
@@ -69,10 +95,53 @@
     VLOG("~MetricsManager()");
 }
 
+void MetricsManager::initLogSourceWhiteList() {
+    std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+    mAllowedLogSources.clear();
+    mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
+
+    for (const auto& pkg : mAllowedPkg) {
+        auto uids = mUidMap->getAppUid(pkg);
+        mAllowedLogSources.insert(uids.begin(), uids.end());
+    }
+    if (DEBUG) {
+        for (const auto& uid : mAllowedLogSources) {
+            VLOG("Allowed uid %d", uid);
+        }
+    }
+}
+
 bool MetricsManager::isConfigValid() const {
     return mConfigValid;
 }
 
+void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const int64_t version) {
+    // check if we care this package
+    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
+        return;
+    }
+    // We will re-initialize the whole list because we don't want to keep the multi mapping of
+    // UID<->pkg inside MetricsManager to reduce the memory usage.
+    initLogSourceWhiteList();
+}
+
+void MetricsManager::notifyAppRemoved(const string& apk, const int uid) {
+    // check if we care this package
+    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
+        return;
+    }
+    // We will re-initialize the whole list because we don't want to keep the multi mapping of
+    // UID<->pkg inside MetricsManager to reduce the memory usage.
+    initLogSourceWhiteList();
+}
+
+void MetricsManager::onUidMapReceived() {
+    if (mAllowedPkg.size() == 0) {
+        return;
+    }
+    initLogSourceWhiteList();
+}
+
 void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) {
     VLOG("=========================Metric Reports Start==========================");
     uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC;
@@ -92,6 +161,14 @@
         return;
     }
 
+    if (event.GetTagId() != android::util::APP_HOOK) {
+        std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+        if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
+            VLOG("log source %d not on the whitelist", event.GetUid());
+            return;
+        }
+    }
+
     int tagId = event.GetTagId();
     uint64_t eventTime = event.GetTimestampNs();
     if (mTagIds.find(tagId) == mTagIds.end()) {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 34ea667..8faa75d 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -24,6 +24,7 @@
 #include "logd/LogEvent.h"
 #include "matchers/LogMatchingTracker.h"
 #include "metrics/MetricProducer.h"
+#include "packages/UidMap.h"
 
 #include <unordered_map>
 
@@ -32,9 +33,10 @@
 namespace statsd {
 
 // A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager {
+class MetricsManager : public PackageInfoListener {
 public:
-    MetricsManager(const ConfigKey& configKey, const StatsdConfig& config);
+    MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const long timeBaseSec,
+                   sp<UidMap> uidMap);
 
     virtual ~MetricsManager();
 
@@ -48,6 +50,12 @@
 
     void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
 
+    void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override;
+
+    void notifyAppRemoved(const string& apk, const int uid) override;
+
+    void onUidMapReceived() override;
+
     // Config source owner can call onDumpReport() to get all the metrics collected.
     virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput);
 
@@ -58,6 +66,23 @@
 private:
     const ConfigKey mConfigKey;
 
+    sp<UidMap> mUidMap;
+
+    bool mConfigValid = false;
+
+    // The uid log sources from StatsdConfig.
+    std::vector<int32_t> mAllowedUid;
+
+    // The pkg log sources from StatsdConfig.
+    std::vector<std::string> mAllowedPkg;
+
+    // The combined uid sources (after translating pkg name to uid).
+    // Logs from uids that are not in the list will be ignored to avoid spamming.
+    std::set<int32_t> mAllowedLogSources;
+
+    // To guard access to mAllowedLogSources
+    mutable std::mutex mAllowedLogSourcesMutex;
+
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
 
@@ -102,7 +127,7 @@
     // maps from ConditionTracker to MetricProducer
     std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
 
-    bool mConfigValid = false;
+    void initLogSourceWhiteList();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 1eabf11..40aed7b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "ValueMetricProducer.h"
@@ -132,16 +132,12 @@
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
         VLOG("  dimension key %s", hashableKey.c_str());
-        auto it = mDimensionKeyMap.find(hashableKey);
-        if (it == mDimensionKeyMap.end()) {
-            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
-            continue;
-        }
+        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
-        for (const auto& kv : it->second) {
+        for (const auto& kv : kvs) {
             long long dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 51af83d..2f27e4e 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,12 +47,6 @@
 
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
 
-    // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
-            override{};
-    // TODO: Implement this later.
-    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 08c9135..6050f43 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true
+#define DEBUG false
 
 #include "Log.h"
 #include "MaxDurationTracker.h"
@@ -42,7 +42,7 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mInfos.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
                                                            newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 8122744..5c43096 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define DEBUG true
+#define DEBUG false
 #include "Log.h"
 #include "OringDurationTracker.h"
 #include "guardrail/StatsdStats.h"
@@ -45,7 +45,7 @@
     }
     if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mConditionKeyMap.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
                                                            newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 943becb..5d0e97e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -188,7 +188,7 @@
     return true;
 }
 
-bool initMetrics(const ConfigKey& key, const StatsdConfig& config,
+bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
                  const unordered_map<string, int>& logTrackerMap,
                  const unordered_map<string, int>& conditionTrackerMap,
                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
@@ -202,7 +202,13 @@
                                 config.event_metric_size() + config.value_metric_size();
     allMetricProducers.reserve(allMetricsCount);
     StatsPullerManager statsPullerManager;
-    uint64_t startTimeNs = time(nullptr) * NS_PER_SEC;
+    // Align all buckets to same instant in MIN_BUCKET_SIZE_SEC, so that avoid alarm
+    // clock will not grow very aggressive. New metrics will be delayed up to
+    // MIN_BUCKET_SIZE_SEC before starting.
+    long currentTimeSec = time(nullptr);
+    uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
+                            (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
+                           NS_PER_SEC;
 
     // Build MetricProducers for each metric defined in config.
     // build CountMetricProducer
@@ -370,15 +376,11 @@
 
         sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
-        int pullTagId = -1;
-        for (int tagId : atomMatcher->getTagIds()) {
-            if (statsPullerManager.PullerForMatcherExists(tagId)) {
-                if (atomMatcher->getTagIds().size() != 1) {
-                    return false;
-                }
-                pullTagId = tagId;
-            }
+        if (atomMatcher->getTagIds().size() != 1) {
+            return false;
         }
+        int atomTagId = *(atomMatcher->getTagIds().begin());
+        int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
@@ -404,7 +406,20 @@
     for (int i = 0; i < config.gauge_metric_size(); i++) {
         const GaugeMetric& metric = config.gauge_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
+            ALOGW("cannot find \"what\" in GaugeMetric \"%s\"", metric.name().c_str());
+            return false;
+        }
+
+        if ((!metric.gauge_fields().has_include_all() ||
+             (metric.gauge_fields().include_all() == false)) &&
+            metric.gauge_fields().field_num_size() == 0) {
+            ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+            return false;
+        }
+        if ((metric.gauge_fields().has_include_all() &&
+             metric.gauge_fields().include_all() == true) &&
+            metric.gauge_fields().field_num_size() > 0) {
+            ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
             return false;
         }
 
@@ -419,15 +434,11 @@
 
         sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
-        int pullTagId = -1;
-        for (int tagId : atomMatcher->getTagIds()) {
-            if (statsPullerManager.PullerForMatcherExists(tagId)) {
-                if (atomMatcher->getTagIds().size() != 1) {
-                    return false;
-                }
-                pullTagId = tagId;
-            }
+        if (atomMatcher->getTagIds().size() != 1) {
+            return false;
         }
+        int atomTagId = *(atomMatcher->getTagIds().begin());
+        int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
@@ -444,8 +455,8 @@
             }
         }
 
-        sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(key, metric, conditionIndex,
-                                                                   wizard, pullTagId, startTimeNs);
+        sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
+                key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs);
         allMetricProducers.push_back(gaugeProducer);
     }
     return true;
@@ -478,7 +489,7 @@
     return true;
 }
 
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, set<int>& allTagIds,
                       vector<sp<LogMatchingTracker>>& allAtomMatchers,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
@@ -502,7 +513,7 @@
         return false;
     }
 
-    if (!initMetrics(key, config, logTrackerMap, conditionTrackerMap, allAtomMatchers,
+    if (!initMetrics(key, config, timeBaseSec, logTrackerMap, conditionTrackerMap, allAtomMatchers,
                      allConditionTrackers, allMetricProducers, conditionToMetricMap,
                      trackerToMetricMap, metricProducerMap)) {
         ALOGE("initMetricProducers failed");
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 8e9c8e3..3337332 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -68,6 +68,7 @@
 // input:
 // [key]: the config key that this config belongs to
 // [config]: the input config
+// [timeBaseSec]: start time base for all metrics
 // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
 // output:
@@ -76,7 +77,7 @@
 //                          the list of MetricProducer index
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 bool initMetrics(
-        const ConfigKey& key, const StatsdConfig& config,
+        const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
         const std::unordered_map<std::string, int>& logTrackerMap,
         const std::unordered_map<std::string, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
@@ -88,7 +89,7 @@
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, std::set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, std::set<int>& allTagIds,
                       std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index bc8b0de..df29eb0 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -32,6 +32,9 @@
 
     // Notify interested listeners that the given apk and uid combination no longer exits.
     virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
+
+    // Notify the listener that the UidMap snapshot is available.
+    virtual void onUidMapReceived() = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6e7a613..21a9cf3 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -31,10 +31,9 @@
 namespace os {
 namespace statsd {
 
-UidMap::UidMap() : mBytesUsed(0) {
-}
-UidMap::~UidMap() {
-}
+UidMap::UidMap() : mBytesUsed(0) {}
+
+UidMap::~UidMap() {}
 
 bool UidMap::hasApp(int uid, const string& packageName) const {
     lock_guard<mutex> lock(mMutex);
@@ -48,6 +47,27 @@
     return false;
 }
 
+string UidMap::normalizeAppName(const string& appName) const {
+    string normalizedName = appName;
+    std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower);
+    return normalizedName;
+}
+
+std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+    lock_guard<mutex> lock(mMutex);
+    return getAppNamesFromUidLocked(uid,returnNormalized);
+}
+
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+    std::set<string> names;
+    auto range = mMap.equal_range(uid);
+    for (auto it = range.first; it != range.second; ++it) {
+        names.insert(returnNormalized ?
+            normalizeAppName(it->second.packageName) : it->second.packageName);
+    }
+    return names;
+}
+
 int64_t UidMap::getAppVersion(int uid, const string& packageName) const {
     lock_guard<mutex> lock(mMutex);
 
@@ -67,26 +87,41 @@
 
 void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
                        const vector<int64_t>& versionCode, const vector<String16>& packageName) {
-    lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
+    vector<wp<PackageInfoListener>> broadcastList;
+    {
+        lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
 
-    mMap.clear();
-    for (size_t j = 0; j < uid.size(); j++) {
-        mMap.insert(make_pair(uid[j],
-                              AppData(string(String8(packageName[j]).string()), versionCode[j])));
-    }
+        mMap.clear();
+        for (size_t j = 0; j < uid.size(); j++) {
+            mMap.insert(make_pair(
+                    uid[j], AppData(string(String8(packageName[j]).string()), versionCode[j])));
+        }
 
-    auto snapshot = mOutput.add_snapshots();
-    snapshot->set_timestamp_nanos(timestamp);
-    for (size_t j = 0; j < uid.size(); j++) {
-        auto t = snapshot->add_package_info();
-        t->set_name(string(String8(packageName[j]).string()));
-        t->set_version(int(versionCode[j]));
-        t->set_uid(uid[j]);
+        auto snapshot = mOutput.add_snapshots();
+        snapshot->set_timestamp_nanos(timestamp);
+        for (size_t j = 0; j < uid.size(); j++) {
+            auto t = snapshot->add_package_info();
+            t->set_name(string(String8(packageName[j]).string()));
+            t->set_version(int(versionCode[j]));
+            t->set_uid(uid[j]);
+        }
+        mBytesUsed += snapshot->ByteSize();
+        StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+        StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
+        ensureBytesUsedBelowLimit();
+        getListenerListCopyLocked(&broadcastList);
     }
-    mBytesUsed += snapshot->ByteSize();
-    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
-    StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
-    ensureBytesUsedBelowLimit();
+    // To avoid invoking callback while holding the internal lock. we get a copy of the listener
+    // list and invoke the callback. It's still possible that after we copy the list, a
+    // listener removes itself before we call it. It's then the listener's job to handle it (expect
+    // the callback to be called after listener is removed, and the listener should properly
+    // ignore it).
+    for (auto weakPtr : broadcastList) {
+        auto strongPtr = weakPtr.promote();
+        if (strongPtr != NULL) {
+            strongPtr->onUidMapReceived();
+        }
+    }
 }
 
 void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int64_t& versionCode) {
@@ -95,38 +130,45 @@
 
 void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
                        const int64_t& versionCode) {
-    lock_guard<mutex> lock(mMutex);
+    vector<wp<PackageInfoListener>> broadcastList;
+    string appName = string(String8(app_16).string());
+    {
+        lock_guard<mutex> lock(mMutex);
 
-    string app = string(String8(app_16).string());
+        auto log = mOutput.add_changes();
+        log->set_deletion(false);
+        log->set_timestamp_nanos(timestamp);
+        log->set_app(appName);
+        log->set_uid(uid);
+        log->set_version(versionCode);
+        mBytesUsed += log->ByteSize();
+        StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+        StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+        ensureBytesUsedBelowLimit();
 
-    // Notify any interested producers that this app has updated
-    for (auto it : mSubscribers) {
-        it->notifyAppUpgrade(app, uid, versionCode);
-    }
-
-    auto log = mOutput.add_changes();
-    log->set_deletion(false);
-    log->set_timestamp_nanos(timestamp);
-    log->set_app(app);
-    log->set_uid(uid);
-    log->set_version(versionCode);
-    mBytesUsed += log->ByteSize();
-    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
-    StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
-    ensureBytesUsedBelowLimit();
-
-    auto range = mMap.equal_range(int(uid));
-    for (auto it = range.first; it != range.second; ++it) {
-        if (it->second.packageName == app) {
-            it->second.versionCode = versionCode;
-            return;
+        auto range = mMap.equal_range(int(uid));
+        bool found = false;
+        for (auto it = range.first; it != range.second; ++it) {
+            // If we find the exact same app name and uid, update the app version directly.
+            if (it->second.packageName == appName) {
+                it->second.versionCode = versionCode;
+                found = true;
+                break;
+            }
         }
-        VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
-        return;
+        if (!found) {
+            // Otherwise, we need to add an app at this uid.
+            mMap.insert(make_pair(uid, AppData(appName, versionCode)));
+        }
+        getListenerListCopyLocked(&broadcastList);
     }
 
-    // Otherwise, we need to add an app at this uid.
-    mMap.insert(make_pair(uid, AppData(app, versionCode)));
+    for (auto weakPtr : broadcastList) {
+        auto strongPtr = weakPtr.promote();
+        if (strongPtr != NULL) {
+            strongPtr->notifyAppUpgrade(appName, uid, versionCode);
+        }
+    }
 }
 
 void UidMap::ensureBytesUsedBelowLimit() {
@@ -154,42 +196,60 @@
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
     removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
 }
-void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
-    lock_guard<mutex> lock(mMutex);
 
-    string app = string(String8(app_16).string());
-
-    for (auto it : mSubscribers) {
-        it->notifyAppRemoved(app, uid);
-    }
-
-    auto log = mOutput.add_changes();
-    log->set_deletion(true);
-    log->set_timestamp_nanos(timestamp);
-    log->set_app(app);
-    log->set_uid(uid);
-    mBytesUsed += log->ByteSize();
-    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
-    StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
-    ensureBytesUsedBelowLimit();
-
-    auto range = mMap.equal_range(int(uid));
-    for (auto it = range.first; it != range.second; ++it) {
-        if (it->second.packageName == app) {
-            mMap.erase(it);
-            return;
+void UidMap::getListenerListCopyLocked(vector<wp<PackageInfoListener>>* output) {
+    for (auto weakIt = mSubscribers.begin(); weakIt != mSubscribers.end();) {
+        auto strongPtr = weakIt->promote();
+        if (strongPtr != NULL) {
+            output->push_back(*weakIt);
+            weakIt++;
+        } else {
+            weakIt = mSubscribers.erase(weakIt);
+            VLOG("The UidMap listener is gone, remove it now");
         }
     }
-    VLOG("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
-    return;
 }
 
-void UidMap::addListener(sp<PackageInfoListener> producer) {
+void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
+    vector<wp<PackageInfoListener>> broadcastList;
+    string app = string(String8(app_16).string());
+    {
+        lock_guard<mutex> lock(mMutex);
+
+        auto log = mOutput.add_changes();
+        log->set_deletion(true);
+        log->set_timestamp_nanos(timestamp);
+        log->set_app(app);
+        log->set_uid(uid);
+        mBytesUsed += log->ByteSize();
+        StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+        StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+        ensureBytesUsedBelowLimit();
+
+        auto range = mMap.equal_range(int(uid));
+        for (auto it = range.first; it != range.second; ++it) {
+            if (it->second.packageName == app) {
+                mMap.erase(it);
+                break;
+            }
+        }
+        getListenerListCopyLocked(&broadcastList);
+    }
+
+    for (auto weakPtr : broadcastList) {
+        auto strongPtr = weakPtr.promote();
+        if (strongPtr != NULL) {
+            strongPtr->notifyAppRemoved(app, uid);
+        }
+    }
+}
+
+void UidMap::addListener(wp<PackageInfoListener> producer) {
     lock_guard<mutex> lock(mMutex);  // Lock for updates
     mSubscribers.insert(producer);
 }
 
-void UidMap::removeListener(sp<PackageInfoListener> producer) {
+void UidMap::removeListener(wp<PackageInfoListener> producer) {
     lock_guard<mutex> lock(mMutex);  // Lock for updates
     mSubscribers.erase(producer);
 }
@@ -250,7 +310,7 @@
     return m;
 }
 
-size_t UidMap::getBytesUsed() {
+size_t UidMap::getBytesUsed() const {
     return mBytesUsed;
 }
 
@@ -296,7 +356,7 @@
     return ret;
 }
 
-void UidMap::printUidMap(FILE* out) {
+void UidMap::printUidMap(FILE* out) const {
     lock_guard<mutex> lock(mMutex);
 
     for (auto it : mMap) {
@@ -330,6 +390,18 @@
     mLastUpdatePerConfigKey.erase(key);
 }
 
+set<int32_t> UidMap::getAppUid(const string& package) const {
+    lock_guard<mutex> lock(mMutex);
+
+    set<int32_t> results;
+    for (const auto& pair : mMap) {
+        if (pair.second.packageName == package) {
+            results.insert(pair.first);
+        }
+    }
+    return results;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 9e1ad69..a9aec94 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef STATSD_UIDMAP_H
-#define STATSD_UIDMAP_H
+#pragma once
 
 #include "config/ConfigKey.h"
 #include "config/ConfigListener.h"
@@ -66,18 +65,21 @@
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
     bool hasApp(int uid, const string& packageName) const;
 
+    // Returns the app names from uid.
+    std::set<string> getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const;
+
     int64_t getAppVersion(int uid, const string& packageName) const;
 
     // Helper for debugging contents of this uid map. Can be triggered with:
     // adb shell cmd stats print-uid-map
-    void printUidMap(FILE* out);
+    void printUidMap(FILE* out) const;
 
     // Commands for indicating to the map that a producer should be notified if an app is updated.
     // This allows the metric producer to distinguish when the same uid or app represents a
     // different version of an app.
-    void addListener(sp<PackageInfoListener> producer);
+    void addListener(wp<PackageInfoListener> producer);
     // Remove the listener from the set of metric producers that subscribe to updates.
-    void removeListener(sp<PackageInfoListener> producer);
+    void removeListener(wp<PackageInfoListener> producer);
 
     // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
     void OnConfigUpdated(const ConfigKey& key);
@@ -100,9 +102,14 @@
     void clearOutput();
 
     // Get currently cached value of memory used by UID map.
-    size_t getBytesUsed();
+    size_t getBytesUsed() const;
+
+    std::set<int32_t> getAppUid(const string& package) const;
 
 private:
+    std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
+    string normalizeAppName(const string& appName) const;
+
     void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
                    const vector<int64_t>& versionCode, const vector<String16>& packageName);
 
@@ -112,6 +119,8 @@
 
     UidMapping getOutput(const int64_t& timestamp, const ConfigKey& key);
 
+    void getListenerListCopyLocked(std::vector<wp<PackageInfoListener>>* output);
+
     // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
     mutable mutex mMutex;
     mutable mutex mIsolatedMutex;
@@ -128,7 +137,7 @@
     UidMapping mOutput;
 
     // Metric producers that should be notified if there's an upgrade in any app.
-    set<sp<PackageInfoListener>> mSubscribers;
+    set<wp<PackageInfoListener>> mSubscribers;
 
     // Mapping of config keys we're aware of to the epoch time they last received an update. This
     // lets us know it's safe to delete events older than the oldest update. The value is nanosec.
@@ -160,4 +169,3 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // STATSD_UIDMAP_H
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 20d9d5c..3c85c57 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -29,9 +29,10 @@
 
   oneof value {
     string value_str = 2;
-    int64 value_int = 3;
-    bool value_bool = 4;
-    float value_float = 5;
+    int32 value_int = 3;
+    int64 value_long = 4;
+    bool value_bool = 5;
+    float value_float = 6;
   }
 }
 
@@ -88,7 +89,7 @@
 
   optional int64 end_bucket_nanos = 2;
 
-  optional int64 gauge = 3;
+  optional Atom atom = 3;
 }
 
 message GaugeMetricData {
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
deleted file mode 100644
index bfa3254..0000000
--- a/cmds/statsd/src/stats_util.cpp
+++ /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.
- */
-
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// There is no existing hash function for the dimension key ("repeated KeyValuePair").
-// Temporarily use a string concatenation as the hashable key.
-// TODO: Find a better hash function for std::vector<KeyValuePair>.
-HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
-    std::string flattened;
-    for (const KeyValuePair& pair : keys) {
-        flattened += std::to_string(pair.key());
-        flattened += ":";
-        switch (pair.value_case()) {
-            case KeyValuePair::ValueCase::kValueStr:
-                flattened += pair.value_str();
-                break;
-            case KeyValuePair::ValueCase::kValueInt:
-                flattened += std::to_string(pair.value_int());
-                break;
-            case KeyValuePair::ValueCase::kValueBool:
-                flattened += std::to_string(pair.value_bool());
-                break;
-            case KeyValuePair::ValueCase::kValueFloat:
-                flattened += std::to_string(pair.value_float());
-                break;
-            default:
-                break;
-        }
-        flattened += "|";
-    }
-    return flattened;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 594561d..1cdf031 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -16,7 +16,10 @@
 
 #pragma once
 
+#include <sstream>
+#include "HashableDimensionKey.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "logd/LogReader.h"
 
 #include <unordered_map>
 
@@ -24,16 +27,45 @@
 namespace os {
 namespace statsd {
 
-#define DEFAULT_DIMENSION_KEY ""
+const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
 
-typedef std::string HashableDimensionKey;
+// Minimum bucket size in seconds
+const long kMinBucketSizeSec = 5 * 60;
 
 typedef std::map<std::string, HashableDimensionKey> ConditionKey;
 
 typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
 
-std::string getHashableKey(std::vector<KeyValuePair> key);
+/*
+ * In memory rep for LogEvent. Uses much less memory than LogEvent
+ */
+typedef struct EventKV {
+    std::vector<KeyValuePair> kv;
+    string ToString() const {
+        std::ostringstream result;
+        result << "{ ";
+        const size_t N = kv.size();
+        for (size_t i = 0; i < N; i++) {
+            result << " ";
+            result << (i + 1);
+            result << "->";
+            const auto& pair = kv[i];
+            if (pair.has_value_int()) {
+                result << pair.value_int();
+            } else if (pair.has_value_long()) {
+                result << pair.value_long();
+            } else if (pair.has_value_float()) {
+                result << pair.value_float();
+            } else if (pair.has_value_str()) {
+                result << pair.value_str().c_str();
+            }
+        }
+        result << " }";
+        return result.str();
+    }
+} EventKV;
 
+typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c9654af..4729f6a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -120,6 +120,11 @@
     repeated KeyMatcher key_in_condition = 3;
 }
 
+message FieldFilter {
+    optional bool include_all = 1;
+    repeated int32 field_num = 2;
+}
+
 message EventMetric {
     optional string name = 1;
 
@@ -170,7 +175,7 @@
 
     optional string what = 2;
 
-    optional int32 gauge_field = 3;
+    optional FieldFilter gauge_fields = 3;
 
     optional string condition = 4;
 
@@ -217,6 +222,11 @@
     optional int64 trigger_if_sum_gt = 6;
 }
 
+message AllowedLogSource {
+    repeated int32 uid = 1;
+    repeated string package = 2;
+}
+
 message StatsdConfig {
     optional string name = 1;
 
@@ -235,4 +245,6 @@
     repeated Predicate predicate = 8;
 
     repeated Alert alert = 9;
+
+    optional AllowedLogSource log_source = 10;
 }
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 696fddf..3d923e2 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -62,8 +62,8 @@
 }
 
 TEST(ConfigManagerTest, TestFakeConfig) {
-    auto metricsManager =
-            std::make_unique<MetricsManager>(ConfigKey(0, "test"), build_fake_config());
+    auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"),
+                                                           build_fake_config(), 1000, new UidMap());
     EXPECT_TRUE(metricsManager->isConfigValid());
 }
 
@@ -88,12 +88,6 @@
     {
         InSequence s;
 
-        // The built-in fake one.
-        // TODO: Remove this when we get rid of the fake one, and make this
-        // test loading one from disk somewhere.
-        EXPECT_CALL(*(listener.get()),
-                    OnConfigUpdated(ConfigKeyEq(1000, "fake"), StatsdConfigEq("CONFIG_12345")))
-                .RetiresOnSaturation();
         manager->Startup();
 
         // Add another one
@@ -147,7 +141,7 @@
 
     StatsdConfig config;
 
-    EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(6);
+    EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(5);
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx")));
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy")));
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")));
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index c6a5310..3c8ccab 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -42,6 +42,8 @@
 
 const ConfigKey kConfigKey(0, "test");
 
+const long timeBaseSec = 1000;
+
 StatsdConfig buildGoodConfig() {
     StatsdConfig config;
     config.set_name("12345");
@@ -275,7 +277,7 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, timeBaseSec,  allTagIds, allAtomMatchers,
                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
     EXPECT_EQ(1u, allMetricProducers.size());
@@ -293,7 +295,7 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
@@ -309,7 +311,7 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
@@ -324,7 +326,7 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
@@ -339,7 +341,7 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
@@ -355,7 +357,7 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
@@ -371,7 +373,7 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
                                   conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index aff06ba..9b96bb7 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -41,7 +41,7 @@
  */
 class MockMetricsManager : public MetricsManager {
 public:
-    MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig()) {
+    MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5b2cedd..3fa96d3 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -74,6 +74,14 @@
     EXPECT_TRUE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
     EXPECT_FALSE(m.hasApp(1000, "not.app"));
+
+    std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */);
+    EXPECT_TRUE(name_set.empty());
 }
 
 TEST(UidMapTest, TestAddAndRemove) {
@@ -90,12 +98,59 @@
     versions.push_back(5);
     m.updateMap(uids, versions, apps);
 
+    std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Update the app1 version.
     m.updateApp(String16(kApp1.c_str()), 1000, 40);
     EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
 
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
     m.removeApp(String16(kApp1.c_str()), 1000);
     EXPECT_FALSE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 1u);
+    EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Remove app2.
+    m.removeApp(String16(kApp2.c_str()), 1000);
+    EXPECT_FALSE(m.hasApp(1000, kApp1));
+    EXPECT_FALSE(m.hasApp(1000, kApp2));
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_TRUE(name_set.empty());
+}
+
+TEST(UidMapTest, TestUpdateApp) {
+    UidMap m;
+    m.updateMap({1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+    std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Adds a new name for uid 1000.
+    m.updateApp(String16("NeW_aPP1_NAmE"), 1000, 40);
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 3u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+    EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+    EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
+
+    // This name is also reused by another uid 2000.
+    m.updateApp(String16("NeW_aPP1_NAmE"), 2000, 1);
+    name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 1u);
+    EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+    EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
 }
 
 TEST(UidMapTest, TestClearingOutput) {
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index f385763..f62171d 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/anomaly/AnomalyTracker.h"
+#include "../metrics/metrics_test_helper.h"
 
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -32,7 +33,18 @@
 
 const ConfigKey kConfigKey(0, "test");
 
-void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
+HashableDimensionKey getMockDimensionKey(int key, string value) {
+    KeyValuePair pair;
+    pair.set_key(key);
+    pair.set_value_str(value);
+
+    vector<KeyValuePair> pairs;
+    pairs.push_back(pair);
+
+    return HashableDimensionKey(pairs);
+}
+
+void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
                       std::shared_ptr<DimToValMap> bucket) {
     for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
         (*bucket)[itr->first] += itr->second;
@@ -40,7 +52,7 @@
 }
 
 std::shared_ptr<DimToValMap> MockBucket(
-        const std::vector<std::pair<string, long>>& key_value_pair_list) {
+        const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) {
     std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
     AddValueToBucket(key_value_pair_list, bucket);
     return bucket;
@@ -54,20 +66,23 @@
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
+    HashableDimensionKey keyA = getMockDimensionKey(1, "a");
+    HashableDimensionKey keyB = getMockDimensionKey(1, "b");
+    HashableDimensionKey keyC = getMockDimensionKey(1, "c");
 
-    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
     int64_t eventTimestamp0 = 10;
-    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
+    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
     int64_t eventTimestamp1 = bucketSizeNs + 11;
-    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
+    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
     int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
-    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
+    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
     int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
-    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
+    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 1}});
     int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
-    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
+    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
     int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
-    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
+    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
     int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
 
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
@@ -79,9 +94,9 @@
     // Adds past bucket #0
     anomalyTracker.addPastBucket(bucket0, 0);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
     EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
@@ -90,9 +105,9 @@
     // Adds past bucket #0 again. The sum does not change.
     anomalyTracker.addPastBucket(bucket0, 0);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
     EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
@@ -102,9 +117,9 @@
     anomalyTracker.addPastBucket(bucket1, 1);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -113,9 +128,9 @@
     anomalyTracker.addPastBucket(bucket1, 1);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -124,8 +139,8 @@
     anomalyTracker.addPastBucket(bucket2, 2);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
     // Within refractory period.
@@ -135,8 +150,8 @@
     anomalyTracker.addPastBucket(bucket3, 3L);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -145,8 +160,8 @@
     anomalyTracker.addPastBucket(bucket4, 4);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
@@ -155,8 +170,8 @@
     anomalyTracker.addPastBucket(bucket5, 5);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
     // Within refractory period.
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
@@ -171,13 +186,18 @@
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
+    HashableDimensionKey keyA = getMockDimensionKey(1, "a");
+    HashableDimensionKey keyB = getMockDimensionKey(1, "b");
+    HashableDimensionKey keyC = getMockDimensionKey(1, "c");
+    HashableDimensionKey keyD = getMockDimensionKey(1, "d");
+    HashableDimensionKey keyE = getMockDimensionKey(1, "e");
 
-    std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
-    std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
-    std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
-    std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
-    std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
-    std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
+    std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
+    std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}});
+    std::shared_ptr<DimToValMap> bucket18 = MockBucket({{keyB, 1}, {keyC, 1}});
+    std::shared_ptr<DimToValMap> bucket20 = MockBucket({{keyB, 3}, {keyC, 1}});
+    std::shared_ptr<DimToValMap> bucket25 = MockBucket({{keyD, 1}});
+    std::shared_ptr<DimToValMap> bucket28 = MockBucket({{keyE, 2}});
 
     int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
     int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
@@ -196,9 +216,9 @@
     anomalyTracker.addPastBucket(bucket9, 9);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
@@ -211,27 +231,27 @@
     anomalyTracker.addPastBucket(bucket16, 16);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
     // Within refractory period.
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
 
     // Add past bucket #18
     anomalyTracker.addPastBucket(bucket18, 18);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
@@ -239,12 +259,12 @@
     anomalyTracker.addPastBucket(bucket18, 18);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
     // Within refractory period.
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
@@ -253,8 +273,8 @@
     anomalyTracker.addPastBucket(bucket20, 20);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -265,7 +285,7 @@
     anomalyTracker.addPastBucket(bucket25, 25);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
     EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -274,7 +294,7 @@
     EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
     // Updates current bucket #28.
-    (*bucket28)["e"] = 5;
+    (*bucket28)[keyE] = 5;
     EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 01ba82d..eb0fafe 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -63,7 +63,7 @@
     vector<KeyValuePair> kv_list;
     kv_list.push_back(kv1);
     map<string, HashableDimensionKey> queryKey;
-    queryKey[conditionName] = getHashableKey(kv_list);
+    queryKey[conditionName] = HashableDimensionKey(kv_list);
     return queryKey;
 }
 
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 312de1b..7658044 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -214,7 +214,7 @@
     stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2);
     stats.noteAtomLogged(android::util::DROPBOX_ERROR_CHANGED, now + 3);
     // pulled event, should ignore
-    stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFERRED, now + 4);
+    stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFER, now + 4);
 
     vector<uint8_t> output;
     stats.dumpStats(&output, false);
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 51eabd5..eec94539 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "metrics_test_helper.h"
 #include "src/metrics/CountMetricProducer.h"
+#include "metrics_test_helper.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -150,13 +150,13 @@
     event1.write("111");  // uid
     event1.init();
     ConditionKey key1;
-    key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+    key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
 
     LogEvent event2(1, bucketStartTimeNs + 10);
     event2.write("222");  // uid
     event2.init();
     ConditionKey key2;
-    key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+    key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index e4fc67f..baaac67 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "metrics_test_helper.h"
 #include "src/metrics/EventMetricProducer.h"
+#include "metrics_test_helper.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -100,13 +100,13 @@
     event1.write("111");  // uid
     event1.init();
     ConditionKey key1;
-    key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+    key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
 
     LogEvent event2(1, bucketStartTimeNs + 10);
     event2.write("222");  // uid
     event2.init();
     ConditionKey key2;
-    key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+    key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 59475d2..5204834 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/metrics/GaugeMetricProducer.h"
 #include "logd/LogEvent.h"
 #include "metrics_test_helper.h"
-#include "src/metrics/GaugeMetricProducer.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -26,6 +26,7 @@
 using std::set;
 using std::unordered_map;
 using std::vector;
+using std::make_shared;
 
 #ifdef __ANDROID__
 
@@ -34,120 +35,142 @@
 namespace statsd {
 
 const ConfigKey kConfigKey(0, "test");
+const int tagId = 1;
+const string metricName = "test_metric";
+const int64_t bucketStartTimeNs = 10000000000;
+const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
 
-TEST(GaugeMetricProducerTest, TestWithCondition) {
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
-
+TEST(GaugeMetricProducerTest, TestNoCondition) {
     GaugeMetric metric;
-    metric.set_name("1");
+    metric.set_name(metricName);
     metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_gauge_field(2);
+    metric.mutable_gauge_fields()->add_field_num(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    GaugeMetricProducer gaugeProducer(metric, 1 /*has condition*/, wizard, -1, bucketStartTimeNs);
+    // TODO: pending refactor of StatsPullerManager
+    // For now we still need this so that it doesn't do real pulling.
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
 
-    vector<std::shared_ptr<LogEvent>> allData;
-    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
-    event1->write(1);
-    event1->write(13);
-    event1->init();
-    allData.push_back(event1);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      tagId, tagId, bucketStartTimeNs, pullerManager);
 
-    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
-    event2->write(1);
-    event2->write(15);
-    event2->init();
-    allData.push_back(event2);
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+    event->write(tagId);
+    event->write(11);
+    event->init();
+    allData.push_back(event);
 
     gaugeProducer.onDataPulled(allData);
-    gaugeProducer.flushIfNeededLocked(event2->GetTimestampNs() + 1);
-    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(11, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
 
-    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 11);
-    gaugeProducer.onConditionChanged(false, bucketStartTimeNs + 21);
-    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + bucketSizeNs + 11);
-    std::shared_ptr<LogEvent> event3 =
-            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
-    event3->write(1);
-    event3->write(25);
-    event3->init();
-    allData.push_back(event3);
+    allData.clear();
+    std::shared_ptr<LogEvent> event2 =
+            std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10);
+    event2->write(tagId);
+    event2->write(25);
+    event2->init();
+    allData.push_back(event2);
     gaugeProducer.onDataPulled(allData);
-    gaugeProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 10);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(25, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     // One dimension.
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
-              gaugeProducer.mPastBuckets.begin()->second.front().mBucketStartNs);
+    EXPECT_EQ(11L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
+
+    gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs);
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    // One dimension.
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
 }
 
-TEST(GaugeMetricProducerTest, TestNoCondition) {
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
-
+TEST(GaugeMetricProducerTest, TestWithCondition) {
     GaugeMetric metric;
-    metric.set_name("1");
+    metric.set_name(metricName);
     metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_gauge_field(2);
+    metric.mutable_gauge_fields()->add_field_num(2);
+    metric.set_condition("SCREEN_ON");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+                event->write(tagId);
+                event->write(100);
+                event->init();
+                data->push_back(event);
+                return true;
+            }));
 
-    vector<std::shared_ptr<LogEvent>> allData;
-    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
-    event1->write(1);
-    event1->write(13);
-    event1->init();
-    allData.push_back(event1);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId, tagId,
+                                      bucketStartTimeNs, pullerManager);
 
-    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
-    event2->write(1);
-    event2->write(15);
-    event2->init();
-    allData.push_back(event2);
-
-    std::shared_ptr<LogEvent> event3 =
-            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
-    event3->write(1);
-    event3->write(25);
-    event3->init();
-    allData.push_back(event3);
-
-    gaugeProducer.onDataPulled(allData);
-    // Has one slice
+    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+    event->write(1);
+    event->write(110);
+    event->init();
+    allData.push_back(event);
+    gaugeProducer.onDataPulled(allData);
+
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+
+    gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10);
+    gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10);
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(13L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
-    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
-    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mGauge);
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
-              gaugeProducer.mPastBuckets.begin()->second.back().mBucketStartNs);
+    EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
 }
 
 TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+
     GaugeMetric metric;
-    metric.set_name("1");
+    metric.set_name(metricName);
     metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_gauge_field(2);
-    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+    metric.mutable_gauge_fields()->add_field_num(2);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      tagId, tagId, bucketStartTimeNs, pullerManager);
 
     Alert alert;
     alert.set_name("alert");
-    alert.set_metric_name("1");
+    alert.set_metric_name(metricName);
     alert.set_trigger_if_sum_gt(25);
     alert.set_number_of_buckets(2);
     sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
@@ -160,7 +183,7 @@
 
     gaugeProducer.onDataPulled({event1});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
 
     std::shared_ptr<LogEvent> event2 =
@@ -171,7 +194,7 @@
 
     gaugeProducer.onDataPulled({event2});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
 
     std::shared_ptr<LogEvent> event3 =
@@ -182,7 +205,7 @@
 
     gaugeProducer.onDataPulled({event3});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
 
     // The event4 does not have the gauge field. Thus the current bucket value is 0.
@@ -191,7 +214,8 @@
     event4->write(1);
     event4->init();
     gaugeProducer.onDataPulled({event4});
-    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(0, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
     EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
 }
 
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 4e5e0d6..7dac0fb 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/metrics/duration_helper/MaxDurationTracker.h"
 #include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
-#include "src/metrics/duration_helper/MaxDurationTracker.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -37,13 +37,18 @@
 namespace statsd {
 
 const ConfigKey kConfigKey(0, "test");
-const string eventKey = "event";
+
+const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1");
+const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1");
+const HashableDimensionKey key1 = getMockedDimensionKey(1, "1");
+const HashableDimensionKey key2 = getMockedDimensionKey(1, "2");
 
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey key1;
+    ConditionKey conditionKey1;
+    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -51,15 +56,15 @@
     MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
                                bucketSizeNs, {});
 
-    tracker.noteStart("1", true, bucketStartTimeNs, key1);
+    tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1);
     // Event starts again. This would not change anything as it already starts.
-    tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
+    tracker.noteStart(key1, true, bucketStartTimeNs + 3, conditionKey1);
     // Stopped.
-    tracker.noteStop("1", bucketStartTimeNs + 10, false);
+    tracker.noteStop(key1, bucketStartTimeNs + 10, false);
 
     // Another event starts in this bucket.
-    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
-    tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
+    tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -71,7 +76,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey key1;
+    ConditionKey conditionKey1;
+    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -79,10 +85,10 @@
     MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
                                bucketSizeNs, {});
 
-    tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
+    tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1);
 
     // Another event starts in this bucket.
-    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
     tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
     EXPECT_TRUE(tracker.mInfos.empty());
@@ -101,7 +107,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey key1;
+    ConditionKey conditionKey1;
+    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -110,14 +117,16 @@
                                bucketSizeNs, {});
 
     // The event starts.
-    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
 
     // Starts again. Does not change anything.
-    tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
+                      conditionKey1);
 
     // The event stops at early 4th bucket.
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
-    tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
+                     false /*stop all*/);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
     EXPECT_EQ(3u, buckets[eventKey].size());
     EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration);
@@ -129,7 +138,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey key1;
+    ConditionKey conditionKey1;
+    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -138,10 +148,10 @@
                                bucketSizeNs, {});
 
     // 2 starts
-    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
-    tracker.noteStart("", true, bucketStartTimeNs + 10, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, conditionKey1);
     // one stop
-    tracker.noteStop("", bucketStartTimeNs + 20, false /*stop all*/);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
 
@@ -151,7 +161,7 @@
     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
 
     // real stop now.
-    tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
 
     EXPECT_EQ(3u, buckets[eventKey].size());
@@ -163,10 +173,11 @@
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    ConditionKey conditionKey1;
+    HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps");
+    conditionKey1["APP_BACKGROUND"] = conditionKey;
 
-    EXPECT_CALL(*wizard, query(_, key1))  // #4
+    EXPECT_CALL(*wizard, query(_, conditionKey1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -180,11 +191,11 @@
                                bucketSizeNs, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.noteStop(key1, eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -195,15 +206,15 @@
 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
     Alert alert;
     alert.set_name("alert");
-    alert.set_metric_name("1");
+    alert.set_metric_name("metric");
     alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
     alert.set_number_of_buckets(2);
     alert.set_refractory_period_secs(1);
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    ConditionKey conditionKey1;
+    conditionKey1["APP_BACKGROUND"] = conditionKey;
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -212,14 +223,14 @@
     MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
                                bucketSizeNs, {anomalyTracker});
 
-    tracker.noteStart("1", true, eventStartTimeNs, key1);
-    tracker.noteStop("1", eventStartTimeNs + 10, false);
+    tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
+    tracker.noteStop(key1, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
     EXPECT_EQ(10LL, tracker.mDuration);
 
-    tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
+    tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
-    tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
+    tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
     EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
     EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
               (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 99d3e05..9ec302f 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/metrics/duration_helper/OringDurationTracker.h"
 #include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
-#include "src/metrics/duration_helper/OringDurationTracker.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -35,13 +35,17 @@
 namespace statsd {
 
 const ConfigKey kConfigKey(0, "test");
-const string eventKey = "event";
+const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event");
+
+const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps");
+const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps");
+const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps");
 
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
@@ -53,12 +57,12 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
                                  bucketStartTimeNs, bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
-    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1);  // overlapping wl
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
 
@@ -70,7 +74,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
@@ -81,11 +85,11 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
-    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1);  // overlapping wl
 
-    tracker.noteStop("2:maps", eventStartTimeNs + 2000, false);
-    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -97,7 +101,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
@@ -108,8 +112,8 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
-    tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, key1);  // overlapping wl
 
     tracker.noteStopAll(eventStartTimeNs + 2003);
 
@@ -123,7 +127,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
@@ -135,18 +139,18 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets);
-    tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, key1);
     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
 
     EXPECT_EQ(2u, buckets[eventKey].size());
     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
-    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
     EXPECT_EQ(2u, buckets[eventKey].size());
@@ -158,7 +162,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
@@ -173,11 +177,11 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
                                  bucketStartTimeNs, bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -189,7 +193,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))
             .Times(2)
@@ -206,13 +210,13 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
                                  bucketStartTimeNs, bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
     // condition to true.
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 1000);
     // 2nd duration: 1000ns
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -224,7 +228,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
@@ -238,14 +242,14 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketSizeNs, {});
 
-    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
-    tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + 3, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -264,7 +268,7 @@
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -274,22 +278,22 @@
                                  bucketSizeNs, {anomalyTracker});
 
     // Nothing in the past bucket.
-    tracker.noteStart("", true, eventStartTimeNs, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
     EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
 
-    tracker.noteStop("", eventStartTimeNs + 3, false);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
     EXPECT_EQ(0u, buckets[eventKey].size());
 
     uint64_t event1StartTimeNs = eventStartTimeNs + 10;
-    tracker.noteStart("1", true, event1StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event1StartTimeNs, key1);
     // No past buckets. The anomaly will happen in bucket #0.
     EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
 
     uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
     tracker.flushIfNeeded(event1StopTimeNs, &buckets);
-    tracker.noteStop("1", event1StopTimeNs, false);
+    tracker.noteStop(kEventKey1, event1StopTimeNs, false);
 
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
     EXPECT_EQ(1u, buckets[eventKey].size());
@@ -301,16 +305,16 @@
 
     // One past buckets. The anomaly will happen in bucket #1.
     uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
-    tracker.noteStart("1", true, event2StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event2StartTimeNs, key1);
     EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
                           bucket1Duration),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
-    tracker.noteStop("1", event2StartTimeNs + 1, false);
+    tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false);
 
     // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
     // bucket #2.
     uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
-    tracker.noteStart("1", true, event3StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event3StartTimeNs, key1);
     EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
 }
@@ -326,7 +330,7 @@
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = "1:maps|";
+    key1["APP_BACKGROUND"] = kConditionKey1;
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -335,21 +339,21 @@
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
                                  bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
 
-    tracker.noteStart("", true, eventStartTimeNs, key1);
-    tracker.noteStop("", eventStartTimeNs + 10, false);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
     EXPECT_TRUE(tracker.mStarted.empty());
     EXPECT_EQ(10LL, tracker.mDuration);
 
     EXPECT_EQ(0u, tracker.mStarted.size());
 
-    tracker.noteStart("", true, eventStartTimeNs + 20, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs + 20, key1);
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
-    tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
-    EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
+    tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+    EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
     EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
               anomalyTracker->mLastAlarmTimestampNs);
 }
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
new file mode 100644
index 0000000..a0a854a
--- /dev/null
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -0,0 +1,34 @@
+// 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.
+
+#include "metrics_test_helper.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+HashableDimensionKey getMockedDimensionKey(int key, string value) {
+    KeyValuePair pair;
+    pair.set_key(key);
+    pair.set_value_str(value);
+
+    vector<KeyValuePair> pairs;
+    pairs.push_back(pair);
+
+    return HashableDimensionKey(pairs);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index fa221aa..7cb3329 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -38,6 +38,8 @@
     MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
 };
 
+HashableDimensionKey getMockedDimensionKey(int key, std::string value);
+
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
index 6b0531d..7bd15d7 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -21,12 +21,12 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += ../../src/stats_log.proto \
                    ../../src/atoms.proto
+
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := lite
-LOCAL_CERTIFICATE := platform
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_DEX_PREOPT := false
 LOCAL_PROGUARD_ENABLED := disabled
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
index cd76c9d..7bfde40 100644
--- a/cmds/statsd/tools/dogfood/AndroidManifest.xml
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -18,7 +18,6 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.statsd.dogfood"
-    android:sharedUserId="android.uid.system"
     android:versionCode="1"
     android:versionName="1.0" >
 
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
index 3997897..5d35c29 100644
--- a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
+++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
@@ -111,6 +111,23 @@
                 android:text="@string/screen_off"/>
         </LinearLayout>
 
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/custom_start"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/custom_start" />
+
+            <Button
+                android:id="@+id/custom_stop"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/custom_stop" />
+        </LinearLayout>
+
         <Button android:id="@+id/dump"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
index d5b8fed..0329992 100644
--- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
index 7690df6..0eab0f4 100644
--- a/cmds/statsd/tools/dogfood/res/values/strings.xml
+++ b/cmds/statsd/tools/dogfood/res/values/strings.xml
@@ -47,6 +47,9 @@
     <string name="screen_on">Screen On</string>
     <string name="screen_off">Screen Off</string>
 
+    <string name="custom_start">App hook start</string>
+    <string name="custom_stop">App hook stop</string>
+
     <string name="dump">DumpReport</string>
     <string name="report_header">Report details</string>
 </resources>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index c83802e..70dd634 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -140,6 +140,20 @@
             }
         });
 
+        findViewById(R.id.custom_start).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.logStart(8);
+            }
+        });
+
+        findViewById(R.id.custom_stop).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.logStop(8);
+            }
+        });
+
         mReportText = (TextView) findViewById(R.id.report_text);
 
         findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
index 2a254df..f10b69d 100644
--- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
+++ b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
@@ -137,13 +137,54 @@
                 android:text="@integer/duration_default"
                 android:textSize="30dp"/>
         </LinearLayout>
-	<CheckBox
+        <CheckBox
             android:id="@+id/placebo"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/placebo"
             android:checked="false" />
 
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <CheckBox
+                android:id="@+id/include_count"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/count"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_duration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/duration"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_event"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/event"
+                android:checked="true"/>
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <CheckBox
+                android:id="@+id/include_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/value"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_gauge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/gauge"
+                android:checked="true"/>
+        </LinearLayout>
+
         <Space
             android:layout_width="1dp"
             android:layout_height="30dp"/>
diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml
index 522337e..d0f77c6 100644
--- a/cmds/statsd/tools/loadtest/res/values/strings.xml
+++ b/cmds/statsd/tools/loadtest/res/values/strings.xml
@@ -26,5 +26,10 @@
     <string name="duration_label">test duration (mins):&#160;</string>
     <string name="start"> &#160;Start&#160; </string>
     <string name="stop"> &#160;Stop&#160; </string>
+    <string name="count"> count </string>
+    <string name="duration"> duration </string>
+    <string name="event"> event </string>
+    <string name="value"> value </string>
+    <string name="gauge"> gauge </string>
 
 </resources>
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
index 0d890fb..5fc4cf5 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
@@ -83,7 +83,9 @@
      * @param placebo If true, only return an empty config
      * @return The serialized config
      */
-  public byte[] getConfig(int replication, long bucketMillis, boolean placebo) {
+  public byte[] getConfig(int replication, long bucketMillis, boolean placebo, boolean includeCount,
+                          boolean includeDuration, boolean includeEvent, boolean includeValue,
+                          boolean includeGauge) {
         StatsdConfig.Builder config = StatsdConfig.newBuilder()
             .setName(CONFIG_NAME);
         if (placebo) {
@@ -92,25 +94,35 @@
         int numMetrics = 0;
         for (int i = 0; i < replication; i++) {
             // metrics
-            for (EventMetric metric : mTemplate.getEventMetricList()) {
-                addEventMetric(metric, i, config);
-                numMetrics++;
+            if (includeEvent) {
+                for (EventMetric metric : mTemplate.getEventMetricList()) {
+                    addEventMetric(metric, i, config);
+                    numMetrics++;
+                }
             }
-            for (CountMetric metric : mTemplate.getCountMetricList()) {
-                addCountMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeCount) {
+                for (CountMetric metric : mTemplate.getCountMetricList()) {
+                    addCountMetric(metric, i, bucketMillis, config);
+                    numMetrics++;
+                }
             }
-            for (DurationMetric metric : mTemplate.getDurationMetricList()) {
-                addDurationMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeDuration) {
+                for (DurationMetric metric : mTemplate.getDurationMetricList()) {
+                    addDurationMetric(metric, i, bucketMillis, config);
+                    numMetrics++;
+                }
             }
-            for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
-                addGaugeMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeGauge) {
+                for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
+                    addGaugeMetric(metric, i, bucketMillis, config);
+                    numMetrics++;
+                }
             }
-            for (ValueMetric metric : mTemplate.getValueMetricList()) {
-                addValueMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeValue) {
+                for (ValueMetric metric : mTemplate.getValueMetricList()) {
+                    addValueMetric(metric, i, bucketMillis, config);
+                    numMetrics++;
+                }
             }
             // predicates
             for (Predicate predicate : mTemplate.getPredicateList()) {
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index a72f72e..83f4b7b 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -110,6 +110,11 @@
     private EditText mDurationText;
     private TextView mReportText;
     private CheckBox mPlaceboCheckBox;
+    private CheckBox mCountMetricCheckBox;
+    private CheckBox mDurationMetricCheckBox;
+    private CheckBox mEventMetricCheckBox;
+    private CheckBox mValueMetricCheckBox;
+    private CheckBox mGaugeMetricCheckBox;
 
     /** When the load test started. */
     private long mStartedTimeMillis;
@@ -129,6 +134,31 @@
      */
     private boolean mPlacebo;
 
+    /**
+     * Whether to include CountMetric in the config.
+     */
+    private boolean mIncludeCountMetric;
+
+    /**
+     * Whether to include DurationMetric in the config.
+     */
+    private boolean mIncludeDurationMetric;
+
+    /**
+     * Whether to include EventMetric in the config.
+     */
+    private boolean mIncludeEventMetric;
+
+    /**
+     * Whether to include ValueMetric in the config.
+     */
+    private boolean mIncludeValueMetric;
+
+    /**
+     * Whether to include GaugeMetric in the config.
+     */
+    private boolean mIncludeGaugeMetric;
+
     /** The burst size. */
     private int mBurst;
 
@@ -170,6 +200,7 @@
         initPeriod();
         initDuration();
         initPlacebo();
+        initMetricWhitelist();
 
         // Hide the keyboard outside edit texts.
         findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() {
@@ -329,7 +360,9 @@
         getData();
 
         // Create a config and push it to statsd.
-        if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo))) {
+        if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo,
+                mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric,
+                mIncludeValueMetric, mIncludeGaugeMetric))) {
             return;
         }
 
@@ -419,11 +452,6 @@
     private void clearConfigs() {
         // TODO: Clear all configs instead of specific ones.
         if (mStatsManager != null) {
-            if (!mStatsManager.removeConfiguration("fake")) {
-                Log.d(TAG, "Removed \"fake\" statsd configs.");
-            } else {
-                Log.d(TAG, "Failed to remove \"fake\" config. Loadtest results cannot be trusted.");
-            }
             if (mStarted) {
                 if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_NAME)) {
                     Log.d(TAG, "Removed loadtest statsd configs.");
@@ -553,4 +581,48 @@
             }
         });
     }
+
+    private void initMetricWhitelist() {
+        mCountMetricCheckBox = findViewById(R.id.include_count);
+        mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+            }
+        });
+        mDurationMetricCheckBox = findViewById(R.id.include_duration);
+        mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+            }
+        });
+        mEventMetricCheckBox = findViewById(R.id.include_event);
+        mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+            }
+        });
+        mValueMetricCheckBox = findViewById(R.id.include_value);
+        mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+            }
+        });
+        mGaugeMetricCheckBox = findViewById(R.id.include_gauge);
+        mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+            }
+        });
+
+        mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+        mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+        mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+        mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+        mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+    }
 }
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index 008bb93..ed99f3e 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
     $(call all-java-files-under, ../library/core-src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_MODULE := uiautomator-instrumentation
 # TODO: change this to 18 when it's available
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 847082a..1adae7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -457,6 +457,20 @@
     /** @hide User operation call: one of related users cannot be stopped. */
     public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
 
+    /**
+     * @hide
+     * Process states, describing the kind of state a particular process is in.
+     * When updating these, make sure to also check all related references to the
+     * constant in code, and update these arrays:
+     *
+     * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+     * @see com.android.server.am.ProcessList#sProcStateToProcMem
+     * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+     * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+     * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+     * @see com.android.server.am.ProcessList#sTestSamePssTimes
+     */
+
     /** @hide Not a real process state. */
     public static final int PROCESS_STATE_UNKNOWN = -1;
 
@@ -476,35 +490,35 @@
     /** @hide Process is hosting a foreground service. */
     public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
 
-    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
-    public static final int PROCESS_STATE_TOP_SLEEPING = 5;
-
     /** @hide Process is important to the user, and something they are aware of. */
-    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
+    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
 
     /** @hide Process is important to the user, but not something they are aware of. */
-    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
+    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
 
     /** @hide Process is in the background transient so we will try to keep running. */
-    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
+    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 7;
 
     /** @hide Process is in the background running a backup/restore operation. */
-    public static final int PROCESS_STATE_BACKUP = 9;
-
-    /** @hide Process is in the background, but it can't restore its state so we want
-     * to try to avoid killing it. */
-    public static final int PROCESS_STATE_HEAVY_WEIGHT = 10;
+    public static final int PROCESS_STATE_BACKUP = 8;
 
     /** @hide Process is in the background running a service.  Unlike oom_adj, this level
      * is used for both the normal running in background state and the executing
      * operations state. */
-    public static final int PROCESS_STATE_SERVICE = 11;
+    public static final int PROCESS_STATE_SERVICE = 9;
 
     /** @hide Process is in the background running a receiver.   Note that from the
      * perspective of oom_adj, receivers run at a higher foreground level, but for our
      * prioritization here that is not necessary and putting them below services means
      * many fewer changes in some process states as they receive broadcasts. */
-    public static final int PROCESS_STATE_RECEIVER = 12;
+    public static final int PROCESS_STATE_RECEIVER = 10;
+
+    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+    public static final int PROCESS_STATE_TOP_SLEEPING = 11;
+
+    /** @hide Process is in the background, but it can't restore its state so we want
+     * to try to avoid killing it. */
+    public static final int PROCESS_STATE_HEAVY_WEIGHT = 12;
 
     /** @hide Process is in the background but hosts the home activity. */
     public static final int PROCESS_STATE_HOME = 13;
@@ -810,7 +824,7 @@
      * impose on your application to let the overall system work best.  The
      * returned value is in megabytes; the baseline Android memory class is
      * 16 (which happens to be the Java heap limit of those devices); some
-     * device with more memory may return 24 or even higher numbers.
+     * devices with more memory may return 24 or even higher numbers.
      */
     public int getMemoryClass() {
         return staticGetMemoryClass();
@@ -837,7 +851,7 @@
      * constrained devices, or it may be significantly larger on devices with
      * a large amount of available RAM.
      *
-     * <p>The is the size of the application's Dalvik heap if it has
+     * <p>This is the size of the application's Dalvik heap if it has
      * specified <code>android:largeHeap="true"</code> in its manifest.
      */
     public int getLargeMemoryClass() {
@@ -2884,13 +2898,13 @@
         public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
 
         /**
-         * Constant for {@link #importance}: This process is running the foreground
-         * UI, but the device is asleep so it is not visible to the user.  This means
-         * the user is not really aware of the process, because they can not see or
-         * interact with it, but it is quite important because it what they expect to
-         * return to once unlocking the device.
+         * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+         * {@link #IMPORTANCE_TOP_SLEEPING}.  As of Android
+         * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+         * important since we want to reduce what apps can do when the screen is off.
          */
-        public static final int IMPORTANCE_TOP_SLEEPING = 150;
+        @Deprecated
+        public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
 
         /**
          * Constant for {@link #importance}: This process is running something
@@ -2942,14 +2956,6 @@
         public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
 
         /**
-         * Constant for {@link #importance}: This process is running an
-         * application that can not save its state, and thus can't be killed
-         * while in the background.  This will be used with apps that have
-         * {@link android.R.attr#cantSaveState} set on their application tag.
-         */
-        public static final int IMPORTANCE_CANT_SAVE_STATE = 270;
-
-        /**
          * Constant for {@link #importance}: This process is contains services
          * that should remain running.  These are background services apps have
          * started, not something the user is aware of, so they may be killed by
@@ -2959,6 +2965,23 @@
         public static final int IMPORTANCE_SERVICE = 300;
 
         /**
+         * Constant for {@link #importance}: This process is running the foreground
+         * UI, but the device is asleep so it is not visible to the user.  Though the
+         * system will try hard to keep its process from being killed, in all other
+         * ways we consider it a kind of cached process, with the limitations that go
+         * along with that state: network access, running background services, etc.
+         */
+        public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+        /**
+         * Constant for {@link #importance}: This process is running an
+         * application that can not save its state, and thus can't be killed
+         * while in the background.  This will be used with apps that have
+         * {@link android.R.attr#cantSaveState} set on their application tag.
+         */
+        public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
+
+        /**
          * Constant for {@link #importance}: This process process contains
          * cached code that is expendable, not actively running any app components
          * we care about.
@@ -2993,16 +3016,16 @@
                 return IMPORTANCE_GONE;
             } else if (procState >= PROCESS_STATE_HOME) {
                 return IMPORTANCE_CACHED;
-            } else if (procState >= PROCESS_STATE_SERVICE) {
-                return IMPORTANCE_SERVICE;
             } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
                 return IMPORTANCE_CANT_SAVE_STATE;
+            } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+                return IMPORTANCE_TOP_SLEEPING;
+            } else if (procState >= PROCESS_STATE_SERVICE) {
+                return IMPORTANCE_SERVICE;
             } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
                 return IMPORTANCE_PERCEPTIBLE;
             } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
                 return IMPORTANCE_VISIBLE;
-            } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
-                return IMPORTANCE_TOP_SLEEPING;
             } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
                 return IMPORTANCE_FOREGROUND_SERVICE;
             } else {
@@ -3036,6 +3059,8 @@
                 switch (importance) {
                     case IMPORTANCE_PERCEPTIBLE:
                         return IMPORTANCE_PERCEPTIBLE_PRE_26;
+                    case IMPORTANCE_TOP_SLEEPING:
+                        return IMPORTANCE_TOP_SLEEPING_PRE_28;
                     case IMPORTANCE_CANT_SAVE_STATE:
                         return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
                 }
@@ -3049,16 +3074,18 @@
                 return PROCESS_STATE_NONEXISTENT;
             } else if (importance >= IMPORTANCE_CACHED) {
                 return PROCESS_STATE_HOME;
+            } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
+                return PROCESS_STATE_HEAVY_WEIGHT;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+                return PROCESS_STATE_TOP_SLEEPING;
             } else if (importance >= IMPORTANCE_SERVICE) {
                 return PROCESS_STATE_SERVICE;
-            } else if (importance == IMPORTANCE_CANT_SAVE_STATE) {
-                return PROCESS_STATE_HEAVY_WEIGHT;
             } else if (importance >= IMPORTANCE_PERCEPTIBLE) {
                 return PROCESS_STATE_TRANSIENT_BACKGROUND;
             } else if (importance >= IMPORTANCE_VISIBLE) {
                 return PROCESS_STATE_IMPORTANT_FOREGROUND;
-            } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
-                return PROCESS_STATE_TOP_SLEEPING;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+                return PROCESS_STATE_FOREGROUND_SERVICE;
             } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
                 return PROCESS_STATE_FOREGROUND_SERVICE;
             } else {
@@ -3837,7 +3864,7 @@
         pw.println();
         dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName });
         pw.println();
-        dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName });
+        dumpService(pw, fd, "usagestats", new String[] { packageName });
         pw.println();
         dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName });
         pw.flush();
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 6666fcc..60a5a11 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -99,7 +99,10 @@
     // Called by the power manager.
     public abstract void onWakefulnessChanged(int wakefulness);
 
-    public abstract int startIsolatedProcess(String entryPoint, String[] mainArgs,
+    /**
+     * @return {@code true} if process start is successful, {@code false} otherwise.
+     */
+    public abstract boolean startIsolatedProcess(String entryPoint, String[] mainArgs,
             String processName, String abiOverride, int uid, Runnable crashHandler);
 
     /**
@@ -309,4 +312,11 @@
      * Returns {@code true} if {@code uid} is running an activity from {@code packageName}.
      */
     public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
+
+    public interface ScreenObserver {
+        public void onAwakeStateChanged(boolean isAwake);
+        public void onKeyguardStateChanged(boolean isShowing);
+    }
+
+    public abstract void registerScreenObserver(ScreenObserver observer);
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4a21f5c..e61c5b7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -36,6 +36,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.transition.Transition;
 import android.transition.TransitionListenerAdapter;
 import android.transition.TransitionManager;
@@ -265,6 +266,8 @@
     public static final int ANIM_CUSTOM_IN_PLACE = 10;
     /** @hide */
     public static final int ANIM_CLIP_REVEAL = 11;
+    /** @hide */
+    public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
 
     private String mPackageName;
     private Rect mLaunchBounds;
@@ -486,6 +489,19 @@
     }
 
     /**
+     * Creates an {@link ActivityOptions} object specifying an animation where the new activity
+     * is started in another user profile by calling {@link
+     * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+     * }.
+     * @hide
+     */
+    public static ActivityOptions makeOpenCrossProfileAppsAnimation() {
+        ActivityOptions options = new ActivityOptions();
+        options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS;
+        return options;
+    }
+
+    /**
      * Create an ActivityOptions specifying an animation where a thumbnail
      * is scaled from a given position to the new activity window that is
      * being started.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index da57efa..aaa6bf0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -225,6 +225,12 @@
      */
     public static final long INVALID_PROC_STATE_SEQ = -1;
 
+    /**
+     * Identifier for the sequence no. associated with this process start. It will be provided
+     * as one of the arguments when the process starts.
+     */
+    public static final String PROC_START_SEQ_IDENT = "seq=";
+
     private final Object mNetworkPolicyLock = new Object();
 
     /**
@@ -1146,7 +1152,7 @@
             int N = stats.dbStats.size();
             if (N > 0) {
                 pw.println(" DATABASES");
-                printRow(pw, "  %8s %8s %14s %14s  %s", "pgsz", "dbsz", "Lookaside(b)", "cache",
+                printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache",
                         "Dbname");
                 for (int i = 0; i < N; i++) {
                     DbStats dbStats = stats.dbStats.get(i);
@@ -1178,6 +1184,124 @@
         }
 
         @Override
+        public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem,
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+                boolean dumpUnreachable, String[] args) {
+            ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor());
+            try {
+                dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+            } finally {
+                proto.flush();
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+                boolean dumpFullInfo, boolean dumpDalvik,
+                boolean dumpSummaryOnly, boolean dumpUnreachable) {
+            long nativeMax = Debug.getNativeHeapSize() / 1024;
+            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+            Runtime runtime = Runtime.getRuntime();
+            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
+            long dalvikMax = runtime.totalMemory() / 1024;
+            long dalvikFree = runtime.freeMemory() / 1024;
+            long dalvikAllocated = dalvikMax - dalvikFree;
+
+            Class[] classesToCount = new Class[] {
+                    ContextImpl.class,
+                    Activity.class,
+                    WebView.class,
+                    OpenSSLSocketImpl.class
+            };
+            long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+            long appContextInstanceCount = instanceCounts[0];
+            long activityInstanceCount = instanceCounts[1];
+            long webviewInstanceCount = instanceCounts[2];
+            long openSslSocketCount = instanceCounts[3];
+
+            long viewInstanceCount = ViewDebug.getViewInstanceCount();
+            long viewRootInstanceCount = ViewDebug.getViewRootImplCount();
+            int globalAssetCount = AssetManager.getGlobalAssetCount();
+            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+            long parcelSize = Parcel.getGlobalAllocSize();
+            long parcelCount = Parcel.getGlobalAllocCount();
+            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+            final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY);
+            proto.write(MemInfoProto.ProcessMemory.PID, Process.myPid());
+            proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME,
+                    (mBoundApplication != null) ? mBoundApplication.processName : "unknown");
+            dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
+                    nativeMax, nativeAllocated, nativeFree,
+                    dalvikMax, dalvikAllocated, dalvikFree);
+            proto.end(mToken);
+
+            final long oToken = proto.start(MemInfoProto.AppData.OBJECTS);
+            proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
+                    viewRootInstanceCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
+                    appContextInstanceCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
+                    activityInstanceCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
+                    globalAssetManagerCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
+                    binderLocalObjectCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
+                    binderProxyObjectCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_MEMORY_KB, parcelSize / 1024);
+            proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
+                    binderDeathObjectCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount);
+            proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
+                    webviewInstanceCount);
+            proto.end(oToken);
+
+            // SQLite mem info
+            final long sToken = proto.start(MemInfoProto.AppData.SQL);
+            proto.write(MemInfoProto.AppData.SqlStats.MEMORY_USED_KB, stats.memoryUsed / 1024);
+            proto.write(MemInfoProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
+                    stats.pageCacheOverflow / 1024);
+            proto.write(MemInfoProto.AppData.SqlStats.MALLOC_SIZE_KB, stats.largestMemAlloc / 1024);
+            int n = stats.dbStats.size();
+            for (int i = 0; i < n; i++) {
+                DbStats dbStats = stats.dbStats.get(i);
+
+                final long dToken = proto.start(MemInfoProto.AppData.SqlStats.DATABASES);
+                proto.write(MemInfoProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
+                proto.write(MemInfoProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
+                proto.write(MemInfoProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
+                proto.write(MemInfoProto.AppData.SqlStats.Database.LOOKASIDE_B, dbStats.lookaside);
+                proto.write(MemInfoProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
+                proto.end(dToken);
+            }
+            proto.end(sToken);
+
+            // Asset details.
+            String assetAlloc = AssetManager.getAssetAllocations();
+            if (assetAlloc != null) {
+                proto.write(MemInfoProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
+            }
+
+            // Unreachable native memory
+            if (dumpUnreachable) {
+                int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags;
+                boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
+                        || android.os.Build.IS_DEBUGGABLE;
+                proto.write(MemInfoProto.AppData.UNREACHABLE_MEMORY,
+                        Debug.getUnreachableMemory(100, showContents));
+            }
+        }
+
+        @Override
         public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
             nDumpGraphicsInfo(pfd.getFileDescriptor());
             WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args);
@@ -1644,7 +1768,9 @@
                             (String[]) ((SomeArgs) msg.obj).arg2);
                     break;
                 case EXECUTE_TRANSACTION:
-                    mTransactionExecutor.execute(((ClientTransaction) msg.obj));
+                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
+                    mTransactionExecutor.execute(transaction);
+                    transaction.recycle();
                     break;
             }
             Object obj = msg.obj;
@@ -2329,23 +2455,23 @@
      *
      * @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss
      */
-    private static void dumpHeap(ProtoOutputStream proto, long fieldId, String name,
+    private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name,
             int pss, int cleanPss, int sharedDirty, int privateDirty,
             int sharedClean, int privateClean,
             boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss) {
         final long token = proto.start(fieldId);
 
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.NAME, name);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.TOTAL_PSS_KB, pss);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.CLEAN_PSS_KB, cleanPss);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
-        proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.NAME, name);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
+        proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
         if (hasSwappedOutPss) {
-            proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
+            proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
         } else {
-            proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
+            proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
         }
 
         proto.end(token);
@@ -2360,26 +2486,26 @@
             long dalvikMax, long dalvikAllocated, long dalvikFree) {
 
         if (!dumpSummaryOnly) {
-            final long nhToken = proto.start(MemInfoProto.NativeProcess.NATIVE_HEAP);
-            dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Native Heap",
+            final long nhToken = proto.start(MemInfoProto.ProcessMemory.NATIVE_HEAP);
+            dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
                     memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
                     memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
                     memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
                     memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
             proto.end(nhToken);
 
-            final long dvToken = proto.start(MemInfoProto.NativeProcess.DALVIK_HEAP);
-            dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Dalvik Heap",
+            final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP);
+            dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
                     memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
                     memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
                     memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
                     memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, dalvikMax);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, dalvikFree);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
             proto.end(dvToken);
 
             int otherPss = memInfo.otherPss;
@@ -2403,7 +2529,7 @@
                 if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                         || mySharedClean != 0 || myPrivateClean != 0
                         || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
-                    dumpHeap(proto, MemInfoProto.NativeProcess.OTHER_HEAPS,
+                    dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS,
                             Debug.MemoryInfo.getOtherLabel(i),
                             myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                             mySharedClean, myPrivateClean,
@@ -2420,21 +2546,21 @@
                 }
             }
 
-            dumpHeap(proto, MemInfoProto.NativeProcess.UNKNOWN_HEAP, "Unknown",
+            dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
                     otherPss, otherSwappablePss,
                     otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
                     memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss);
-            final long tToken = proto.start(MemInfoProto.NativeProcess.TOTAL_HEAP);
-            dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "TOTAL",
+            final long tToken = proto.start(MemInfoProto.ProcessMemory.TOTAL_HEAP);
+            dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
                     memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
                     memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
                     memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
                     memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
                     memInfo.getTotalSwappedOutPss());
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB,
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
                     nativeAllocated + dalvikAllocated);
-            proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree);
+            proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree);
             proto.end(tToken);
 
             if (dumpDalvik) {
@@ -2452,7 +2578,7 @@
                     if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                             || mySharedClean != 0 || myPrivateClean != 0
                             || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
-                        dumpHeap(proto, MemInfoProto.NativeProcess.DALVIK_DETAILS,
+                        dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS,
                                 Debug.MemoryInfo.getOtherLabel(i),
                                 myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                                 mySharedClean, myPrivateClean,
@@ -2462,24 +2588,24 @@
             }
         }
 
-        final long asToken = proto.start(MemInfoProto.NativeProcess.APP_SUMMARY);
-        proto.write(MemInfoProto.NativeProcess.AppSummary.JAVA_HEAP_PSS_KB,
+        final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY);
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
                 memInfo.getSummaryJavaHeap());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.NATIVE_HEAP_PSS_KB,
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
                 memInfo.getSummaryNativeHeap());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.GRAPHICS_PSS_KB,
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode());
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack());
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
                 memInfo.getSummaryGraphics());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.PRIVATE_OTHER_PSS_KB,
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
                 memInfo.getSummaryPrivateOther());
-        proto.write(MemInfoProto.NativeProcess.AppSummary.SYSTEM_PSS_KB,
+        proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
                 memInfo.getSummarySystem());
         if (memInfo.hasSwappedOutPss) {
-            proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS,
+            proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
                     memInfo.getSummaryTotalSwapPss());
         } else {
-            proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS,
+            proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
                     memInfo.getSummaryTotalSwap());
         }
         proto.end(asToken);
@@ -6289,7 +6415,7 @@
         System.exit(0);
     }
 
-    private void attach(boolean system) {
+    private void attach(boolean system, long startSeq) {
         sCurrentActivityThread = this;
         mSystemThread = system;
         if (!system) {
@@ -6304,7 +6430,7 @@
             RuntimeInit.setApplicationObject(mAppThread.asBinder());
             final IActivityManager mgr = ActivityManager.getService();
             try {
-                mgr.attachApplication(mAppThread);
+                mgr.attachApplication(mAppThread, startSeq);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -6383,7 +6509,7 @@
             ThreadedRenderer.enableForegroundTrimming();
         }
         ActivityThread thread = new ActivityThread();
-        thread.attach(true);
+        thread.attach(true, 0);
         return thread;
     }
 
@@ -6455,8 +6581,19 @@
 
         Looper.prepareMainLooper();
 
+        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
+        // It will be in the format "seq=114"
+        long startSeq = 0;
+        if (args != null) {
+            for (int i = args.length - 1; i >= 0; --i) {
+                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
+                    startSeq = Long.parseLong(
+                            args[i].substring(PROC_START_SEQ_IDENT.length()));
+                }
+            }
+        }
         ActivityThread thread = new ActivityThread();
-        thread.attach(false);
+        thread.attach(false, startSeq);
 
         if (sMainThreadHandler == null) {
             sMainThreadHandler = thread.getHandler();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 50e3f0a..ea22d33 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -260,10 +260,8 @@
     public static final int OP_REQUEST_DELETE_PACKAGES = 72;
     /** @hide Bind an accessibility service. */
     public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73;
-    /** @hide Interact with the system UI via an Accessibility Service */
-    public static final int OP_PERFORM_ACCESSIBILITY_ACTION = 74;
     /** @hide */
-    public static final int _NUM_OP = 75;
+    public static final int _NUM_OP = 74;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -508,7 +506,6 @@
             OP_CHANGE_WIFI_STATE,
             OP_REQUEST_DELETE_PACKAGES,
             OP_BIND_ACCESSIBILITY_SERVICE,
-            OP_PERFORM_ACCESSIBILITY_ACTION,
     };
 
     /**
@@ -590,7 +587,6 @@
             null, // OP_CHANGE_WIFI_STATE
             null, // OP_REQUEST_DELETE_PACKAGES
             null, // OP_BIND_ACCESSIBILITY_SERVICE
-            null, // OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
@@ -672,7 +668,6 @@
             "CHANGE_WIFI_STATE",
             "REQUEST_DELETE_PACKAGES",
             "BIND_ACCESSIBILITY_SERVICE",
-            "OP_PERFORM_ACCESSIBILITY_ACTION",
     };
 
     /**
@@ -754,7 +749,6 @@
             Manifest.permission.CHANGE_WIFI_STATE,
             Manifest.permission.REQUEST_DELETE_PACKAGES,
             Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
-            null, // no permission for OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
@@ -837,7 +831,6 @@
             null, // OP_CHANGE_WIFI_STATE
             null, // REQUEST_DELETE_PACKAGES
             null, // OP_BIND_ACCESSIBILITY_SERVICE
-            null, // OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
@@ -919,7 +912,6 @@
             false, // OP_CHANGE_WIFI_STATE
             false, // OP_REQUEST_DELETE_PACKAGES
             false, // OP_BIND_ACCESSIBILITY_SERVICE
-            false, // OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
@@ -1000,7 +992,6 @@
             AppOpsManager.MODE_ALLOWED,  // OP_CHANGE_WIFI_STATE
             AppOpsManager.MODE_ALLOWED,  // REQUEST_DELETE_PACKAGES
             AppOpsManager.MODE_ALLOWED,  // OP_BIND_ACCESSIBILITY_SERVICE
-            AppOpsManager.MODE_ALLOWED,  // OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
@@ -1085,7 +1076,6 @@
             false, // OP_CHANGE_WIFI_STATE
             false, // OP_REQUEST_DELETE_PACKAGES
             false, // OP_BIND_ACCESSIBILITY_SERVICE
-            false, // OP_PERFORM_ACCESSIBILITY_ACTION
     };
 
     /**
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index a0fb6ee..3a355d9 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -137,7 +137,9 @@
  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
  *      embed}
  *
- * @deprecated Use {@link android.support.v4.app.DialogFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class DialogFragment extends Fragment
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a92684b..4ff07f2 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -257,7 +257,9 @@
  * pressing back will pop it to return the user to whatever previous state
  * the activity UI was in.
  *
- * @deprecated Use {@link android.support.v4.app.Fragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.Fragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index a1dd32f..536c866 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -25,7 +25,8 @@
 /**
  * Callbacks to a {@link Fragment}'s container.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentContainer}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentContainer}.
  */
 @Deprecated
 public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cbb58d4..40bc248 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -38,7 +38,8 @@
  * It is the responsibility of the host to take care of the Fragment's lifecycle.
  * The methods provided by {@link FragmentController} are for that purpose.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentController}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentController}
  */
 @Deprecated
 public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 1edc68e..b48817b 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -38,7 +38,8 @@
  * host fragments, implement {@link FragmentHostCallback}, overriding the methods
  * applicable to the host.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentHostCallback}
  */
 @Deprecated
 public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 12e60b8..708450f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -75,7 +75,9 @@
  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
  * Fragments For All</a> for more details.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public abstract class FragmentManager {
@@ -90,7 +92,8 @@
      * the identifier as returned by {@link #getId} is the only thing that
      * will be persisted across activity instances.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
      */
     @Deprecated
     public interface BackStackEntry {
@@ -136,7 +139,9 @@
     /**
      * Interface to watch for changes to the back stack.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a>
+     *      {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
      */
     @Deprecated
     public interface OnBackStackChangedListener {
@@ -438,7 +443,9 @@
      * Callback interface for listening to fragment state changes that happen
      * within a given FragmentManager.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a>
+     *      {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
      */
     @Deprecated
     public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index beb1a15..326438a 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -28,7 +28,8 @@
  * {@link FragmentController#retainNonConfig()} and
  * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
  *
- * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentManagerNonConfig}
  */
 @Deprecated
 public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1103649..713a559 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -22,7 +22,8 @@
  * guide.</p>
  * </div>
  *
- * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentTransaction}
  */
 @Deprecated
 public abstract class FragmentTransaction {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 85bf6aa..1278f75 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -86,6 +86,9 @@
     // the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h
     // =============== Beginning of transactions used on native side as well ======================
     ParcelFileDescriptor openContentUri(in String uriString);
+    void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
+            String callingPackage);
+    void unregisterUidObserver(in IUidObserver observer);
     // =============== End of transactions used on native side as well ============================
 
     // Special low-level communication with activity manager.
@@ -108,7 +111,7 @@
     void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId);
     oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
             boolean abortBroadcast, int flags);
-    void attachApplication(in IApplicationThread app);
+    void attachApplication(in IApplicationThread app, long startSeq);
     oneway void activityIdle(in IBinder token, in Configuration config,
             in boolean stopProfiling);
     void activityPaused(in IBinder token);
@@ -478,9 +481,6 @@
      */
     void keyguardGoingAway(int flags);
     int getUidProcessState(int uid, in String callingPackage);
-    void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
-            String callingPackage);
-    void unregisterUidObserver(in IUidObserver observer);
     boolean isAssistDataAllowedOnCurrentActivity();
     boolean showAssistFromActivity(in IBinder token, in Bundle args);
     boolean isRootVoiceInteraction(in IBinder token);
@@ -626,9 +626,6 @@
     /** Cancels the window transitions for the given task. */
     void cancelTaskWindowTransition(int taskId);
 
-    /** Cancels the thumbnail transitions for the given task. */
-    void cancelTaskThumbnailTransition(int taskId);
-
     /**
      * @param taskId the id of the task to retrieve the sAutoapshots for
      * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index b25d778..893a41c 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -110,6 +110,9 @@
     void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
             in String[] args);
+    void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem,
+            boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
+            in String[] args);
     void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args);
     void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken,
             in String[] args);
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 01a9cbf..ce88809 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,15 +18,14 @@
 
 /** {@hide} */
 oneway interface IUidObserver {
-    /**
-     * General report of a state change of an uid.
-     *
-     * @param uid The uid for which the state change is being reported.
-     * @param procState The updated process state for the uid.
-     * @param procStateSeq The sequence no. associated with process state change of the uid,
-     *                     see UidRecord.procStateSeq for details.
-     */
-    void onUidStateChanged(int uid, int procState, long procStateSeq);
+    // WARNING: when these transactions are updated, check if they are any callers on the native
+    // side. If so, make sure they are using the correct transaction ids and arguments.
+    // If a transaction which will also be used on the native side is being inserted, add it to
+    // below block of transactions.
+
+    // Since these transactions are also called from native code, these must be kept in sync with
+    // the ones in frameworks/native/include/binder/IActivityManager.h
+    // =============== Beginning of transactions used on native side as well ======================
 
     /**
      * Report that there are no longer any processes running for a uid.
@@ -44,6 +43,18 @@
      */
     void onUidIdle(int uid, boolean disabled);
 
+    // =============== End of transactions used on native side as well ============================
+
+    /**
+     * General report of a state change of an uid.
+     *
+     * @param uid The uid for which the state change is being reported.
+     * @param procState The updated process state for the uid.
+     * @param procStateSeq The sequence no. associated with process state change of the uid,
+     *                     see UidRecord.procStateSeq for details.
+     */
+    void onUidStateChanged(int uid, int procState, long procStateSeq);
+
     /**
      * Report when the cached state of a uid has changed.
      * If true, a uid has become cached -- that is, it has some active processes that are
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 49d58eb..80782e3 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -155,4 +155,9 @@
      * Unregister a callback that was receiving color updates
      */
     void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
+
+    /**
+     * Called from SystemUI when it shows the AoD UI.
+     */
+    void setInAmbientMode(boolean inAmbienMode);
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 41324d0..b469de5 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1209,6 +1209,11 @@
     }
 
     private AppComponentFactory getFactory(String pkg) {
+        if (mThread == null) {
+            Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
+                    + " disabling AppComponentFactory", new Throwable());
+            return AppComponentFactory.DEFAULT;
+        }
         LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true);
         // This is in the case of starting up "android".
         if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk;
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 90b77b3..7790f70 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -145,7 +145,9 @@
  * @see #setListAdapter
  * @see android.widget.ListView
  *
- * @deprecated Use {@link android.support.v4.app.ListFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class ListFragment extends Fragment {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7969684..86d0fd62 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -55,14 +55,16 @@
  * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
  * </div>
  *
- * @deprecated Use {@link android.support.v4.app.LoaderManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.LoaderManager}
  */
 @Deprecated
 public abstract class LoaderManager {
     /**
      * Callback interface for a client to interact with the manager.
      *
-     * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
      */
     @Deprecated
     public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 705f9a0..85c3be8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1090,6 +1090,12 @@
     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
 
     /**
+     * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
+     * represents a group conversation.
+     */
+    public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+
+    /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}}.
      */
@@ -5960,9 +5966,10 @@
         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
 
         CharSequence mUserDisplayName;
-        CharSequence mConversationTitle;
+        @Nullable CharSequence mConversationTitle;
         List<Message> mMessages = new ArrayList<>();
         List<Message> mHistoricMessages = new ArrayList<>();
+        boolean mIsGroupConversation;
 
         MessagingStyle() {
         }
@@ -5985,20 +5992,20 @@
         }
 
         /**
-         * Sets the title to be displayed on this conversation. This should only be used for
-         * group messaging and left unset for one-on-one conversations.
-         * @param conversationTitle
+         * Sets the title to be displayed on this conversation. May be set to {@code null}.
+         *
+         * @param conversationTitle A name for the conversation, or {@code null}
          * @return this object for method chaining.
          */
-        public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+        public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
             mConversationTitle = conversationTitle;
             return this;
         }
 
         /**
-         * Return the title to be displayed on this conversation. Can be <code>null</code> and
-         * should be for one-on-one conversations
+         * Return the title to be displayed on this conversation. May return {@code null}.
          */
+        @Nullable
         public CharSequence getConversationTitle() {
             return mConversationTitle;
         }
@@ -6075,6 +6082,24 @@
         }
 
         /**
+         * Sets whether this conversation notification represents a group.
+         * @param isGroupConversation {@code true} if the conversation represents a group,
+         * {@code false} otherwise.
+         * @return this object for method chaining
+         */
+        public MessagingStyle setGroupConversation(boolean isGroupConversation) {
+            mIsGroupConversation = isGroupConversation;
+            return this;
+        }
+
+        /**
+         * Returns {@code true} if this notification represents a group conversation.
+         */
+        public boolean isGroupConversation() {
+            return mIsGroupConversation;
+        }
+
+        /**
          * @hide
          */
         @Override
@@ -6094,6 +6119,7 @@
             }
 
             fixTitleAndTextExtras(extras);
+            extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
         }
 
         private void fixTitleAndTextExtras(Bundle extras) {
@@ -6136,6 +6162,7 @@
             mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
+            mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
         }
 
         /**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 495fd3c..66cf991 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -552,8 +552,16 @@
         registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class,
                 new CachedServiceFetcher<WallpaperManager>() {
             @Override
-            public WallpaperManager createService(ContextImpl ctx) {
-                return new WallpaperManager(ctx.getOuterContext(),
+            public WallpaperManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                final IBinder b;
+                if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                    b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE);
+                } else {
+                    b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
+                }
+                IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
+                return new WallpaperManager(service, ctx.getOuterContext(),
                         ctx.mMainThread.getHandler());
             }});
 
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 9d40381f..35a1789 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -16,18 +16,15 @@
 
 package android.app;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -39,6 +36,9 @@
 import android.util.Printer;
 import android.util.Xml;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 
 /**
@@ -76,6 +76,7 @@
     final int mContextUriResource;
     final int mContextDescriptionResource;
     final boolean mShowMetadataInPreview;
+    final boolean mSupportsAmbientMode;
 
     /**
      * Constructor.
@@ -89,15 +90,7 @@
         mService = service;
         ServiceInfo si = service.serviceInfo;
         
-        PackageManager pm = context.getPackageManager();
-        String settingsActivityComponent = null;
-        int thumbnailRes = -1;
-        int authorRes = -1;
-        int descriptionRes = -1;
-        int contextUriRes = -1;
-        int contextDescriptionRes = -1;
-        boolean showMetadataInPreview = false;
-
+        final PackageManager pm = context.getPackageManager();
         XmlResourceParser parser = null;
         try {
             parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA);
@@ -123,27 +116,30 @@
             
             TypedArray sa = res.obtainAttributes(attrs,
                     com.android.internal.R.styleable.Wallpaper);
-            settingsActivityComponent = sa.getString(
+            mSettingsActivityName = sa.getString(
                     com.android.internal.R.styleable.Wallpaper_settingsActivity);
-            
-            thumbnailRes = sa.getResourceId(
+
+            mThumbnailResource = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_thumbnail,
                     -1);
-            authorRes = sa.getResourceId(
+            mAuthorResource = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_author,
                     -1);
-            descriptionRes = sa.getResourceId(
+            mDescriptionResource = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_description,
                     -1);
-            contextUriRes = sa.getResourceId(
+            mContextUriResource = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_contextUri,
                     -1);
-            contextDescriptionRes = sa.getResourceId(
+            mContextDescriptionResource = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_contextDescription,
                     -1);
-            showMetadataInPreview = sa.getBoolean(
+            mShowMetadataInPreview = sa.getBoolean(
                     com.android.internal.R.styleable.Wallpaper_showMetadataInPreview,
                     false);
+            mSupportsAmbientMode = sa.getBoolean(
+                    com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
+                    false);
 
             sa.recycle();
         } catch (NameNotFoundException e) {
@@ -152,14 +148,6 @@
         } finally {
             if (parser != null) parser.close();
         }
-        
-        mSettingsActivityName = settingsActivityComponent;
-        mThumbnailResource = thumbnailRes;
-        mAuthorResource = authorRes;
-        mDescriptionResource = descriptionRes;
-        mContextUriResource = contextUriRes;
-        mContextDescriptionResource = contextDescriptionRes;
-        mShowMetadataInPreview = showMetadataInPreview;
     }
 
     WallpaperInfo(Parcel source) {
@@ -170,6 +158,7 @@
         mContextUriResource = source.readInt();
         mContextDescriptionResource = source.readInt();
         mShowMetadataInPreview = source.readInt() != 0;
+        mSupportsAmbientMode = source.readInt() != 0;
         mService = ResolveInfo.CREATOR.createFromParcel(source);
     }
     
@@ -326,6 +315,16 @@
     }
 
     /**
+     * Returns whether a wallpaper was optimized or not for ambient mode.
+     *
+     * @return {@code true} if wallpaper can draw in ambient mode.
+     * @hide
+     */
+    public boolean getSupportsAmbientMode() {
+        return mSupportsAmbientMode;
+    }
+
+    /**
      * Return the class name of an activity that provides a settings UI for
      * the wallpaper.  You can launch this activity be starting it with
      * an {@link android.content.Intent} whose action is MAIN and with an
@@ -366,6 +365,7 @@
         dest.writeInt(mContextUriResource);
         dest.writeInt(mContextDescriptionResource);
         dest.writeInt(mShowMetadataInPreview ? 1 : 0);
+        dest.writeInt(mSupportsAmbientMode ? 1 : 0);
         mService.writeToParcel(dest, flags);
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3829afb..f21746c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -286,9 +286,8 @@
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
 
-        Globals(Looper looper) {
-            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
-            mService = IWallpaperManager.Stub.asInterface(b);
+        Globals(IWallpaperManager service, Looper looper) {
+            mService = service;
             mMainLooperHandler = new Handler(looper);
             forgetLoadedWallpaper();
         }
@@ -497,17 +496,17 @@
     private static final Object sSync = new Object[0];
     private static Globals sGlobals;
 
-    static void initGlobals(Looper looper) {
+    static void initGlobals(IWallpaperManager service, Looper looper) {
         synchronized (sSync) {
             if (sGlobals == null) {
-                sGlobals = new Globals(looper);
+                sGlobals = new Globals(service, looper);
             }
         }
     }
 
-    /*package*/ WallpaperManager(Context context, Handler handler) {
+    /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) {
         mContext = context;
-        initGlobals(context.getMainLooper());
+        initGlobals(service, context.getMainLooper());
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 89df421..7e80ac7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2635,10 +2635,115 @@
     }
 
     /**
+     * The maximum number of characters allowed in the password blacklist.
+     */
+    private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;
+
+    /**
+     * Throws an exception if the password blacklist is too large.
+     *
+     * @hide
+     */
+    public static void enforcePasswordBlacklistSize(List<String> blacklist) {
+        if (blacklist == null) {
+            return;
+        }
+        long characterCount = 0;
+        for (final String item : blacklist) {
+            characterCount += item.length();
+        }
+        if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
+            throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
+                      + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
+        }
+    }
+
+    /**
+     * Called by an application that is administering the device to blacklist passwords.
+     * <p>
+     * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
+     * Note that the match against the blacklist is case insensitive. The blacklist applies for all
+     * password qualities requested by {@link #setPasswordQuality} however it is not taken into
+     * consideration by {@link #isActivePasswordSufficient}.
+     * <p>
+     * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
+     * given a name that is used to track which blacklist is currently set by calling {@link
+     * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
+     * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
+     * the blacklist is being cleared.
+     * <p>
+     * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
+     * number of entries.
+     * <p>
+     * This method can be called on the {@link DevicePolicyManager} instance returned by
+     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+     * profile.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @param name name to associate with the blacklist
+     * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
+     * @return whether the new blacklist was successfully installed
+     * @throws SecurityException if {@code admin} is not a device or profile owner
+     * @throws IllegalArgumentException if the blacklist surpasses the character limit
+     * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
+     *
+     * @see #getPasswordBlacklistName
+     * @see #isActivePasswordSufficient
+     * @see #resetPasswordWithToken
+     */
+    public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
+            @Nullable List<String> blacklist) {
+        enforcePasswordBlacklistSize(blacklist);
+
+        try {
+            return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the name of the password blacklist set by the given admin.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @return the name of the blacklist or {@code null} if no blacklist is set
+     *
+     * @see #setPasswordBlacklist
+     */
+    public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
+        try {
+            return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Test if a given password is blacklisted.
+     *
+     * @param userId the user to valiate for
+     * @param password the password to check against the blacklist
+     * @return whether the password is blacklisted
+     *
+     * @see #setPasswordBlacklist
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
+        try {
+            return mService.isPasswordBlacklisted(userId, password);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Determine whether the current password the user has set is sufficient to meet the policy
      * requirements (e.g. quality, minimum length) that have been requested by the admins of this
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
-     * are not taken into account. The user must be unlocked in order to perform the check.
+     * are not taken into account. The user must be unlocked in order to perform the check. The
+     * password blacklist is not considered when checking sufficiency.
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -2676,6 +2781,7 @@
      * @see UserManager#DISALLOW_UNIFIED_PASSWORD
      */
     public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) {
+        throwIfParentInstance("isUsingUnifiedPassword");
         if (mService != null) {
             try {
                 return mService.isUsingUnifiedPassword(admin);
@@ -3101,23 +3207,6 @@
     }
 
     /**
-     * Returns maximum time to lock that applied by all profiles in this user. We do this because we
-     * do not have a separate timeout to lock for work challenge only.
-     *
-     * @hide
-     */
-    public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
-        if (mService != null) {
-            try {
-                return mService.getMaximumTimeToLockForUserAndProfiles(userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return 0;
-    }
-
-    /**
      * Called by a device/profile owner to set the timeout after which unlocking with secondary, non
      * strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong
      * authentication method like password, pin or pattern.
@@ -4062,6 +4151,52 @@
         return null;
     }
 
+
+    /**
+     * Called by a device or profile owner, or delegated certificate installer, to associate
+     * certificates with a key pair that was generated using {@link #generateKeyPair}, and
+     * set whether the key is available for the user to choose in the certificate selection
+     * prompt.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if calling from a delegated certificate installer.
+     * @param alias The private key alias under which to install the certificate. The {@code alias}
+     *        should denote an existing private key. If a certificate with that alias already
+     *        exists, it will be overwritten.
+     * @param certs The certificate chain to install. The chain should start with the leaf
+     *        certificate and include the chain of trust in order. This will be returned by
+     *        {@link android.security.KeyChain#getCertificateChain}.
+     * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+     *        certificate selection prompt, {@code false} to indicate that this key can only be
+     *        granted access by implementing
+     *        {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+     * @return {@code true} if the provided {@code alias} exists and the certificates has been
+     *        successfully associated with it, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner, or {@code admin} is null but the calling application is not a delegated
+     *         certificate installer.
+     */
+    public boolean setKeyPairCertificate(@Nullable ComponentName admin,
+            @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
+        throwIfParentInstance("setKeyPairCertificate");
+        try {
+            final byte[] pemCert = Credentials.convertToPem(certs.get(0));
+            byte[] pemChain = null;
+            if (certs.size() > 1) {
+                pemChain = Credentials.convertToPem(
+                        certs.subList(1, certs.size()).toArray(new Certificate[0]));
+            }
+            return mService.setKeyPairCertificate(admin, mContext.getPackageName(), alias, pemCert,
+                    pemChain, isUserSelectable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (CertificateException | IOException e) {
+            Log.w(TAG, "Could not pem-encode certificate", e);
+        }
+        return false;
+    }
+
+
     /**
      * @return the alias of a given CA certificate in the certificate store, or {@code null} if it
      * doesn't exist.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 05f6c2a..b692ffd9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import android.annotation.UserIdInt;
 import android.content.Intent;
 
 import java.util.List;
@@ -115,4 +116,11 @@
      * device owner to be affiliated with.
      */
     public abstract boolean isUserAffiliatedWithDevice(int userId);
+
+    /**
+     * Reports that a profile has changed to use a unified or separate credential.
+     *
+     * @param userId User ID of the profile.
+     */
+    public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId);
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9128208..7cf19ee 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -78,6 +78,10 @@
 
     long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);
 
+    boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
+    String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
+    boolean isPasswordBlacklisted(int userId, String password);
+
     boolean isActivePasswordSufficient(int userHandle, boolean parent);
     boolean isProfileActivePasswordSufficientForParent(int userHandle);
     boolean isUsingUnifiedPassword(in ComponentName admin);
@@ -91,7 +95,6 @@
 
     void setMaximumTimeToLock(in ComponentName who, long timeMs, boolean parent);
     long getMaximumTimeToLock(in ComponentName who, int userHandle, boolean parent);
-    long getMaximumTimeToLockForUserAndProfiles(int userHandle);
 
     void setRequiredStrongAuthTimeout(in ComponentName who, long timeMs, boolean parent);
     long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent);
@@ -171,6 +174,8 @@
     boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
             in ParcelableKeyGenParameterSpec keySpec,
             out KeymasterCertificateChain attestationChain);
+    boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
+            in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
 
     void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 0f93c59..d3b66d0 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -17,6 +17,7 @@
 package android.app.admin;
 
 import android.annotation.IntDef;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
@@ -26,6 +27,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
+import java.util.Objects;
 
 /**
  * Definitions for working with security logs.
@@ -135,9 +137,28 @@
      */
     public static final class SecurityEvent implements Parcelable {
         private Event mEvent;
+        private long mId;
+
+        /**
+         * Constructor used by native classes to generate SecurityEvent instances.
+         * @hide
+         */
+        /* package */ SecurityEvent(byte[] data) {
+            this(0, data);
+        }
+
+        /**
+         * Constructor used by Parcelable.Creator to generate SecurityEvent instances.
+         * @hide
+         */
+        /* package */ SecurityEvent(Parcel source) {
+            this(source.readLong(), source.createByteArray());
+        }
 
         /** @hide */
-        /*package*/ SecurityEvent(byte[] data) {
+        @TestApi
+        public SecurityEvent(long id, byte[] data) {
+            mId = id;
             mEvent = Event.fromBytes(data);
         }
 
@@ -162,6 +183,21 @@
             return mEvent.getData();
         }
 
+        /**
+         * @hide
+         */
+        public void setId(long id) {
+            this.mId = id;
+        }
+
+        /**
+         * Returns the id of the event, where the id monotonically increases for each event. The id
+         * is reset when the device reboots, and when security logging is enabled.
+         */
+        public long getId() {
+            return mId;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -169,6 +205,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(mId);
             dest.writeByteArray(mEvent.getBytes());
         }
 
@@ -176,7 +213,7 @@
                 new Parcelable.Creator<SecurityEvent>() {
             @Override
             public SecurityEvent createFromParcel(Parcel source) {
-                return new SecurityEvent(source.createByteArray());
+                return new SecurityEvent(source);
             }
 
             @Override
@@ -193,7 +230,7 @@
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             SecurityEvent other = (SecurityEvent) o;
-            return mEvent.equals(other.mEvent);
+            return mEvent.equals(other.mEvent) && mId == other.mId;
         }
 
         /**
@@ -201,7 +238,7 @@
          */
         @Override
         public int hashCode() {
-            return mEvent.hashCode();
+            return Objects.hash(mEvent, mId);
         }
     }
     /**
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 764ceed..3c96f06 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -54,6 +54,11 @@
     /** Target client activity. Might be null if the entire transaction is targeting an app. */
     private IBinder mActivityToken;
 
+    /** Get the target client of the transaction. */
+    public IApplicationThread getClient() {
+        return mClient;
+    }
+
     /**
      * Add a message to the end of the sequence of callbacks.
      * @param activityCallback A single message that can contain a lifecycle request/callback.
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
index 9812125..2fec30a 100644
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -16,8 +16,8 @@
 
 package android.app.servertransaction;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.Map;
 
 /**
@@ -27,7 +27,7 @@
 class ObjectPool {
 
     private static final Object sPoolSync = new Object();
-    private static final Map<Class, LinkedList<? extends ObjectPoolItem>> sPoolMap =
+    private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap =
             new HashMap<>();
 
     private static final int MAX_POOL_SIZE = 50;
@@ -40,9 +40,9 @@
     public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
         synchronized (sPoolSync) {
             @SuppressWarnings("unchecked")
-            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(itemClass);
+            final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass);
             if (itemPool != null && !itemPool.isEmpty()) {
-                return itemPool.poll();
+                return itemPool.remove(itemPool.size() - 1);
             }
             return null;
         }
@@ -56,16 +56,20 @@
     public static <T extends ObjectPoolItem> void recycle(T item) {
         synchronized (sPoolSync) {
             @SuppressWarnings("unchecked")
-            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(item.getClass());
+            ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass());
             if (itemPool == null) {
-                itemPool = new LinkedList<>();
+                itemPool = new ArrayList<>();
                 sPoolMap.put(item.getClass(), itemPool);
             }
-            if (itemPool.contains(item)) {
-                throw new IllegalStateException("Trying to recycle already recycled item");
+            // Check if the item is already in the pool
+            final int size = itemPool.size();
+            for (int i = 0; i < size; i++) {
+                if (itemPool.get(i) == item) {
+                    throw new IllegalStateException("Trying to recycle already recycled item");
+                }
             }
 
-            if (itemPool.size() < MAX_POOL_SIZE) {
+            if (size < MAX_POOL_SIZE) {
                 itemPool.add(item);
             }
         }
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index e61e8b2..5c7f674 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -63,7 +63,7 @@
             HINT_ACTIONS,
             HINT_SELECTED,
             HINT_NO_TINT,
-            HINT_HIDDEN,
+            HINT_SHORTCUT,
             HINT_TOGGLE,
             HINT_HORIZONTAL,
             HINT_PARTIAL,
@@ -118,12 +118,10 @@
      */
     public static final String HINT_NO_TINT     = "no_tint";
     /**
-     * Hint to indicate that this content should not be shown in larger renderings
-     * of Slices. This content may be used to populate the shortcut/icon
-     * format of the slice.
-     * @hide
+     * Hint to indicate that this content should only be displayed if the slice is presented
+     * as a shortcut.
      */
-    public static final String HINT_HIDDEN = "hidden";
+    public static final String HINT_SHORTCUT = "shortcut";
     /**
      * Hint indicating this content should be shown instead of the normal content when the slice
      * is in small format.
@@ -156,7 +154,6 @@
 
     /**
      * Key to retrieve an extra added to an intent when a control is changed.
-     * @hide
      */
     public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
     /**
@@ -176,6 +173,17 @@
      * Subtype to tag an item represents a slider.
      */
     public static final String SUBTYPE_SLIDER = "slider";
+    /**
+     * Subtype to indicate that this content has a toggle action associated with it. To indicate
+     * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the
+     * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE}
+     * which can be retrieved to see the new state of the toggle.
+     */
+    public static final String SUBTYPE_TOGGLE = "toggle";
+    /**
+     * Subtype to tag an item representing priority.
+     */
+    public static final String SUBTYPE_PRIORITY = "priority";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index f38e462..2fab305 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -85,7 +85,7 @@
      *
      * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
      * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
-     * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onInterruptData(BluetoothDevice, byte, byte[])
      */
     public static final byte REPORT_TYPE_INPUT = (byte) 1;
     public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -155,8 +155,8 @@
         }
 
         @Override
-        public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
-            mCallback.onIntrData(device, reportId, data);
+        public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            mCallback.onInterruptData(device, reportId, data);
         }
 
         @Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index bd19955..e71b00f 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -106,8 +106,8 @@
      * @param reportId Report Id.
      * @param data Report data.
      */
-    public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
-        Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+    public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+        Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
     }
 
     /**
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 6e9f09c..c44e356 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -50,7 +50,8 @@
  *
  * @param <D> the data type to be loaded.
  *
- * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.AsyncTaskLoader}
  */
 @Deprecated
 public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 137c169..74ba60d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -324,6 +324,15 @@
     public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
 
     /**
+     * @hide Flag for {@link #bindService}: allows binding to a service provided
+     * by an instant app. Note that the caller may not have access to the instant
+     * app providing the service which is a violation of the instant app sandbox.
+     * This flag is intended ONLY for development/testing and should be used with
+     * great care. Only the system is allowed to use this flag.
+     */
+    public static final int BIND_ALLOW_INSTANT = 0x00400000;
+
+    /**
      * @hide Flag for {@link #bindService}: like {@link #BIND_NOT_FOREGROUND}, but puts it
      * up in to the important background state (instead of transient).
      */
@@ -3091,6 +3100,14 @@
      * service objects between various different contexts (Activities, Applications,
      * Services, Providers, etc.)
      *
+     * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+     * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+     * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+     * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+     * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+     * Generally, if you are running as an instant app you should always check whether the result
+     * of this method is null.
+     *
      * @param name The name of the desired service.
      *
      * @return The service or null if the name does not exist.
@@ -3174,6 +3191,14 @@
      * Services, Providers, etc.)
      * </p>
      *
+     * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+     * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+     * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+     * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+     * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+     * Generally, if you are running as an instant app you should always check whether the result
+     * of this method is null.
+     *
      * @param serviceClass The class of the desired service.
      * @return The service or null if the class is not a supported system service.
      */
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7f24c51..5a08636 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -39,7 +39,8 @@
  * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
  * and {@link #setProjection(String[])}.
  *
- * @deprecated Use {@link android.support.v4.content.CursorLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.CursorLoader}
  */
 @Deprecated
 public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 80f9a14..b0555d4 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -49,7 +49,8 @@
  *
  * @param <D> The result returned when the load is complete
  *
- * @deprecated Use {@link android.support.v4.content.Loader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.Loader}
  */
 @Deprecated
 public class Loader<D> {
@@ -561,4 +562,4 @@
                     writer.print(" mReset="); writer.println(mReset);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ff02c40..2d72632 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2075,6 +2075,13 @@
     public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports cell-broadcast reception using the MBMS APIs.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports connecting to USB devices
      * as the USB host.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e2fd82d..77eb57f2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -87,7 +87,6 @@
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.apk.ApkSignatureVerifier;
-import android.util.apk.SignatureNotFoundException;
 import android.view.Gravity;
 
 import com.android.internal.R;
@@ -1559,43 +1558,46 @@
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
-        boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0;
         int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
-        if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) {
+        if (pkg.applicationInfo.isStaticSharedLibrary()) {
             // must use v2 signing scheme
             minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
         }
-        try {
-            ApkSignatureVerifier.Result verified =
-                    ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir);
-            if (pkg.mCertificates == null) {
-                pkg.mCertificates = verified.certs;
-                pkg.mSignatures = verified.sigs;
-                pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
-                for (int i = 0; i < verified.certs.length; i++) {
-                    Certificate[] signerCerts = verified.certs[i];
-                    Certificate signerCert = signerCerts[0];
-                    pkg.mSigningKeys.add(signerCert.getPublicKey());
-                }
-            } else {
-                if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
-                    throw new PackageParserException(
-                            INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                            apkPath + " has mismatched certificates");
-                }
-            }
-        } catch (SignatureNotFoundException e) {
+        ApkSignatureVerifier.Result verified;
+        if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) {
+            // systemDir APKs are already trusted, save time by not verifying
+            verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
+                        apkPath, minSignatureScheme);
+        } else {
+            verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
+        }
+        if (verified.signatureSchemeVersion
+                < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) {
+            // TODO (b/68860689): move this logic to packagemanagerserivce
             if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                        "No APK Signature Scheme v2 signature in ephemeral package " + apkPath,
-                        e);
+                    "No APK Signature Scheme v2 signature in ephemeral package " + apkPath);
             }
-            if (pkg.applicationInfo.isStaticSharedLibrary()) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                        "Static shared libs must use v2 signature scheme " + apkPath);
+        }
+
+        // Verify that entries are signed consistently with the first pkg
+        // we encountered. Note that for splits, certificates may have
+        // already been populated during an earlier parse of a base APK.
+        if (pkg.mCertificates == null) {
+            pkg.mCertificates = verified.certs;
+            pkg.mSignatures = verified.sigs;
+            pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
+            for (int i = 0; i < verified.certs.length; i++) {
+                Certificate[] signerCerts = verified.certs[i];
+                Certificate signerCert = signerCerts[0];
+                pkg.mSigningKeys.add(signerCert.getPublicKey());
             }
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "No APK Signature Scheme v2 signature in package " + apkPath, e);
+        } else {
+            if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
+                throw new PackageParserException(
+                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                        apkPath + " has mismatched certificates");
+            }
         }
     }
 
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 61b0eb0..30222b7 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -36,15 +36,26 @@
 import java.util.List;
 
 /**
- * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users with quick
- * access to activities other than an app's main activity in the currently-active launcher, provided
- * that the launcher supports app shortcuts.  For example, an email app may publish the "compose new
- * email" action, which will directly open the compose activity.  The {@link ShortcutInfo} class
- * contains information about each of the shortcuts themselves.
+ * The ShortcutManager performs operations on an app's set of <em>shortcuts</em>. The
+ * {@link ShortcutInfo} class contains information about each of the shortcuts themselves.
+ *
+ * <p>An app's shortcuts represent specific tasks and actions that users can perform within your
+ * app. When a user selects a shortcut in the currently-active launcher, your app opens an activity
+ * other than the app's starting activity, provided that the currently-active launcher supports app
+ * shortcuts.</p>
+ *
+ * <p>The types of shortcuts that you create for your app depend on the app's key use cases. For
+ * example, an email app may publish the "compose new email" shortcut, which allows the app to
+ * directly open the compose activity.</p>
+ *
+ * <p class="note"><b>Note:</b> Only main activities&mdash;activities that handle the
+ * {@link Intent#ACTION_MAIN} action and the {@link Intent#CATEGORY_LAUNCHER} category&mdash;can
+ * have shortcuts. If an app has multiple main activities, you need to define the set of shortcuts
+ * for <em>each</em> activity.
  *
  * <p>This page discusses the implementation details of the <code>ShortcutManager</code> class. For
- * guidance on performing operations on app shortcuts within your app, see the
- * <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide.
+ * definitions of key terms and guidance on performing operations on shortcuts within your app, see
+ * the <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide.
  *
  * <h3>Shortcut characteristics</h3>
  *
@@ -69,8 +80,8 @@
  * <ul>
  *     <li>The user removes it.
  *     <li>The publisher app associated with the shortcut is uninstalled.
- *     <li>The user performs the clear data action on the publisher app from the device's
- *     <b>Settings</b> app.
+ *     <li>The user selects <b>Clear data</b> from the publisher app's <i>Storage</i> screen, within
+ *     the system's <b>Settings</b> app.
  * </ul>
  *
  * <p>Because the system performs
@@ -83,12 +94,17 @@
  *
  * <p>When the launcher displays an app's shortcuts, they should appear in the following order:
  *
- * <ul>
- *   <li>Static shortcuts (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}),
- *   and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}).
- *   <li>Within each shortcut type (static and dynamic), sort the shortcuts in order of increasing
- *   rank according to {@link ShortcutInfo#getRank()}.
- * </ul>
+ * <ol>
+ *   <li><b>Static shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method
+ *   returns {@code true}.</li>
+ *   <li><b>Dynamic shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDynamic()} method returns
+ *   {@code true}.</li>
+ * </ol>
+ *
+ * <p>Within each shortcut type (static and dynamic), shortcuts are sorted in order of increasing
+ * rank according to {@link ShortcutInfo#getRank()}.</p>
+ *
+ * <h4>Shortcut ranks</h4>
  *
  * <p>Shortcut ranks are non-negative, sequential integers that determine the order in which
  * shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks
@@ -103,64 +119,99 @@
  *
  * <h3>Options for static shortcuts</h3>
  *
- * The following list includes descriptions for the different attributes within a static shortcut:
+ * The following list includes descriptions for the different attributes within a static shortcut.
+ * You must provide a value for {@code android:shortcutId} and {@code android:shortcutShortLabel};
+ * all other values are optional.
+ *
  * <dl>
  *   <dt>{@code android:shortcutId}</dt>
- *   <dd>Mandatory shortcut ID.
- *   <p>
- *   This must be a string literal.
- *   A resource string, such as <code>@string/foo</code>, cannot be used.
+ *   <dd><p>A string literal, which represents the shortcut when a {@code ShortcutManager} object
+ *   performs operations on it.</p>
+ *   <p class="note"><b>Note: </b>You cannot set this attribute's value to a resource string, such
+ *   as <code>@string/foo</code>.</p>
  *   </dd>
  *
  *   <dt>{@code android:enabled}</dt>
- *   <dd>Default is {@code true}.  Can be set to {@code false} in order
- *   to disable a static shortcut that was published in a previous version and set a custom
- *   disabled message.  If a custom disabled message is not needed, then a static shortcut can
- *   be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd>
+ *   <dd><p>Whether the user can interact with the shortcut from a supported launcher.</p>
+ *   <p>The default value is {@code true}. If you set it to {@code false}, you should also set
+ *   {@code android:shortcutDisabledMessage} to a message that explains why you've disabled the
+ *   shortcut. If you don't think you need to provide such a message, it's easiest to just remove
+ *   the shortcut from the XML file entirely, rather than changing the values of the shortcut's
+ *   {@code android:enabled} and {@code android:shortcutDisabledMessage} attributes.
+ *   </dd>
  *
  *   <dt>{@code android:icon}</dt>
- *   <dd>Shortcut icon.</dd>
+ *   <dd><p>The <a href="/topic/performance/graphics/index.html">bitmap</a> or
+ *   <a href="/guide/practices/ui_guidelines/icon_design_adaptive.html">adaptive icon</a> that the
+ *   launcher uses when displaying the shortcut to the user. This value can be either the path to an
+ *   image or the resource file that contains the image. Use adaptive icons whenever possible to
+ *   improve performance and consistency.</p>
+ *   <p class="note"><b>Note: </b>Shortcut icons cannot include
+ *   <a href="/training/material/drawables.html#DrawableTint">tints</a>.
+ *   </dd>
  *
  *   <dt>{@code android:shortcutShortLabel}</dt>
- *   <dd>Mandatory shortcut short label.
- *   See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.
- *   <p>
- *   This must be a resource string, such as <code>@string/shortcut_label</code>.
+ *   <dd><p>A concise phrase that describes the shortcut's purpose. For more information, see
+ *   {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</p>
+ *   <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ *   <code>@string/shortcut_short_label</code>.</p>
  *   </dd>
  *
  *   <dt>{@code android:shortcutLongLabel}</dt>
- *   <dd>Shortcut long label.
- *   See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.
- *   <p>
- *   This must be a resource string, such as <code>@string/shortcut_long_label</code>.
+ *   <dd><p>An extended phrase that describes the shortcut's purpose. If there's enough space, the
+ *   launcher displays this value instead of {@code android:shortcutShortLabel}. For more
+ *   information, see {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</p>
+ *   <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ *   <code>@string/shortcut_long_label</code>.</p>
  *   </dd>
  *
  *   <dt>{@code android:shortcutDisabledMessage}</dt>
- *   <dd>When {@code android:enabled} is set to
- *   {@code false}, this attribute is used to display a custom disabled message.
- *   <p>
- *   This must be a resource string, such as <code>@string/shortcut_disabled_message</code>.
+ *   <dd><p>The message that appears in a supported launcher when the user attempts to launch a
+ *   disabled shortcut. The message should explain to the user why the shortcut is now disabled.
+ *   This attribute's value has no effect if {@code android:enabled} is {@code true}.</p>
+ *   <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ *   <code>@string/shortcut_disabled_message</code>.</p>
+ *   </dd>
+ * </dl>
+ *
+ * <h3>Inner elements that define static shortcuts</h3>
+ *
+ * <p>The XML file that lists an app's static shortcuts supports the following elements inside each
+ * {@code <shortcut>} element. You must include an {@code intent} inner element for each
+ * static shortcut that you define.</p>
+ *
+ * <dl>
+ *   <dt>{@code intent}</dt>
+ *   <dd><p>The action that the system launches when the user selects the shortcut. This intent must
+ *   provide a value for the {@code android:action} attribute.</p>
+ *   <p>You can provide multiple intents for a single shortcut. If you do so, the last defined
+ *   activity is launched, and the other activities are placed in the
+ *   <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See
+ *   <a href="/guide/topics/ui/shortcuts.html#static">Using Static Shortcuts</a> and the
+ *   {@link android.app.TaskStackBuilder} class reference for details.</p>
+ *   <p class="note"><b>Note:</b> This {@code intent} element cannot include string resources.</p>
+ *   <p>To learn more about how to configure intents, see
+ *   <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a>.</p>
  *   </dd>
  *
- *   <dt>{@code intent}</dt>
- *   <dd>Intent to launch when the user selects the shortcut.
- *   {@code android:action} is mandatory.
- *   See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the
- *   other supported tags.
- *   <p>You can provide multiple intents for a single shortcut so that the last defined activity is
- *   launched with the other activities in the
- *   <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See
- *   {@link android.app.TaskStackBuilder} for details.
- *   <p><b>Note:</b> String resources may not be used within an {@code <intent>} element.
- *   </dd>
  *   <dt>{@code categories}</dt>
- *   <dd>Specify shortcut categories.  Currently only
- *   {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework.
+ *   <dd><p>Provides a grouping for the types of actions that your app's shortcuts perform, such as
+ *   creating new chat messages.</p>
+ *   <p>For a list of supported shortcut categories, see the {@link ShortcutInfo} class reference
+ *   for a list of supported shortcut categories.
  *   </dd>
  * </dl>
  *
  * <h3>Updating shortcuts</h3>
  *
+ * <p>Each app's launcher icon can contain at most {@link #getMaxShortcutCountPerActivity()} number
+ * of static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts
+ * that an app can create, though.
+ *
+ * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
+ * the pinned shortcut is still visible and launchable.  This allows an app to have more than
+ * {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
+ *
  * <p>As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5:
  * <ol>
  *     <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent
@@ -168,18 +219,13 @@
  *
  *     <li>The user pins all 5 of the shortcuts.
  *
- *     <li>Later, the user has started 3 additional conversations (c6, c7, and c8),
- *     so the publisher app
- *     re-publishes its dynamic shortcuts.  The new dynamic shortcut list is:
- *     c4, c5, ..., c8.
- *     The publisher app has to remove c1, c2, and c3 because it can't have more than
- *     5 dynamic shortcuts.
- *
- *     <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned
- *     shortcuts for these conversations are still available and launchable.
- *
- *     <li>At this point, the user can access a total of 8 shortcuts that link to activities in
- *     the publisher app, including the 3 pinned shortcuts, even though an app can have at most 5
+ *     <li>Later, the user has started 3 additional conversations (c6, c7, and c8), so the publisher
+ *     app re-publishes its dynamic shortcuts. The new dynamic shortcut list is: c4, c5, ..., c8.
+ *     <p>The publisher app has to remove c1, c2, and c3 because it can't have more than 5 dynamic
+ *     shortcuts. However, c1, c2, and c3 are still pinned shortcuts that the user can access and
+ *     launch.
+ *     <p>At this point, the user can access a total of 8 shortcuts that link to activities in the
+ *     publisher app, including the 3 pinned shortcuts, even though an app can have at most 5
  *     dynamic shortcuts.
  *
  *     <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
@@ -196,44 +242,23 @@
  * Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags.
  * Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other
  * flags; otherwise, if the app is already running, the app is simply brought to
- * the foreground, and the target activity may not appear.
+ * the foreground, and the target activity might not appear.
  *
  * <p>Static shortcuts <b>cannot</b> have custom intent flags.
  * The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
  * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all
- * the existing activities in your app will be destroyed when a static shortcut is launched.
+ * the existing activities in your app are destroyed when a static shortcut is launched.
  * If this behavior is not desirable, you can use a <em>trampoline activity</em>, or an invisible
  * activity that starts another activity in {@link Activity#onCreate}, then calls
  * {@link Activity#finish()}:
  * <ol>
  *     <li>In the <code>AndroidManifest.xml</code> file, the trampoline activity should include the
  *     attribute assignment {@code android:taskAffinity=""}.
- *     <li>In the shortcuts resource file, the intent within the static shortcut should point at
+ *     <li>In the shortcuts resource file, the intent within the static shortcut should reference
  *     the trampoline activity.
  * </ol>
  *
- * <h3>Handling system locale changes</h3>
- *
- * <p>Apps should update dynamic and pinned shortcuts when the system locale changes using the
- * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes,
- * <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is reset, so even
- * background apps can add and update dynamic shortcuts until the rate limit is reached again.
- *
- * <h3>Shortcut limits</h3>
- *
- * <p>Only main activities&mdash;activities that handle the {@code MAIN} action and the
- * {@code LAUNCHER} category&mdash;can have shortcuts. If an app has multiple main activities, you
- * need to define the set of shortcuts for <em>each</em> activity.
- *
- * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
- * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that
- * an app can create.
- *
- * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
- * the pinned shortcut is still visible and launchable.  This allows an app to have more than
- * {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
- *
- * <h4>Rate limiting</h4>
+ * <h3>Rate limiting</h3>
  *
  * <p>When <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is active,
  * {@link #isRateLimitingActive()} returns {@code true}.
@@ -243,8 +268,20 @@
  * <ul>
  *   <li>An app comes to the foreground.
  *   <li>The system locale changes.
- *   <li>The user performs the <strong>inline reply</strong> action on a notification.
+ *   <li>The user performs the <a href="/guide/topics/ui/notifiers/notifications.html#direct">inline
+ *   reply</a> action on a notification.
  * </ul>
+ *
+ * <h3>Handling system locale changes</h3>
+ *
+ * <p>Apps should update dynamic and pinned shortcuts when they receive the
+ * {@link Intent#ACTION_LOCALE_CHANGED} broadcast, indicating that the system locale has changed.
+ * <p>When the system locale changes, <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate
+ * limiting</a> is reset, so even background apps can add and update dynamic shortcuts until the
+ * rate limit is reached again.
+ *
+ * <h3>Retrieving class instances</h3>
+ * <!-- Provides a heading for the content filled in by the @SystemService annotation below -->
  */
 @SystemService(Context.SHORTCUT_SERVICE)
 public class ShortcutManager {
diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
index c9f184a..414c138 100644
--- a/core/java/android/content/pm/crossprofile/CrossProfileApps.java
+++ b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
@@ -16,7 +16,6 @@
 package android.content.pm.crossprofile;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -61,15 +60,10 @@
      * @param user The UserHandle of the profile, must be one of the users returned by
      *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
      *        be thrown.
-     * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
-     *                     {@link android.content.Intent#setSourceBounds(Rect)}.
-     * @param startActivityOptions Options to pass to startActivity
      */
-    public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
-            @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+    public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user) {
         try {
-            mService.startActivityAsUser(mContext.getPackageName(),
-                    component, sourceBounds, startActivityOptions, user);
+            mService.startActivityAsUser(mContext.getPackageName(), component, user);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
index dd8d04f..227f91f5 100644
--- a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
@@ -26,6 +26,7 @@
  * @hide
  */
 interface ICrossProfileApps {
-    void startActivityAsUser(in String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle startActivityOptions, in UserHandle user);
+    void startActivityAsUser(in String callingPackage, in ComponentName component,
+        in UserHandle user);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
index e02e68d..5bf3a7c 100644
--- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
+++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
@@ -37,7 +37,7 @@
 
     private static final String TAG = "SQLiteCompatibilityWalFlags";
 
-    private static volatile boolean sInitialized = true; // Temporarily disable flags
+    private static volatile boolean sInitialized;
     private static volatile boolean sFlagsSet;
     private static volatile boolean sCompatibilityWalSupported;
     private static volatile String sWALSyncMode;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3a3048e..57ab18e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.utils.TypeReference;
 import android.util.Rational;
 
@@ -169,6 +170,7 @@
     private final CameraMetadataNative mProperties;
     private List<CameraCharacteristics.Key<?>> mKeys;
     private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
+    private List<CaptureRequest.Key<?>> mAvailableSessionKeys;
     private List<CaptureResult.Key<?>> mAvailableResultKeys;
 
     /**
@@ -251,6 +253,67 @@
     }
 
     /**
+     * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that the
+     * camera device can pass as part of the capture session initialization.</p>
+     *
+     * <p>This list includes keys that are difficult to apply per-frame and
+     * can result in unexpected delays when modified during the capture session
+     * lifetime. Typical examples include parameters that require a
+     * time-consuming hardware re-configuration or internal camera pipeline
+     * change. For performance reasons we suggest clients to pass their initial
+     * values as part of {@link SessionConfiguration#setSessionParameters}. Once
+     * the camera capture session is enabled it is also recommended to avoid
+     * changing them from their initial values set in
+     * {@link SessionConfiguration#setSessionParameters }.
+     * Control over session parameters can still be exerted in capture requests
+     * but clients should be aware and expect delays during their application.
+     * An example usage scenario could look like this:</p>
+     * <ul>
+     * <li>The camera client starts by quering the session parameter key list via
+     *   {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+     * <li>Before triggering the capture session create sequence, a capture request
+     *   must be built via {@link CameraDevice#createCaptureRequest } using an
+     *   appropriate template matching the particular use case.</li>
+     * <li>The client should go over the list of session parameters and check
+     *   whether some of the keys listed matches with the parameters that
+     *   they intend to modify as part of the first capture request.</li>
+     * <li>If there is no such match, the capture request can be  passed
+     *   unmodified to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>If matches do exist, the client should update the respective values
+     *   and pass the request to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>After the capture session initialization completes the session parameter
+     *   key list can continue to serve as reference when posting or updating
+     *   further requests. As mentioned above further changes to session
+     *   parameters should ideally be avoided, if updates are necessary
+     *   however clients could expect a delay/glitch during the
+     *   parameter switch.</li>
+     * </ul>
+     *
+     * <p>The list returned is not modifiable, so any attempts to modify it will throw
+     * a {@code UnsupportedOperationException}.</p>
+     *
+     * <p>Each key is only listed once in the list. The order of the keys is undefined.</p>
+     *
+     * @return List of keys that can be passed during capture session initialization. In case the
+     * camera device doesn't support such keys the list can be null.
+     */
+    @SuppressWarnings({"unchecked"})
+    public List<CaptureRequest.Key<?>> getAvailableSessionKeys() {
+        if (mAvailableSessionKeys == null) {
+            Object crKey = CaptureRequest.Key.class;
+            Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey;
+
+            int[] filterTags = get(REQUEST_AVAILABLE_SESSION_KEYS);
+            if (filterTags == null) {
+                return null;
+            }
+            mAvailableSessionKeys =
+                    getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags);
+        }
+        return mAvailableSessionKeys;
+    }
+
+    /**
      * Returns the list of keys supported by this {@link CameraDevice} for querying
      * with a {@link CaptureRequest}.
      *
@@ -1571,6 +1634,48 @@
             new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class);
 
     /**
+     * <p>A subset of the available request keys that the camera device
+     * can pass as part of the capture session initialization.</p>
+     * <p>This is a subset of android.request.availableRequestKeys which
+     * contains a list of keys that are difficult to apply per-frame and
+     * can result in unexpected delays when modified during the capture session
+     * lifetime. Typical examples include parameters that require a
+     * time-consuming hardware re-configuration or internal camera pipeline
+     * change. For performance reasons we advise clients to pass their initial
+     * values as part of {@link SessionConfiguration#setSessionParameters }. Once
+     * the camera capture session is enabled it is also recommended to avoid
+     * changing them from their initial values set in
+     * {@link SessionConfiguration#setSessionParameters }.
+     * Control over session parameters can still be exerted in capture requests
+     * but clients should be aware and expect delays during their application.
+     * An example usage scenario could look like this:</p>
+     * <ul>
+     * <li>The camera client starts by quering the session parameter key list via
+     *   {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+     * <li>Before triggering the capture session create sequence, a capture request
+     *   must be built via {@link CameraDevice#createCaptureRequest } using an
+     *   appropriate template matching the particular use case.</li>
+     * <li>The client should go over the list of session parameters and check
+     *   whether some of the keys listed matches with the parameters that
+     *   they intend to modify as part of the first capture request.</li>
+     * <li>If there is no such match, the capture request can be  passed
+     *   unmodified to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>If matches do exist, the client should update the respective values
+     *   and pass the request to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>After the capture session initialization completes the session parameter
+     *   key list can continue to serve as reference when posting or updating
+     *   further requests. As mentioned above further changes to session
+     *   parameters should ideally be avoided, if updates are necessary
+     *   however clients could expect a delay/glitch during the
+     *   parameter switch.</li>
+     * </ul>
+     * <p>This key is available on all devices.</p>
+     * @hide
+     */
+    public static final Key<int[]> REQUEST_AVAILABLE_SESSION_KEYS =
+            new Key<int[]>("android.request.availableSessionKeys", int[].class);
+
+    /**
      * <p>The list of image formats that are supported by this
      * camera device for output streams.</p>
      * <p>All camera devices will support JPEG and YUV_420_888 formats.</p>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 55343a2..87e503d 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Handler;
 import android.view.Surface;
 
@@ -811,6 +812,26 @@
             throws CameraAccessException;
 
     /**
+     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+     * object that aggregates all supported parameters.</p>
+     *
+     * @param config A session configuration (see {@link SessionConfiguration}).
+     *
+     * @throws IllegalArgumentException In case the session configuration is invalid; or the output
+     *                                  configurations are empty.
+     * @throws CameraAccessException In case the camera device is no longer connected or has
+     *                               encountered a fatal error.
+     * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+     * @see #createCaptureSessionByOutputConfigurations
+     * @see #createReprocessableCaptureSession
+     * @see #createConstrainedHighSpeedCaptureSession
+     */
+    public void createCaptureSession(
+            SessionConfiguration config) throws CameraAccessException {
+        throw new UnsupportedOperationException("No default implementation");
+    }
+
+    /**
      * <p>Create a {@link CaptureRequest.Builder} for new capture requests,
      * initialized with template for a target use case. The settings are chosen
      * to be the best options for the specific camera device, so it is not
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 0262ecb..77da2a5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,15 +21,18 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.TypeReference;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.Surface;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 
@@ -198,7 +201,24 @@
         }
     }
 
-    private final HashSet<Surface> mSurfaceSet;
+    private final String            TAG = "CaptureRequest-JV";
+
+    private final ArraySet<Surface> mSurfaceSet = new ArraySet<Surface>();
+
+    // Speed up sending CaptureRequest across IPC:
+    // mSurfaceConverted should only be set to true during capture request
+    // submission by {@link #convertSurfaceToStreamId}. The method will convert
+    // surfaces to stream/surface indexes based on passed in stream configuration at that time.
+    // This will save significant unparcel time for remote camera device.
+    // Once the request is submitted, camera device will call {@link #recoverStreamIdToSurface}
+    // to reset the capture request back to its original state.
+    private final Object           mSurfacesLock = new Object();
+    private boolean                mSurfaceConverted = false;
+    private int[]                  mStreamIdxArray;
+    private int[]                  mSurfaceIdxArray;
+
+    private static final ArraySet<Surface> mEmptySurfaceSet = new ArraySet<Surface>();
+
     private final CameraMetadataNative mSettings;
     private boolean mIsReprocess;
     // If this request is part of constrained high speed request list that was created by
@@ -218,7 +238,6 @@
     private CaptureRequest() {
         mSettings = new CameraMetadataNative();
         setNativeInstance(mSettings);
-        mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = false;
         mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
@@ -232,7 +251,7 @@
     private CaptureRequest(CaptureRequest source) {
         mSettings = new CameraMetadataNative(source.mSettings);
         setNativeInstance(mSettings);
-        mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
+        mSurfaceSet.addAll(source.mSurfaceSet);
         mIsReprocess = source.mIsReprocess;
         mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
         mReprocessableSessionId = source.mReprocessableSessionId;
@@ -263,7 +282,6 @@
             int reprocessableSessionId) {
         mSettings = CameraMetadataNative.move(settings);
         setNativeInstance(mSettings);
-        mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = isReprocess;
         if (isReprocess) {
             if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) {
@@ -463,22 +481,25 @@
     private void readFromParcel(Parcel in) {
         mSettings.readFromParcel(in);
         setNativeInstance(mSettings);
-
-        mSurfaceSet.clear();
-
-        Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
-
-        if (parcelableArray == null) {
-            return;
-        }
-
-        for (Parcelable p : parcelableArray) {
-            Surface s = (Surface) p;
-            mSurfaceSet.add(s);
-        }
-
         mIsReprocess = (in.readInt() == 0) ? false : true;
         mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
+
+        synchronized (mSurfacesLock) {
+            mSurfaceSet.clear();
+            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+            if (parcelableArray != null) {
+                for (Parcelable p : parcelableArray) {
+                    Surface s = (Surface) p;
+                    mSurfaceSet.add(s);
+                }
+            }
+            // Intentionally disallow java side readFromParcel to receive streamIdx/surfaceIdx
+            // Since there is no good way to convert indexes back to Surface
+            int streamSurfaceSize = in.readInt();
+            if (streamSurfaceSize != 0) {
+                throw new RuntimeException("Reading cached CaptureRequest is not supported");
+            }
+        }
     }
 
     @Override
@@ -489,8 +510,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         mSettings.writeToParcel(dest, flags);
-        dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
         dest.writeInt(mIsReprocess ? 1 : 0);
+
+        synchronized (mSurfacesLock) {
+            final ArraySet<Surface> surfaces = mSurfaceConverted ? mEmptySurfaceSet : mSurfaceSet;
+            dest.writeParcelableArray(surfaces.toArray(new Surface[surfaces.size()]), flags);
+            if (mSurfaceConverted) {
+                dest.writeInt(mStreamIdxArray.length);
+                for (int i = 0; i < mStreamIdxArray.length; i++) {
+                    dest.writeInt(mStreamIdxArray[i]);
+                    dest.writeInt(mSurfaceIdxArray[i]);
+                }
+            } else {
+                dest.writeInt(0);
+            }
+        }
     }
 
     /**
@@ -508,6 +542,67 @@
     }
 
     /**
+     * @hide
+     */
+    public void convertSurfaceToStreamId(
+            final SparseArray<OutputConfiguration> configuredOutputs) {
+        synchronized (mSurfacesLock) {
+            if (mSurfaceConverted) {
+                Log.v(TAG, "Cannot convert already converted surfaces!");
+                return;
+            }
+
+            mStreamIdxArray = new int[mSurfaceSet.size()];
+            mSurfaceIdxArray = new int[mSurfaceSet.size()];
+            int i = 0;
+            for (Surface s : mSurfaceSet) {
+                boolean streamFound = false;
+                for (int j = 0; j < configuredOutputs.size(); ++j) {
+                    int streamId = configuredOutputs.keyAt(j);
+                    OutputConfiguration outConfig = configuredOutputs.valueAt(j);
+                    int surfaceId = 0;
+                    for (Surface outSurface : outConfig.getSurfaces()) {
+                        if (s == outSurface) {
+                            streamFound = true;
+                            mStreamIdxArray[i] = streamId;
+                            mSurfaceIdxArray[i] = surfaceId;
+                            i++;
+                            break;
+                        }
+                        surfaceId++;
+                    }
+                    if (streamFound) {
+                        break;
+                    }
+                }
+                if (!streamFound) {
+                    mStreamIdxArray = null;
+                    mSurfaceIdxArray = null;
+                    throw new IllegalArgumentException(
+                            "CaptureRequest contains unconfigured Input/Output Surface!");
+                }
+            }
+            mSurfaceConverted = true;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void recoverStreamIdToSurface() {
+        synchronized (mSurfacesLock) {
+            if (!mSurfaceConverted) {
+                Log.v(TAG, "Cannot convert already converted surfaces!");
+                return;
+            }
+
+            mStreamIdxArray = null;
+            mSurfaceIdxArray = null;
+            mSurfaceConverted = false;
+        }
+    }
+
+    /**
      * A builder for capture requests.
      *
      * <p>To obtain a builder instance, use the
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 374789c..8b8bbc3 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -800,7 +800,8 @@
                 try {
                     // begin transition to unconfigured
                     mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
-                            /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE);
+                            /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
+                            /*sessionParams*/ null);
                 } catch (CameraAccessException e) {
                     // OK: do not throw checked exceptions.
                     Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 6787d84..f1ffb89 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -32,6 +32,7 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.ReprocessFormatsMap;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SubmitInfo;
 import android.hardware.camera2.utils.SurfaceUtils;
@@ -362,7 +363,7 @@
             outputConfigs.add(new OutputConfiguration(s));
         }
         configureStreamsChecked(/*inputConfig*/null, outputConfigs,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
 
     }
 
@@ -382,12 +383,13 @@
      * @param outputs a list of one or more surfaces, or {@code null} to unconfigure
      * @param operatingMode If the stream configuration is for a normal session,
      *     a constrained high speed session, or something else.
+     * @param sessionParams Session parameters.
      * @return whether or not the configuration was successful
      *
      * @throws CameraAccessException if there were any unexpected problems during configuration
      */
     public boolean configureStreamsChecked(InputConfiguration inputConfig,
-            List<OutputConfiguration> outputs, int operatingMode)
+            List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
                     throws CameraAccessException {
         // Treat a null input the same an empty list
         if (outputs == null) {
@@ -463,7 +465,11 @@
                     }
                 }
 
-                mRemoteDevice.endConfigure(operatingMode);
+                if (sessionParams != null) {
+                    mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy());
+                } else {
+                    mRemoteDevice.endConfigure(operatingMode, null);
+                }
 
                 success = true;
             } catch (IllegalArgumentException e) {
@@ -499,7 +505,7 @@
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(null, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
     }
 
     @Override
@@ -515,7 +521,7 @@
         List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);
 
         createCaptureSessionInternal(null, currentOutputs, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null);
     }
 
     @Override
@@ -535,7 +541,7 @@
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
     }
 
     @Override
@@ -563,7 +569,8 @@
             currentOutputs.add(new OutputConfiguration(output));
         }
         createCaptureSessionInternal(inputConfig, currentOutputs,
-                callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
+                /*sessionParams*/ null);
     }
 
     @Override
@@ -574,16 +581,13 @@
             throw new IllegalArgumentException(
                     "Output surface list must not be null and the size must be no more than 2");
         }
-        StreamConfigurationMap config =
-                getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config);
-
         List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
         for (Surface surface : outputs) {
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(null, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
+                /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE,
+                /*sessionParams*/ null);
     }
 
     @Override
@@ -596,13 +600,30 @@
         for (OutputConfiguration output : outputs) {
             currentOutputs.add(new OutputConfiguration(output));
         }
-        createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode);
+        createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode,
+                /*sessionParams*/ null);
+    }
+
+    @Override
+    public void createCaptureSession(SessionConfiguration config)
+            throws CameraAccessException {
+        if (config == null) {
+            throw new IllegalArgumentException("Invalid session configuration");
+        }
+
+        List<OutputConfiguration> outputConfigs = config.getOutputConfigurations();
+        if (outputConfigs == null) {
+            throw new IllegalArgumentException("Invalid output configurations");
+        }
+        createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
+                config.getStateCallback(), config.getHandler(), config.getSessionType(),
+                config.getSessionParameters());
     }
 
     private void createCaptureSessionInternal(InputConfiguration inputConfig,
             List<OutputConfiguration> outputConfigurations,
             CameraCaptureSession.StateCallback callback, Handler handler,
-            int operatingMode) throws CameraAccessException {
+            int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
         synchronized(mInterfaceLock) {
             if (DEBUG) {
                 Log.d(TAG, "createCaptureSessionInternal");
@@ -630,7 +651,7 @@
             try {
                 // configure streams and then block until IDLE
                 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
-                        operatingMode);
+                        operatingMode, sessionParams);
                 if (configureSuccess == true && inputConfig != null) {
                     input = mRemoteDevice.getInputSurface();
                 }
@@ -646,6 +667,14 @@
             // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
             CameraCaptureSessionCore newSession = null;
             if (isConstrainedHighSpeed) {
+                ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size());
+                for (OutputConfiguration outConfig : outputConfigurations) {
+                    surfaces.add(outConfig.getSurface());
+                }
+                StreamConfigurationMap config =
+                    getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config);
+
                 newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
                         callback, handler, this, mDeviceHandler, configureSuccess,
                         mCharacteristics);
@@ -779,6 +808,7 @@
             }
 
             mRemoteDevice.updateOutputConfiguration(streamId, config);
+            mConfiguredOutputs.put(streamId, config);
         }
     }
 
@@ -828,6 +858,7 @@
                             + " must have at least 1 surface");
                 }
                 mRemoteDevice.finalizeOutputConfigurations(streamId, config);
+                mConfiguredOutputs.put(streamId, config);
             }
         }
     }
@@ -950,11 +981,20 @@
             SubmitInfo requestInfo;
 
             CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
+            // Convert Surface to streamIdx and surfaceIdx
+            for (CaptureRequest request : requestArray) {
+                request.convertSurfaceToStreamId(mConfiguredOutputs);
+            }
+
             requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
             if (DEBUG) {
                 Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
             }
 
+            for (CaptureRequest request : requestArray) {
+                request.recoverStreamIdToSurface();
+            }
+
             if (callback != null) {
                 mCaptureCallbackMap.put(requestInfo.getRequestId(),
                         new CaptureCallbackHolder(
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 0978ff8..1f4ed13 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -106,9 +106,11 @@
         }
     }
 
-    public void endConfigure(int operatingMode) throws CameraAccessException {
+    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams)
+           throws CameraAccessException {
         try {
-            mRemoteDevice.endConfigure(operatingMode);
+            mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
+                    new CameraMetadataNative() : sessionParams);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
             throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 119cca8..eccab75 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -498,7 +498,7 @@
     }
 
     @Override
-    public void endConfigure(int operatingMode) {
+    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
         if (DEBUG) {
             Log.d(TAG, "endConfigure called.");
         }
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 7409671..a85b5f7 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -486,6 +486,7 @@
         this.mConfiguredSize = other.mConfiguredSize;
         this.mConfiguredGenerationId = other.mConfiguredGenerationId;
         this.mIsDeferredConfig = other.mIsDeferredConfig;
+        this.mIsShared = other.mIsShared;
     }
 
     /**
@@ -498,6 +499,7 @@
         int width = source.readInt();
         int height = source.readInt();
         boolean isDeferred = source.readInt() == 1;
+        boolean isShared = source.readInt() == 1;
         ArrayList<Surface> surfaces = new ArrayList<Surface>();
         source.readTypedList(surfaces, Surface.CREATOR);
 
@@ -508,6 +510,7 @@
         mSurfaces = surfaces;
         mConfiguredSize = new Size(width, height);
         mIsDeferredConfig = isDeferred;
+        mIsShared = isShared;
         mSurfaces = surfaces;
         if (mSurfaces.size() > 0) {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
new file mode 100644
index 0000000..a79a6c1
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -0,0 +1,200 @@
+/*
+ * 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.hardware.camera2.params;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * A helper class that aggregates all supported arguments for capture session initialization.
+ */
+public final class SessionConfiguration {
+    /**
+     * A regular session type containing instances of {@link OutputConfiguration} running
+     * at regular non high speed FPS ranges and optionally {@link InputConfiguration} for
+     * reprocessable sessions.
+     *
+     * @see CameraDevice#createCaptureSession
+     * @see CameraDevice#createReprocessableCaptureSession
+     */
+    public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL;
+
+    /**
+     * A high speed session type that can only contain instances of {@link OutputConfiguration}.
+     * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
+     * are not supported.
+     *
+     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+     */
+    public static final int SESSION_HIGH_SPEED =
+        CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED;
+
+    /**
+     * First vendor-specific session mode
+     * @hide
+     */
+    public static final int SESSION_VENDOR_START =
+        CameraDevice.SESSION_OPERATION_MODE_VENDOR_START;
+
+     /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SESSION_"}, value =
+            {SESSION_REGULAR,
+             SESSION_HIGH_SPEED })
+    public @interface SessionMode {};
+
+    // Camera capture session related parameters.
+    private List<OutputConfiguration> mOutputConfigurations;
+    private CameraCaptureSession.StateCallback mStateCallback;
+    private int mSessionType;
+    private Handler mHandler = null;
+    private InputConfiguration mInputConfig = null;
+    private CaptureRequest mSessionParameters = null;
+
+    /**
+     * Create a new {@link SessionConfiguration}.
+     *
+     * @param sessionType The session type.
+     * @param outputs A list of output configurations for the capture session.
+     * @param cb A state callback interface implementation.
+     * @param handler The handler on which the callback will be invoked. If it is
+     *                set to null, the callback will be invoked on the current thread's
+     *                {@link android.os.Looper looper}.
+     *
+     * @see #SESSION_REGULAR
+     * @see #SESSION_HIGH_SPEED
+     * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+     * @see CameraDevice#createCaptureSessionByOutputConfigurations
+     * @see CameraDevice#createReprocessableCaptureSession
+     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+     */
+    public SessionConfiguration(@SessionMode int sessionType,
+            @NonNull List<OutputConfiguration> outputs,
+            @NonNull CameraCaptureSession.StateCallback cb, @Nullable Handler handler) {
+        mSessionType = sessionType;
+        mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+        mStateCallback = cb;
+        mHandler = handler;
+    }
+
+    /**
+     * Retrieve the type of the capture session.
+     *
+     * @return The capture session type.
+     */
+    public @SessionMode int getSessionType() {
+        return mSessionType;
+    }
+
+    /**
+     * Retrieve the {@link OutputConfiguration} list for the capture session.
+     *
+     * @return A list of output configurations for the capture session.
+     */
+    public List<OutputConfiguration> getOutputConfigurations() {
+        return mOutputConfigurations;
+    }
+
+    /**
+     * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+     *
+     * @return A state callback interface implementation.
+     */
+    public CameraCaptureSession.StateCallback getStateCallback() {
+        return mStateCallback;
+    }
+
+    /**
+     * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+     *
+     * @return The handler on which the callback will be invoked. If it is
+     *         set to null, the callback will be invoked on the current thread's
+     *         {@link android.os.Looper looper}.
+     */
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Sets the {@link InputConfiguration} for a reprocessable session. Input configuration are not
+     * supported for {@link #SESSION_HIGH_SPEED}.
+     *
+     * @param input Input configuration.
+     * @throws UnsupportedOperationException In case it is called for {@link #SESSION_HIGH_SPEED}
+     *                                       type session configuration.
+     */
+    public void setInputConfiguration(@NonNull InputConfiguration input) {
+        if (mSessionType != SESSION_HIGH_SPEED) {
+            mInputConfig = input;
+        } else {
+            throw new UnsupportedOperationException("Method not supported for high speed session" +
+                    " types");
+        }
+    }
+
+    /**
+     * Retrieve the {@link InputConfiguration}.
+     *
+     * @return The capture session input configuration.
+     */
+    public InputConfiguration getInputConfiguration() {
+        return mInputConfig;
+    }
+
+    /**
+     * Sets the session wide camera parameters (see {@link CaptureRequest}). This argument can
+     * be set for every supported session type and will be passed to the camera device as part
+     * of the capture session initialization. Session parameters are a subset of the available
+     * capture request parameters (see {@link CameraCharacteristics#getAvailableSessionKeys})
+     * and their application can introduce internal camera delays. To improve camera performance
+     * it is suggested to change them sparingly within the lifetime of the capture session and
+     * to pass their initial values as part of this method.
+     *
+     * @param params A capture request that includes the initial values for any available
+     *               session wide capture keys.
+     */
+    public void setSessionParameters(CaptureRequest params) {
+        mSessionParameters = params;
+    }
+
+    /**
+     * Retrieve the session wide camera parameters (see {@link CaptureRequest}).
+     *
+     * @return A capture request that includes the initial values for any available
+     *         session wide capture keys.
+     */
+    public CaptureRequest getSessionParameters() {
+        return mSessionParameters;
+    }
+}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index e1137aa..c2b2800 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -26,7 +26,7 @@
  * @hide
  */
 @SystemApi
-public class ContextHubInfo {
+public class ContextHubInfo implements Parcelable {
     private int mId;
     private String mName;
     private String mVendor;
@@ -262,7 +262,7 @@
     @Override
     public String toString() {
         String retVal = "";
-        retVal += "Id : " + mId;
+        retVal += "ID/handle : " + mId;
         retVal += ", Name : " + mName;
         retVal += "\n\tVendor : " + mVendor;
         retVal += ", Toolchain : " + mToolchain;
@@ -275,8 +275,6 @@
         retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
         retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
         retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
-        retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
-        retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
 
         return retVal;
     }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 5b89f54..4cea0ac 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,13 +15,15 @@
  */
 package android.hardware.location;
 
-import android.annotation.Nullable;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -29,6 +31,7 @@
 import android.util.Log;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A class that exposes the Context hubs on a device to applications.
@@ -258,9 +261,9 @@
     }
 
     /**
-     * Returns the list of context hubs in the system.
+     * Returns the list of ContextHubInfo objects describing the available Context Hubs.
      *
-     * @return the list of context hub informations
+     * @return the list of ContextHubInfo objects
      *
      * @see ContextHubInfo
      *
@@ -268,7 +271,11 @@
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public List<ContextHubInfo> getContextHubs() {
-        throw new UnsupportedOperationException("TODO: Implement this");
+        try {
+            return mService.getContextHubs();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -287,7 +294,7 @@
             public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
                 Log.e(TAG, "Received a query callback on a non-query request");
                 transaction.setResponse(new ContextHubTransaction.Response<Void>(
-                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
             }
 
             @Override
@@ -319,7 +326,7 @@
             public void onTransactionComplete(int result) {
                 Log.e(TAG, "Received a non-query callback on a query request");
                 transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
-                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
             }
         };
     }
@@ -509,46 +516,46 @@
      * Creates an interface to the ContextHubClient to send down to the service.
      *
      * @param callback the callback to invoke at the client process
-     * @param handler the handler to post callbacks for this client
+     * @param executor the executor to invoke callbacks for this client
      *
      * @return the callback interface
      */
     private IContextHubClientCallback createClientCallback(
-            ContextHubClientCallback callback, Handler handler) {
+            ContextHubClientCallback callback, Executor executor) {
         return new IContextHubClientCallback.Stub() {
             @Override
             public void onMessageFromNanoApp(NanoAppMessage message) {
-                handler.post(() -> callback.onMessageFromNanoApp(message));
+                executor.execute(() -> callback.onMessageFromNanoApp(message));
             }
 
             @Override
             public void onHubReset() {
-                handler.post(() -> callback.onHubReset());
+                executor.execute(() -> callback.onHubReset());
             }
 
             @Override
             public void onNanoAppAborted(long nanoAppId, int abortCode) {
-                handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+                executor.execute(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
             }
 
             @Override
             public void onNanoAppLoaded(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+                executor.execute(() -> callback.onNanoAppLoaded(nanoAppId));
             }
 
             @Override
             public void onNanoAppUnloaded(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+                executor.execute(() -> callback.onNanoAppUnloaded(nanoAppId));
             }
 
             @Override
             public void onNanoAppEnabled(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+                executor.execute(() -> callback.onNanoAppEnabled(nanoAppId));
             }
 
             @Override
             public void onNanoAppDisabled(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+                executor.execute(() -> callback.onNanoAppDisabled(nanoAppId));
             }
         };
     }
@@ -560,9 +567,9 @@
      * registration succeeds, the client can send messages to nanoapps through the returned
      * {@link ContextHubClient} object, and receive notifications through the provided callback.
      *
-     * @param callback the notification callback to register
      * @param hubInfo  the hub to attach this client to
-     * @param handler  the handler to invoke the callback, if null uses the main thread's Looper
+     * @param callback the notification callback to register
+     * @param executor the executor to invoke the callback
      * @return the registered client object
      *
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
@@ -572,8 +579,9 @@
      * @hide
      * @see ContextHubClientCallback
      */
-    public ContextHubClient createClient(
-            ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
         if (callback == null) {
             throw new NullPointerException("Callback cannot be null");
         }
@@ -581,8 +589,7 @@
             throw new NullPointerException("Hub info cannot be null");
         }
 
-        Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
-        IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
+        IContextHubClientCallback clientInterface = createClientCallback(callback, executor);
 
         IContextHubClient client;
         try {
@@ -595,6 +602,25 @@
     }
 
     /**
+     * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+     * with the executor using the main thread's Looper.
+     *
+     * @param hubInfo  the hub to attach this client to
+     * @param callback the notification callback to register
+     * @return the registered client object
+     *
+     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+     * @throws IllegalStateException    if there were too many registered clients at the service
+     * @throws NullPointerException     if callback or hubInfo is null
+     * @hide
+     * @see ContextHubClientCallback
+     */
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
+        return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index 2a66cbc..a1b743d 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -15,15 +15,16 @@
  */
 package android.hardware.location;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
+import android.os.HandlerExecutor;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -33,8 +34,8 @@
  * This object is generated as a result of an asynchronous request sent to the Context Hub
  * through the ContextHubManager APIs. The caller can either retrieve the result
  * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
- * asynchronously through a user-defined callback
- * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
+ * asynchronously through a user-defined listener
+ * ({@link #setOnCompleteListener(Listener, Executor)} )}).
  *
  * @param <T> the type of the contents in the transaction response
  *
@@ -66,51 +67,51 @@
      * Constants describing the result of a transaction or request through the Context Hub Service.
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "TRANSACTION_" }, value = {
-            TRANSACTION_SUCCESS,
-            TRANSACTION_FAILED_UNKNOWN,
-            TRANSACTION_FAILED_BAD_PARAMS,
-            TRANSACTION_FAILED_UNINITIALIZED,
-            TRANSACTION_FAILED_PENDING,
-            TRANSACTION_FAILED_AT_HUB,
-            TRANSACTION_FAILED_TIMEOUT,
-            TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE,
-            TRANSACTION_FAILED_HAL_UNAVAILABLE
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_SUCCESS,
+            RESULT_FAILED_UNKNOWN,
+            RESULT_FAILED_BAD_PARAMS,
+            RESULT_FAILED_UNINITIALIZED,
+            RESULT_FAILED_PENDING,
+            RESULT_FAILED_AT_HUB,
+            RESULT_FAILED_TIMEOUT,
+            RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
+            RESULT_FAILED_HAL_UNAVAILABLE
     })
     public @interface Result {}
-    public static final int TRANSACTION_SUCCESS = 0;
+    public static final int RESULT_SUCCESS = 0;
     /**
      * Generic failure mode.
      */
-    public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+    public static final int RESULT_FAILED_UNKNOWN = 1;
     /**
      * Failure mode when the request parameters were not valid.
      */
-    public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+    public static final int RESULT_FAILED_BAD_PARAMS = 2;
     /**
      * Failure mode when the Context Hub is not initialized.
      */
-    public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+    public static final int RESULT_FAILED_UNINITIALIZED = 3;
     /**
      * Failure mode when there are too many transactions pending.
      */
-    public static final int TRANSACTION_FAILED_PENDING = 4;
+    public static final int RESULT_FAILED_PENDING = 4;
     /**
      * Failure mode when the request went through, but failed asynchronously at the hub.
      */
-    public static final int TRANSACTION_FAILED_AT_HUB = 5;
+    public static final int RESULT_FAILED_AT_HUB = 5;
     /**
      * Failure mode when the transaction has timed out.
      */
-    public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+    public static final int RESULT_FAILED_TIMEOUT = 6;
     /**
      * Failure mode when the transaction has failed internally at the service.
      */
-    public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
+    public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7;
     /**
      * Failure mode when the Context Hub HAL was not available.
      */
-    public static final int TRANSACTION_FAILED_HAL_UNAVAILABLE = 8;
+    public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
 
     /**
      * A class describing the response for a ContextHubTransaction.
@@ -145,20 +146,20 @@
     }
 
     /**
-     * An interface describing the callback to be invoked when a transaction completes.
+     * An interface describing the listener for a transaction completion.
      *
-     * @param <C> the type of the contents in the transaction response
+     * @param <L> the type of the contents in the transaction response
      */
     @FunctionalInterface
-    public interface Callback<C> {
+    public interface Listener<L> {
         /**
-         * The callback to invoke when the transaction completes.
+         * The listener function to invoke when the transaction completes.
          *
          * @param transaction the transaction that this callback was attached to.
          * @param response the response of the transaction.
          */
         void onComplete(
-                ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+                ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response);
     }
 
     /*
@@ -173,14 +174,14 @@
     private ContextHubTransaction.Response<T> mResponse;
 
     /*
-     * The handler to invoke the aynsc response supplied by onComplete.
+     * The executor to invoke the onComplete async callback.
      */
-    private Handler mHandler = null;
+    private Executor mExecutor = null;
 
     /*
-     * The callback to invoke when the transaction completes.
+     * The listener to be invoked when the transaction completes.
      */
-    private ContextHubTransaction.Callback<T> mCallback = null;
+    private ContextHubTransaction.Listener<T> mListener = null;
 
     /*
      * Synchronization latch used to block on response.
@@ -258,73 +259,68 @@
     }
 
     /**
-     * Sets a callback to be invoked when the transaction completes.
+     * Sets the listener to be invoked invoked when the transaction completes.
      *
      * This function provides an asynchronous approach to retrieve the result of the
      * transaction. When the transaction response has been provided by the Context Hub,
-     * the given callback will be posted by the provided handler.
+     * the given listener will be invoked.
      *
-     * If the transaction has already completed at the time of invocation, the callback
-     * will be immediately posted by the handler. If the transaction has been invalidated,
-     * the callback will never be invoked.
+     * If the transaction has already completed at the time of invocation, the listener
+     * will be immediately invoked. If the transaction has been invalidated,
+     * the listener will never be invoked.
      *
      * A transaction can be invalidated if the process owning the transaction is no longer active
      * and the reference to this object is lost.
      *
-     * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback)} can only be
+     * This method or {@link #setOnCompleteListener(ContextHubTransaction.Listener)} can only be
      * invoked once, or an IllegalStateException will be thrown.
      *
-     * @param callback the callback to be invoked upon completion
-     * @param handler the handler to post the callback
+     * @param listener the listener to be invoked upon completion
+     * @param executor the executor to invoke the callback
      *
      * @throws IllegalStateException if this method is called multiple times
      * @throws NullPointerException if the callback or handler is null
      */
-    public void setCallbackOnComplete(
-            @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+    public void setOnCompleteListener(
+            @NonNull ContextHubTransaction.Listener<T> listener,
+            @NonNull @CallbackExecutor Executor executor) {
         synchronized (this) {
-            if (callback == null) {
-                throw new NullPointerException("Callback cannot be null");
+            if (listener == null) {
+                throw new NullPointerException("Listener cannot be null");
             }
-            if (handler == null) {
-                throw new NullPointerException("Handler cannot be null");
+            if (executor == null) {
+                throw new NullPointerException("Executor cannot be null");
             }
-            if (mCallback != null) {
+            if (mListener != null) {
                 throw new IllegalStateException(
-                        "Cannot set ContextHubTransaction callback multiple times");
+                        "Cannot set ContextHubTransaction listener multiple times");
             }
 
-            mCallback = callback;
-            mHandler = handler;
+            mListener = listener;
+            mExecutor = executor;
 
             if (mDoneSignal.getCount() == 0) {
-                boolean callbackPosted = mHandler.post(() -> {
-                    mCallback.onComplete(this, mResponse);
-                });
-
-                if (!callbackPosted) {
-                    Log.e(TAG, "Failed to post callback to Handler");
-                }
+                mExecutor.execute(() -> mListener.onComplete(this, mResponse));
             }
         }
     }
 
     /**
-     * Sets a callback to be invoked when the transaction completes.
+     * Sets the listener to be invoked invoked when the transaction completes.
      *
-     * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
-     * with the handler being that of the main thread's Looper.
+     * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.Listener, Executor)}
+     * with the executor using the main thread's Looper.
      *
-     * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+     * This method or {@link #setOnCompleteListener(ContextHubTransaction.Listener, Executor)}
      * can only be invoked once, or an IllegalStateException will be thrown.
      *
-     * @param callback the callback to be invoked upon completion
+     * @param listener the listener to be invoked upon completion
      *
      * @throws IllegalStateException if this method is called multiple times
      * @throws NullPointerException if the callback is null
      */
-    public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
-        setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+    public void setOnCompleteListener(@NonNull ContextHubTransaction.Listener<T> listener) {
+        setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain()));
     }
 
     /**
@@ -339,7 +335,7 @@
      * @throws IllegalStateException if this method is invoked multiple times
      * @throws NullPointerException if the response is null
      */
-    void setResponse(ContextHubTransaction.Response<T> response) {
+    /* package */ void setResponse(ContextHubTransaction.Response<T> response) {
         synchronized (this) {
             if (response == null) {
                 throw new NullPointerException("Response cannot be null");
@@ -353,14 +349,8 @@
             mIsResponseSet = true;
 
             mDoneSignal.countDown();
-            if (mCallback != null) {
-                boolean callbackPosted = mHandler.post(() -> {
-                    mCallback.onComplete(this, mResponse);
-                });
-
-                if (!callbackPosted) {
-                    Log.e(TAG, "Failed to post callback to Handler");
-                }
+            if (mListener != null) {
+                mExecutor.execute(() -> mListener.onComplete(this, mResponse));
             }
         }
     }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index db5bd36..233e857 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -43,23 +43,26 @@
     ContextHubInfo getContextHubInfo(int contextHubHandle);
 
     // Loads a nanoapp at the specified hub (old API)
-    int loadNanoApp(int hubHandle, in NanoApp app);
+    int loadNanoApp(int contextHubHandle, in NanoApp nanoApp);
 
     // Unloads a nanoapp given its instance ID (old API)
-    int unloadNanoApp(int nanoAppInstanceHandle);
+    int unloadNanoApp(int nanoAppHandle);
 
     // Gets the NanoAppInstanceInfo of a nanoapp give its instance ID
-    NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle);
+    NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle);
 
     // Finds all nanoApp instances matching some filter
-    int[] findNanoAppOnHub(int hubHandle, in NanoAppFilter filter);
+    int[] findNanoAppOnHub(int contextHubHandle, in NanoAppFilter filter);
 
     // Sends a message to a nanoApp
-    int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+    int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
 
     // Creates a client to send and receive messages
     IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
 
+    // Returns a list of ContextHub objects of available hubs
+    List<ContextHubInfo> getContextHubs();
+
     // Loads a nanoapp at the specified hub (new API)
     void loadNanoAppOnHub(
             int contextHubId, in IContextHubTransactionCallback transactionCallback,
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index b7e6b66..f73fd87 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -27,15 +27,13 @@
  * Describes an instance of a nanoapp, used by the internal state manged by ContextHubService.
  *
  * TODO(b/69270990) Remove this class once the old API is deprecated.
- * TODO(b/70624255) Clean up toString() by removing unnecessary fields
  *
  * @hide
  */
 @SystemApi
 public class NanoAppInstanceInfo {
-    private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
-    private String mPublisher = PRE_LOADED_GENERIC_UNKNOWN;
-    private String mName = PRE_LOADED_GENERIC_UNKNOWN;
+    private String mPublisher = "Unknown";
+    private String mName = "Unknown";
 
     private int mHandle;
     private long mAppId;
@@ -227,9 +225,7 @@
     public String toString() {
         String retVal = "handle : " + mHandle;
         retVal += ", Id : 0x" + Long.toHexString(mAppId);
-        retVal += ", Version : " + mAppVersion;
-        retVal += ", Name : " + mName;
-        retVal += ", Publisher : " + mPublisher;
+        retVal += ", Version : 0x" + Integer.toHexString(mAppVersion);
 
         return retVal;
     }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 8071e8b..11d338d 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1794,7 +1794,7 @@
                 ITelephony it = ITelephony.Stub.asInterface(b);
                 int subId = SubscriptionManager.getDefaultDataSubscriptionId();
                 Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId);
-                boolean retVal = it.getDataEnabled(subId);
+                boolean retVal = it.isUserDataEnabled(subId);
                 Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId
                         + " retVal=" + retVal);
                 return retVal;
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9180112..95e7f60 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -67,4 +67,13 @@
     /** Unregisters a callback on data usage. */
     void unregisterUsageRequest(in DataUsageRequest request);
 
+    /** Get the uid stats information since boot */
+    long getUidStats(int uid, int type);
+
+    /** Get the iface stats information since boot */
+    long getIfaceStats(String iface, int type);
+
+    /** Get the total network stats information since boot */
+    long getTotalStats(int type);
+
 }
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 3458861..d6992aa 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -130,11 +130,12 @@
     }
 
     /**
-     * @return a String representation of the OUI part of this MacAddres,
-     * with the lower 3 bytes constituting the NIC part replaced with 0.
+     * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
+     * numbers in [0,ff] joined by ':' characters.
      */
-    public String toSafeString() {
-        return stringAddrFromLongAddr(mAddr & OUI_MASK);
+    public String toOuiString() {
+        return String.format(
+                "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
     }
 
     @Override
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 954e59c..196a3bc 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.DownloadManager;
 import android.app.backup.BackupManager;
 import android.app.usage.NetworkStatsManager;
@@ -154,6 +155,8 @@
 
     private static Object sProfilingLock = new Object();
 
+    private static final String LOOPBACK_IFACE = "lo";
+
     /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
@@ -502,7 +505,12 @@
     public static long getMobileTcpRxPackets() {
         long total = 0;
         for (String iface : getMobileIfaces()) {
-            final long stat = nativeGetIfaceStat(iface, TYPE_TCP_RX_PACKETS);
+            long stat = UNSUPPORTED;
+            try {
+                stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
             if (stat != UNSUPPORTED) {
                 total += stat;
             }
@@ -514,7 +522,12 @@
     public static long getMobileTcpTxPackets() {
         long total = 0;
         for (String iface : getMobileIfaces()) {
-            final long stat = nativeGetIfaceStat(iface, TYPE_TCP_TX_PACKETS);
+            long stat = UNSUPPORTED;
+            try {
+                stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
             if (stat != UNSUPPORTED) {
                 total += stat;
             }
@@ -524,22 +537,78 @@
 
     /** {@hide} */
     public static long getTxPackets(String iface) {
-        return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
+        try {
+            return getStatsService().getIfaceStats(iface, TYPE_TX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     public static long getRxPackets(String iface) {
-        return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
+        try {
+            return getStatsService().getIfaceStats(iface, TYPE_RX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     public static long getTxBytes(String iface) {
-        return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
+        try {
+            return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     public static long getRxBytes(String iface) {
-        return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
+        try {
+            return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static long getLoopbackTxPackets() {
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static long getLoopbackRxPackets() {
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static long getLoopbackTxBytes() {
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static long getLoopbackRxBytes() {
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -552,7 +621,11 @@
      * return {@link #UNSUPPORTED} on devices where statistics aren't available.
      */
     public static long getTotalTxPackets() {
-        return nativeGetTotalStat(TYPE_TX_PACKETS);
+        try {
+            return getStatsService().getTotalStats(TYPE_TX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -565,7 +638,11 @@
      * return {@link #UNSUPPORTED} on devices where statistics aren't available.
      */
     public static long getTotalRxPackets() {
-        return nativeGetTotalStat(TYPE_RX_PACKETS);
+        try {
+            return getStatsService().getTotalStats(TYPE_RX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -578,7 +655,11 @@
      * return {@link #UNSUPPORTED} on devices where statistics aren't available.
      */
     public static long getTotalTxBytes() {
-        return nativeGetTotalStat(TYPE_TX_BYTES);
+        try {
+            return getStatsService().getTotalStats(TYPE_TX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -591,7 +672,11 @@
      * return {@link #UNSUPPORTED} on devices where statistics aren't available.
      */
     public static long getTotalRxBytes() {
-        return nativeGetTotalStat(TYPE_RX_BYTES);
+        try {
+            return getStatsService().getTotalStats(TYPE_RX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -617,7 +702,11 @@
         // unsupported value. The real filtering is done at the kernel level.
         final int callingUid = android.os.Process.myUid();
         if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
-            return nativeGetUidStat(uid, TYPE_TX_BYTES);
+            try {
+                return getStatsService().getUidStats(uid, TYPE_TX_BYTES);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         } else {
             return UNSUPPORTED;
         }
@@ -646,7 +735,11 @@
         // unsupported value. The real filtering is done at the kernel level.
         final int callingUid = android.os.Process.myUid();
         if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
-            return nativeGetUidStat(uid, TYPE_RX_BYTES);
+            try {
+                return getStatsService().getUidStats(uid, TYPE_RX_BYTES);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         } else {
             return UNSUPPORTED;
         }
@@ -675,7 +768,11 @@
         // unsupported value. The real filtering is done at the kernel level.
         final int callingUid = android.os.Process.myUid();
         if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
-            return nativeGetUidStat(uid, TYPE_TX_PACKETS);
+            try {
+                return getStatsService().getUidStats(uid, TYPE_TX_PACKETS);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         } else {
             return UNSUPPORTED;
         }
@@ -704,7 +801,11 @@
         // unsupported value. The real filtering is done at the kernel level.
         final int callingUid = android.os.Process.myUid();
         if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
-            return nativeGetUidStat(uid, TYPE_RX_PACKETS);
+            try {
+                return getStatsService().getUidStats(uid, TYPE_RX_PACKETS);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         } else {
             return UNSUPPORTED;
         }
@@ -832,8 +933,4 @@
     private static final int TYPE_TX_PACKETS = 3;
     private static final int TYPE_TCP_RX_PACKETS = 4;
     private static final int TYPE_TCP_TX_PACKETS = 5;
-
-    private static native long nativeGetTotalStat(int type);
-    private static native long nativeGetIfaceStat(String iface, int type);
-    private static native long nativeGetUidStat(int uid, int type);
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2e9eeb1..1e847c5 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.app.ActivityManager;
 import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -33,6 +34,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
@@ -225,8 +227,11 @@
      * New in version 28:
      *   - Light/Deep Doze power
      *   - WiFi Multicast Wakelock statistics (count & duration)
+     * New in version 29:
+     *   - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
+     *   - CPU times per UID process state
      */
-    static final int CHECKIN_VERSION = 28;
+    static final int CHECKIN_VERSION = 29;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -327,7 +332,8 @@
      *
      * Other types might include times spent in foreground, background etc.
      */
-    private final String UID_TIMES_TYPE_ALL = "A";
+    @VisibleForTesting
+    public static final String UID_TIMES_TYPE_ALL = "A";
 
     /**
      * State for keeping track of counting information.
@@ -507,6 +513,31 @@
     }
 
     /**
+     * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+     */
+    public static int mapToInternalProcessState(int procState) {
+        if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+            return ActivityManager.PROCESS_STATE_NONEXISTENT;
+        } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+            return Uid.PROCESS_STATE_TOP;
+        } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            // Persistent and other foreground states go here.
+            return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+        } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+            // Persistent and other foreground states go here.
+            return Uid.PROCESS_STATE_FOREGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+            return Uid.PROCESS_STATE_BACKGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+            return Uid.PROCESS_STATE_TOP_SLEEPING;
+        } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+            return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+        } else {
+            return Uid.PROCESS_STATE_CACHED;
+        }
+    }
+
+    /**
      * The statistics associated with a particular uid.
      */
     public static abstract class Uid {
@@ -645,6 +676,15 @@
         public abstract long[] getCpuFreqTimes(int which);
         public abstract long[] getScreenOffCpuFreqTimes(int which);
 
+        /**
+         * Returns cpu times of an uid at a particular process state.
+         */
+        public abstract long[] getCpuFreqTimes(int which, int procState);
+        /**
+         * Returns cpu times of an uid while the screen if off at a particular process state.
+         */
+        public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
         // Note: the following times are disjoint.  They can be added together to find the
         // total time a uid has had any processes running at all.
 
@@ -658,32 +698,61 @@
          */
         public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
         /**
-         * Time this uid has any process that is top while the device is sleeping, but none
-         * in the "foreground service" or better state.
-         */
-        public static final int PROCESS_STATE_TOP_SLEEPING = 2;
-        /**
          * Time this uid has any process in an active foreground state, but none in the
          * "top sleeping" or better state.
          */
-        public static final int PROCESS_STATE_FOREGROUND = 3;
+        public static final int PROCESS_STATE_FOREGROUND = 2;
         /**
          * Time this uid has any process in an active background state, but none in the
          * "foreground" or better state.
          */
-        public static final int PROCESS_STATE_BACKGROUND = 4;
+        public static final int PROCESS_STATE_BACKGROUND = 3;
+        /**
+         * Time this uid has any process that is top while the device is sleeping, but not
+         * active for any other reason.  We kind-of consider it a kind of cached process
+         * for execution restrictions.
+         */
+        public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+        /**
+         * Time this uid has any process that is in the background but it has an activity
+         * marked as "can't save state".  This is essentially a cached process, though the
+         * system will try much harder than normal to avoid killing it.
+         */
+        public static final int PROCESS_STATE_HEAVY_WEIGHT = 5;
         /**
          * Time this uid has any processes that are sitting around cached, not in one of the
          * other active states.
          */
-        public static final int PROCESS_STATE_CACHED = 5;
+        public static final int PROCESS_STATE_CACHED = 6;
         /**
          * Total number of process states we track.
          */
-        public static final int NUM_PROCESS_STATE = 6;
+        public static final int NUM_PROCESS_STATE = 7;
 
+        // Used in dump
         static final String[] PROCESS_STATE_NAMES = {
-            "Top", "Fg Service", "Top Sleeping", "Foreground", "Background", "Cached"
+                "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
+                "Cached"
+        };
+
+        // Used in checkin dump
+        @VisibleForTesting
+        public static final String[] UID_PROCESS_TYPES = {
+                "T",  // TOP
+                "FS", // FOREGROUND_SERVICE
+                "F",  // FOREGROUND
+                "B",  // BACKGROUND
+                "TS", // TOP_SLEEPING
+                "HW",  // HEAVY_WEIGHT
+                "C"   // CACHED
+        };
+
+        /**
+         * When the process exits one of these states, we need to make sure cpu time in this state
+         * is not attributed to any non-critical process states.
+         */
+        public static final int[] CRITICAL_PROC_STATES = {
+            PROCESS_STATE_TOP, PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_STATE_FOREGROUND
         };
 
         public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
@@ -3994,6 +4063,29 @@
                     dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
                             cpuFreqTimeMs.length, sb.toString());
                 }
+
+                for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                    final long[] timesMs = u.getCpuFreqTimes(which, procState);
+                    if (timesMs != null && timesMs.length == cpuFreqs.length) {
+                        sb.setLength(0);
+                        for (int i = 0; i < timesMs.length; ++i) {
+                            sb.append((i == 0 ? "" : ",") + timesMs[i]);
+                        }
+                        final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+                                which, procState);
+                        if (screenOffTimesMs != null) {
+                            for (int i = 0; i < screenOffTimesMs.length; ++i) {
+                                sb.append("," + screenOffTimesMs[i]);
+                            }
+                        } else {
+                            for (int i = 0; i < timesMs.length; ++i) {
+                                sb.append(",0");
+                            }
+                        }
+                        dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+                                Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+                    }
+                }
             }
 
             final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
@@ -5604,6 +5696,30 @@
                 pw.println(sb.toString());
             }
 
+            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+                if (cpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("    Cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < cpuTimes.length; ++i) {
+                        sb.append(" " + cpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+
+                final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+                if (screenOffCpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("   Screen-off cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+                        sb.append(" " + screenOffCpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+            }
+
             final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
                     = u.getProcessStats();
             for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2acf36f..848ab88 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1136,7 +1136,7 @@
             int intervalUs) {
         VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs);
     }
-    
+
     /**
      * Formats name of trace log file for method tracing.
      */
@@ -1706,11 +1706,11 @@
      * Retrieves information about this processes memory usages. This information is broken down by
      * how much is in use by dalvik, the native heap, and everything else.
      *
-     * <p><b>Note:</b> this method directly retrieves memory information for the give process
+     * <p><b>Note:</b> this method directly retrieves memory information for the given process
      * from low-level data available to it.  It may not be able to retrieve information about
      * some protected allocations, such as graphics.  If you want to be sure you can see
-     * all information about allocations by the process, use instead
-     * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p>
+     * all information about allocations by the process, use
+     * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])} instead.</p>
      */
     public static native void getMemoryInfo(MemoryInfo memoryInfo);
 
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 1a76648..b67b99f 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.os.IIncidentReportCompletedListener;
 import android.os.IIncidentReportStatusListener;
 import android.os.IncidentReportArgs;
 
diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl
deleted file mode 100644
index 2d66bf6..0000000
--- a/core/java/android/os/IIncidentReportCompletedListener.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
-  * Listener for incident report status
-  *
-  * {@hide}
-  */
-oneway interface IIncidentReportCompletedListener {
-    /**
-     * Called when there has been an incident report.
-     *
-     * The system service implementing this method should delete or move the file
-     * after it is finished with it.
-     */
-    void onIncidentReport(String filename);
-}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9c90c38..f643c57 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,9 +79,7 @@
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
     boolean markGuestForDeletion(int userHandle);
-    void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target);
     boolean isQuietModeEnabled(int userHandle);
-    boolean trySetQuietModeDisabled(int userHandle, in IntentSender target);
     void setSeedAccountData(int userHandle, in String accountName,
             in String accountType, in PersistableBundle accountOptions, boolean persist);
     String getSeedAccountName();
@@ -99,4 +97,5 @@
     boolean isUserRunning(int userId);
     boolean isUserNameSet(int userHandle);
     boolean hasRestrictedProfiles();
+    boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 56c6391..cd6d41b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1540,7 +1540,7 @@
          */
         public void setWorkSource(WorkSource ws) {
             synchronized (mToken) {
-                if (ws != null && ws.size() == 0) {
+                if (ws != null && ws.isEmpty()) {
                     ws = null;
                 }
 
@@ -1552,7 +1552,7 @@
                     changed = true;
                     mWorkSource = new WorkSource(ws);
                 } else {
-                    changed = mWorkSource.diff(ws);
+                    changed = !mWorkSource.equals(ws);
                     if (changed) {
                         mWorkSource.set(ws);
                     }
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 77ac2651..3ef0961 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -110,7 +110,7 @@
      *
      * This method must only be called by the device administration policy manager.
      */
-    public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs);
+    public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int userId, long timeMs);
 
     /**
      * Used by the dream manager to override certain properties while dozing.
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b9b9a18..bbb8a7b 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -334,6 +334,23 @@
     }
 
     /**
+     * Performs {@code action} for each cookie associated with a callback, calling
+     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+     *
+     * @hide
+     */
+    public <C> void broadcastForEachCookie(Consumer<C> action) {
+        int itemCount = beginBroadcast();
+        try {
+            for (int i = 0; i < itemCount; i++) {
+                action.accept((C) getBroadcastCookie(i));
+            }
+        } finally {
+            finishBroadcast();
+        }
+    }
+
+    /**
      * Returns the number of registered callbacks. Note that the number of registered
      * callbacks may differ from the value returned by {@link #beginBroadcast()} since
      * the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
index 3ec744d..3e8161f 100644
--- a/core/java/android/os/StatsLogEventWrapper.java
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -52,7 +52,7 @@
         // pushed ones to be consistent.
         write4Bytes(STATS_BUFFER_TAG_ID);
         mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
-        mStorage.write(fields); // Indicate number of elements in this list.
+        mStorage.write(fields + 1); // Indicate number of elements in this list. +1 for the tag
         mStorage.write(EVENT_TYPE_INT);
         // The first element is the real atom tag number
         write4Bytes(tag);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3504142..dd9fd93 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -194,6 +194,21 @@
     public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
 
     /**
+     * Specifies if airplane mode is disallowed on the device.
+     *
+     * <p> This restriction can only be set by the device owner and the profile owner on the
+     * primary user and it applies globally - i.e. it disables airplane mode on the entire device.
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
+
+    /**
      * Specifies if a user is disallowed from enabling the
      * "Unknown Sources" setting, that allows installation of apps from unknown sources.
      * The default value is <code>false</code>.
@@ -335,6 +350,28 @@
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
 
     /**
+     * Specifies if a user is disallowed from configuring location mode. Device owner and profile
+     * owners can set this restriction and it only applies on the managed user.
+     *
+     * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
+     * user can still turn off location sharing on managed profile when the restriction is set by
+     * profile owner on managed profile.
+     *
+     * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+     * as the device owner or profile owner can still enable or disable location mode via
+     * {@link DevicePolicyManager#setSecureSetting} when this restriction is on.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode";
+
+    /**
      * Specifies if date, time and timezone configuring is disallowed.
      *
      * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
@@ -2130,15 +2167,46 @@
     }
 
     /**
-     * Set quiet mode of a managed profile.
+     * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
+     * managed profile don't run, generate notifications, or consume data or battery.
+     * <p>
+     * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be
+     * shown to the user.
+     * <p>
+     * The change may not happen instantly, however apps can listen for
+     * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
+     * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of
+     * the change of the quiet mode. Apps can also check the current state of quiet mode by
+     * calling {@link #isQuietModeEnabled(UserHandle)}.
+     * <p>
+     * The caller must either be the foreground default launcher or have one of these permissions:
+     * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}.
      *
-     * @param userHandle The user handle of the profile.
-     * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+     * @param enableQuietMode whether quiet mode should be enabled or disabled
+     * @param userHandle user handle of the profile
+     * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+     *         {@code true} otherwise
+     * @throws SecurityException if the caller is invalid
+     * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+     *
+     * @see #isQuietModeEnabled(UserHandle)
+     */
+    public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+        return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+    }
+
+    /**
+     * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+     * a target to start when user is unlocked.
+     *
+     * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
      * @hide
      */
-    public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) {
+    public boolean trySetQuietModeEnabled(
+            boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
         try {
-            mService.setQuietModeEnabled(userHandle, enableQuietMode, null);
+            return mService.trySetQuietModeEnabled(
+                    mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2160,27 +2228,6 @@
     }
 
     /**
-     * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user
-     * first by showing the confirm credentials screen and disable quiet mode upon successful
-     * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled}
-     * directly.
-     *
-     * @param userHandle The user that is going to disable quiet mode.
-     * @param target The target to launch when the user is unlocked.
-     * @return {@code true} if quiet mode is disabled without showing confirm credentials screen,
-     *         {@code false} otherwise.
-     * @hide
-     */
-    public boolean trySetQuietModeDisabled(
-            @UserIdInt int userHandle, @Nullable IntentSender target) {
-        try {
-            return mService.trySetQuietModeDisabled(userHandle, target);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a badged copy of the given
      * icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index ecec448..e9d4fe82 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,10 +1,13 @@
 package android.os;
 
+import android.annotation.Nullable;
 import android.os.WorkSourceProto;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Describes the source of some work that may be done by someone else.
@@ -19,6 +22,8 @@
     int[] mUids;
     String[] mNames;
 
+    private ArrayList<WorkChain> mChains;
+
     /**
      * Internal statics to avoid object allocations in some operations.
      * The WorkSource object itself is not thread safe, but we need to
@@ -39,6 +44,7 @@
      */
     public WorkSource() {
         mNum = 0;
+        mChains = null;
     }
 
     /**
@@ -48,6 +54,7 @@
     public WorkSource(WorkSource orig) {
         if (orig == null) {
             mNum = 0;
+            mChains = null;
             return;
         }
         mNum = orig.mNum;
@@ -58,6 +65,16 @@
             mUids = null;
             mNames = null;
         }
+
+        if (orig.mChains != null) {
+            // Make a copy of all WorkChains that exist on |orig| since they are mutable.
+            mChains = new ArrayList<>(orig.mChains.size());
+            for (WorkChain chain : orig.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        } else {
+            mChains = null;
+        }
     }
 
     /** @hide */
@@ -65,6 +82,7 @@
         mNum = 1;
         mUids = new int[] { uid, 0 };
         mNames = null;
+        mChains = null;
     }
 
     /** @hide */
@@ -75,12 +93,21 @@
         mNum = 1;
         mUids = new int[] { uid, 0 };
         mNames = new String[] { name, null };
+        mChains = null;
     }
 
     WorkSource(Parcel in) {
         mNum = in.readInt();
         mUids = in.createIntArray();
         mNames = in.createStringArray();
+
+        int numChains = in.readInt();
+        if (numChains > 0) {
+            mChains = new ArrayList<>(numChains);
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+        } else {
+            mChains = null;
+        }
     }
 
     /** @hide */
@@ -99,7 +126,8 @@
     }
 
     /**
-     * Clear names from this WorkSource.  Uids are left intact.
+     * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
+     * intact.
      *
      * <p>Useful when combining with another WorkSource that doesn't have names.
      * @hide
@@ -127,11 +155,16 @@
      */
     public void clear() {
         mNum = 0;
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
     @Override
     public boolean equals(Object o) {
-        return o instanceof WorkSource && !diff((WorkSource)o);
+        return o instanceof WorkSource
+            && !diff((WorkSource) o)
+            && Objects.equals(mChains, ((WorkSource) o).mChains);
     }
 
     @Override
@@ -145,6 +178,11 @@
                 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
             }
         }
+
+        if (mChains != null) {
+            result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
+        }
+
         return result;
     }
 
@@ -153,6 +191,8 @@
      * @param other The WorkSource to compare against.
      * @return If there is a difference, true is returned.
      */
+    // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
+    // we keep its semantics the same and ignore any differences in WorkChains (if any).
     public boolean diff(WorkSource other) {
         int N = mNum;
         if (N != other.mNum) {
@@ -175,12 +215,15 @@
 
     /**
      * Replace the current contents of this work source with the given
-     * work source.  If <var>other</var> is null, the current work source
+     * work source.  If {@code other} is null, the current work source
      * will be made empty.
      */
     public void set(WorkSource other) {
         if (other == null) {
             mNum = 0;
+            if (mChains != null) {
+                mChains.clear();
+            }
             return;
         }
         mNum = other.mNum;
@@ -203,6 +246,18 @@
             mUids = null;
             mNames = null;
         }
+
+        if (other.mChains != null) {
+            if (mChains != null) {
+                mChains.clear();
+            } else {
+                mChains = new ArrayList<>(other.mChains.size());
+            }
+
+            for (WorkChain chain : other.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        }
     }
 
     /** @hide */
@@ -211,6 +266,9 @@
         if (mUids == null) mUids = new int[2];
         mUids[0] = uid;
         mNames = null;
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
     /** @hide */
@@ -225,9 +283,23 @@
         }
         mUids[0] = uid;
         mNames[0] = name;
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
-    /** @hide */
+    /**
+     * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
+     * differences in chains are returned. This will be removed once its callers have been
+     * rewritten.
+     *
+     * NOTE: This is currently only used in GnssLocationProvider.
+     *
+     * @hide
+     * @deprecated for internal use only. WorkSources are opaque and no external callers should need
+     *     to be aware of internal differences.
+     */
+    @Deprecated
     public WorkSource[] setReturningDiffs(WorkSource other) {
         synchronized (sTmpWorkSource) {
             sNewbWork = null;
@@ -251,11 +323,34 @@
      */
     public boolean add(WorkSource other) {
         synchronized (sTmpWorkSource) {
-            return updateLocked(other, false, false);
+            boolean uidAdded = updateLocked(other, false, false);
+
+            boolean chainAdded = false;
+            if (other.mChains != null) {
+                // NOTE: This is quite an expensive operation, especially if the number of chains
+                // is large. We could look into optimizing it if it proves problematic.
+                if (mChains == null) {
+                    mChains = new ArrayList<>(other.mChains.size());
+                }
+
+                for (WorkChain wc : other.mChains) {
+                    if (!mChains.contains(wc)) {
+                        mChains.add(new WorkChain(wc));
+                    }
+                }
+            }
+
+            return uidAdded || chainAdded;
         }
     }
 
-    /** @hide */
+    /**
+     * Legacy API: DO NOT USE. Only in use from unit tests.
+     *
+     * @hide
+     * @deprecated meant for unit testing use only. Will be removed in a future API revision.
+     */
+    @Deprecated
     public WorkSource addReturningNewbs(WorkSource other) {
         synchronized (sTmpWorkSource) {
             sNewbWork = null;
@@ -311,22 +406,14 @@
         return true;
     }
 
-    /** @hide */
-    public WorkSource addReturningNewbs(int uid) {
-        synchronized (sTmpWorkSource) {
-            sNewbWork = null;
-            sTmpWorkSource.mUids[0] = uid;
-            updateLocked(sTmpWorkSource, false, true);
-            return sNewbWork;
-        }
-    }
-
     public boolean remove(WorkSource other) {
         if (mNum <= 0 || other.mNum <= 0) {
             return false;
         }
+
+        boolean uidRemoved = false;
         if (mNames == null && other.mNames == null) {
-            return removeUids(other);
+            uidRemoved = removeUids(other);
         } else {
             if (mNames == null) {
                 throw new IllegalArgumentException("Other " + other + " has names, but target "
@@ -336,24 +423,54 @@
                 throw new IllegalArgumentException("Target " + this + " has names, but other "
                         + other + " does not");
             }
-            return removeUidsAndNames(other);
+            uidRemoved = removeUidsAndNames(other);
         }
+
+        boolean chainRemoved = false;
+        if (other.mChains != null) {
+            if (mChains != null) {
+                chainRemoved = mChains.removeAll(other.mChains);
+            }
+        } else if (mChains != null) {
+            mChains.clear();
+            chainRemoved = true;
+        }
+
+        return uidRemoved || chainRemoved;
     }
 
-    /** @hide */
-    public WorkSource stripNames() {
-        if (mNum <= 0) {
-            return new WorkSource();
+    /**
+     * Create a new {@code WorkChain} associated with this WorkSource and return it.
+     *
+     * @hide
+     */
+    public WorkChain createWorkChain() {
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
         }
-        WorkSource result = new WorkSource();
-        int lastUid = -1;
-        for (int i=0; i<mNum; i++) {
-            int uid = mUids[i];
-            if (i == 0 || lastUid != uid) {
-                result.add(uid);
-            }
-        }
-        return result;
+
+        final WorkChain wc = new WorkChain();
+        mChains.add(wc);
+
+        return wc;
+    }
+
+    /**
+     * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
+     * attribute usage to.
+     *
+     * @hide for internal use only.
+     */
+    public boolean isEmpty() {
+        return mNum == 0 && (mChains == null || mChains.isEmpty());
+    }
+
+    /**
+     * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+     * @hide
+     */
+    public ArrayList<WorkChain> getWorkChains() {
+        return mChains;
     }
 
     private boolean removeUids(WorkSource other) {
@@ -664,6 +781,224 @@
         }
     }
 
+    /**
+     * Represents an attribution chain for an item of work being performed. An attribution chain is
+     * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
+     * of the work, and the node at the highest index performs the work. Nodes at other indices
+     * are intermediaries that facilitate the work. Examples :
+     *
+     * (1) Work being performed by uid=2456 (no chaining):
+     * <pre>
+     * WorkChain {
+     *   mUids = { 2456 }
+     *   mTags = { null }
+     *   mSize = 1;
+     * }
+     * </pre>
+     *
+     * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
+     *
+     * <pre>
+     * WorkChain {
+     *   mUids = { 5678, 2456 }
+     *   mTags = { null, "c1" }
+     *   mSize = 1
+     * }
+     * </pre>
+     *
+     * Attribution chains are mutable, though the only operation that can be performed on them
+     * is the addition of a new node at the end of the attribution chain to represent
+     *
+     * @hide
+     */
+    public static class WorkChain implements Parcelable {
+        private int mSize;
+        private int[] mUids;
+        private String[] mTags;
+
+        // @VisibleForTesting
+        public WorkChain() {
+            mSize = 0;
+            mUids = new int[4];
+            mTags = new String[4];
+        }
+
+        // @VisibleForTesting
+        public WorkChain(WorkChain other) {
+            mSize = other.mSize;
+            mUids = other.mUids.clone();
+            mTags = other.mTags.clone();
+        }
+
+        private WorkChain(Parcel in) {
+            mSize = in.readInt();
+            mUids = in.createIntArray();
+            mTags = in.createStringArray();
+        }
+
+        /**
+         * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
+         * {@code WorkChain}.
+         */
+        public WorkChain addNode(int uid, @Nullable String tag) {
+            if (mSize == mUids.length) {
+                resizeArrays();
+            }
+
+            mUids[mSize] = uid;
+            mTags[mSize] = tag;
+            mSize++;
+
+            return this;
+        }
+
+        /**
+         * Return the UID to which this WorkChain should be attributed to, i.e, the UID performing
+         * the actual work.
+         */
+        public int getAttributionUid() {
+            return mUids[mSize - 1];
+        }
+
+        // TODO: The following three trivial getters are purely for testing and will be removed
+        // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
+        // diffing it etc.
+        //
+        // @VisibleForTesting
+        public int[] getUids() {
+            return mUids;
+        }
+        // @VisibleForTesting
+        public String[] getTags() {
+            return mTags;
+        }
+        // @VisibleForTesting
+        public int getSize() {
+            return mSize;
+        }
+
+        private void resizeArrays() {
+            final int newSize = mSize * 2;
+            int[] uids = new int[newSize];
+            String[] tags = new String[newSize];
+
+            System.arraycopy(mUids, 0, uids, 0, mSize);
+            System.arraycopy(mTags, 0, tags, 0, mSize);
+
+            mUids = uids;
+            mTags = tags;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("WorkChain{");
+            for (int i = 0; i < mSize; ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append("(");
+                result.append(mUids[i]);
+                if (mTags[i] != null) {
+                    result.append(", ");
+                    result.append(mTags[i]);
+                }
+                result.append(")");
+            }
+
+            result.append("}");
+            return result.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof WorkChain) {
+                WorkChain other = (WorkChain) o;
+
+                return mSize == other.mSize
+                    && Arrays.equals(mUids, other.mUids)
+                    && Arrays.equals(mTags, other.mTags);
+            }
+
+            return false;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mSize);
+            dest.writeIntArray(mUids);
+            dest.writeStringArray(mTags);
+        }
+
+        public static final Parcelable.Creator<WorkChain> CREATOR =
+                new Parcelable.Creator<WorkChain>() {
+                    public WorkChain createFromParcel(Parcel in) {
+                        return new WorkChain(in);
+                    }
+                    public WorkChain[] newArray(int size) {
+                        return new WorkChain[size];
+                    }
+                };
+    }
+
+    /**
+     * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
+     *
+     * Returns {@code null} if no differences exist, otherwise returns a two element array. The
+     * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
+     * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
+     * {@code oldWs} but not in {@code newWs}.
+     *
+     * @hide
+     */
+    public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
+        ArrayList<WorkChain> newChains = null;
+        ArrayList<WorkChain> goneChains = null;
+
+        // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
+        // WorkSource objects. We can replace this with something smarter, for e.g by defining
+        // a Comparator between WorkChains. It's unclear whether that will be more efficient if
+        // the number of chains associated with a WorkSource is expected to be small
+        if (oldWs.mChains != null) {
+            for (int i = 0; i < oldWs.mChains.size(); ++i) {
+                final WorkChain wc = oldWs.mChains.get(i);
+                if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
+                    if (goneChains == null) {
+                        goneChains = new ArrayList<>(oldWs.mChains.size());
+                    }
+                    goneChains.add(wc);
+                }
+            }
+        }
+
+        if (newWs.mChains != null) {
+            for (int i = 0; i < newWs.mChains.size(); ++i) {
+                final WorkChain wc = newWs.mChains.get(i);
+                if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
+                    if (newChains == null) {
+                        newChains = new ArrayList<>(newWs.mChains.size());
+                    }
+                    newChains.add(wc);
+                }
+            }
+        }
+
+        if (newChains != null || goneChains != null) {
+            return new ArrayList[] { newChains, goneChains };
+        }
+
+        return null;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -674,6 +1009,13 @@
         dest.writeInt(mNum);
         dest.writeIntArray(mUids);
         dest.writeStringArray(mNames);
+
+        if (mChains == null) {
+            dest.writeInt(-1);
+        } else {
+            dest.writeInt(mChains.size());
+            dest.writeParcelableList(mChains, flags);
+        }
     }
 
     @Override
@@ -690,6 +1032,17 @@
                 result.append(mNames[i]);
             }
         }
+
+        if (mChains != null) {
+            result.append(" chains=");
+            for (int i = 0; i < mChains.size(); ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append(mChains.get(i));
+            }
+        }
+
         result.append("}");
         return result.toString();
     }
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.aidl b/core/java/android/os/connectivity/CellularBatteryStats.aidl
new file mode 100644
index 0000000..ca0a585
--- /dev/null
+++ b/core/java/android/os/connectivity/CellularBatteryStats.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.os.connectivity;
+
+/** {@hide} */
+parcelable CellularBatteryStats;
\ No newline at end of file
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
new file mode 100644
index 0000000..2593c85
--- /dev/null
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -0,0 +1,242 @@
+/*
+ * 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 android.os.connectivity;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.telephony.ModemActivityInfo;
+import android.telephony.SignalStrength;
+
+import java.util.Arrays;
+
+/**
+ * API for Cellular power stats
+ *
+ * @hide
+ */
+public final class CellularBatteryStats implements Parcelable {
+
+  private long mLoggingDurationMs;
+  private long mKernelActiveTimeMs;
+  private long mNumPacketsTx;
+  private long mNumBytesTx;
+  private long mNumPacketsRx;
+  private long mNumBytesRx;
+  private long mSleepTimeMs;
+  private long mIdleTimeMs;
+  private long mRxTimeMs;
+  private long mEnergyConsumedMaMs;
+  private long[] mTimeInRatMs;
+  private long[] mTimeInRxSignalStrengthLevelMs;
+  private long[] mTxTimeMs;
+
+  public static final Parcelable.Creator<CellularBatteryStats> CREATOR = new
+      Parcelable.Creator<CellularBatteryStats>() {
+        public CellularBatteryStats createFromParcel(Parcel in) {
+          return new CellularBatteryStats(in);
+        }
+
+        public CellularBatteryStats[] newArray(int size) {
+          return new CellularBatteryStats[size];
+        }
+      };
+
+  public CellularBatteryStats() {
+    initialize();
+  }
+
+  public void writeToParcel(Parcel out, int flags) {
+    out.writeLong(mLoggingDurationMs);
+    out.writeLong(mKernelActiveTimeMs);
+    out.writeLong(mNumPacketsTx);
+    out.writeLong(mNumBytesTx);
+    out.writeLong(mNumPacketsRx);
+    out.writeLong(mNumBytesRx);
+    out.writeLong(mSleepTimeMs);
+    out.writeLong(mIdleTimeMs);
+    out.writeLong(mRxTimeMs);
+    out.writeLong(mEnergyConsumedMaMs);
+    out.writeLongArray(mTimeInRatMs);
+    out.writeLongArray(mTimeInRxSignalStrengthLevelMs);
+    out.writeLongArray(mTxTimeMs);
+  }
+
+  public void readFromParcel(Parcel in) {
+    mLoggingDurationMs = in.readLong();
+    mKernelActiveTimeMs = in.readLong();
+    mNumPacketsTx = in.readLong();
+    mNumBytesTx = in.readLong();
+    mNumPacketsRx = in.readLong();
+    mNumBytesRx = in.readLong();
+    mSleepTimeMs = in.readLong();
+    mIdleTimeMs = in.readLong();
+    mRxTimeMs = in.readLong();
+    mEnergyConsumedMaMs = in.readLong();
+    in.readLongArray(mTimeInRatMs);
+    in.readLongArray(mTimeInRxSignalStrengthLevelMs);
+    in.readLongArray(mTxTimeMs);
+  }
+
+  public long getLoggingDurationMs() {
+    return mLoggingDurationMs;
+  }
+
+  public long getKernelActiveTimeMs() {
+    return mKernelActiveTimeMs;
+  }
+
+  public long getNumPacketsTx() {
+    return mNumPacketsTx;
+  }
+
+  public long getNumBytesTx() {
+    return mNumBytesTx;
+  }
+
+  public long getNumPacketsRx() {
+    return mNumPacketsRx;
+  }
+
+  public long getNumBytesRx() {
+    return mNumBytesRx;
+  }
+
+  public long getSleepTimeMs() {
+    return mSleepTimeMs;
+  }
+
+  public long getIdleTimeMs() {
+    return mIdleTimeMs;
+  }
+
+  public long getRxTimeMs() {
+    return mRxTimeMs;
+  }
+
+  public long getEnergyConsumedMaMs() {
+    return mEnergyConsumedMaMs;
+  }
+
+  public long[] getTimeInRatMs() {
+    return mTimeInRatMs;
+  }
+
+  public long[] getTimeInRxSignalStrengthLevelMs() {
+    return mTimeInRxSignalStrengthLevelMs;
+  }
+
+  public long[] getTxTimeMs() {
+    return mTxTimeMs;
+  }
+
+  public void setLoggingDurationMs(long t) {
+    mLoggingDurationMs = t;
+    return;
+  }
+
+  public void setKernelActiveTimeMs(long t) {
+    mKernelActiveTimeMs = t;
+    return;
+  }
+
+  public void setNumPacketsTx(long n) {
+    mNumPacketsTx = n;
+    return;
+  }
+
+  public void setNumBytesTx(long b) {
+    mNumBytesTx = b;
+    return;
+  }
+
+  public void setNumPacketsRx(long n) {
+    mNumPacketsRx = n;
+    return;
+  }
+
+  public void setNumBytesRx(long b) {
+    mNumBytesRx = b;
+    return;
+  }
+
+  public void setSleepTimeMs(long t) {
+    mSleepTimeMs = t;
+    return;
+  }
+
+  public void setIdleTimeMs(long t) {
+    mIdleTimeMs = t;
+    return;
+  }
+
+  public void setRxTimeMs(long t) {
+    mRxTimeMs = t;
+    return;
+  }
+
+  public void setEnergyConsumedMaMs(long e) {
+    mEnergyConsumedMaMs = e;
+    return;
+  }
+
+  public void setTimeInRatMs(long[] t) {
+    mTimeInRatMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, BatteryStats.NUM_DATA_CONNECTION_TYPES));
+    return;
+  }
+
+  public void setTimeInRxSignalStrengthLevelMs(long[] t) {
+    mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, SignalStrength.NUM_SIGNAL_STRENGTH_BINS));
+    return;
+  }
+
+  public void setTxTimeMs(long[] t) {
+    mTxTimeMs = Arrays.copyOfRange(t, 0, Math.min(t.length, ModemActivityInfo.TX_POWER_LEVELS));
+    return;
+  }
+
+  public int describeContents() {
+    return 0;
+  }
+
+  private CellularBatteryStats(Parcel in) {
+    initialize();
+    readFromParcel(in);
+  }
+
+  private void initialize() {
+    mLoggingDurationMs = 0;
+    mKernelActiveTimeMs = 0;
+    mNumPacketsTx = 0;
+    mNumBytesTx = 0;
+    mNumPacketsRx = 0;
+    mNumBytesRx = 0;
+    mSleepTimeMs = 0;
+    mIdleTimeMs = 0;
+    mRxTimeMs = 0;
+    mEnergyConsumedMaMs = 0;
+    mTimeInRatMs = new long[BatteryStats.NUM_DATA_CONNECTION_TYPES];
+    Arrays.fill(mTimeInRatMs, 0);
+    mTimeInRxSignalStrengthLevelMs = new long[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+    Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0);
+    mTxTimeMs = new long[ModemActivityInfo.TX_POWER_LEVELS];
+    Arrays.fill(mTxTimeMs, 0);
+    return;
+  }
+}
\ No newline at end of file
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1fc0b82..070b8c1 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
-import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Parcel;
@@ -78,13 +77,11 @@
 public final class StorageVolume implements Parcelable {
 
     private final String mId;
-    private final int mStorageId;
     private final File mPath;
     private final String mDescription;
     private final boolean mPrimary;
     private final boolean mRemovable;
     private final boolean mEmulated;
-    private final long mMtpReserveSize;
     private final boolean mAllowMassStorage;
     private final long mMaxFileSize;
     private final UserHandle mOwner;
@@ -121,17 +118,15 @@
     public static final int STORAGE_ID_PRIMARY = 0x00010001;
 
     /** {@hide} */
-    public StorageVolume(String id, int storageId, File path, String description, boolean primary,
-            boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+    public StorageVolume(String id, File path, String description, boolean primary,
+            boolean removable, boolean emulated, boolean allowMassStorage,
             long maxFileSize, UserHandle owner, String fsUuid, String state) {
         mId = Preconditions.checkNotNull(id);
-        mStorageId = storageId;
         mPath = Preconditions.checkNotNull(path);
         mDescription = Preconditions.checkNotNull(description);
         mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
-        mMtpReserveSize = mtpReserveSize;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
         mOwner = Preconditions.checkNotNull(owner);
@@ -141,13 +136,11 @@
 
     private StorageVolume(Parcel in) {
         mId = in.readString();
-        mStorageId = in.readInt();
         mPath = new File(in.readString());
         mDescription = in.readString();
         mPrimary = in.readInt() != 0;
         mRemovable = in.readInt() != 0;
         mEmulated = in.readInt() != 0;
-        mMtpReserveSize = in.readLong();
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
@@ -211,34 +204,6 @@
     }
 
     /**
-     * Returns the MTP storage ID for the volume.
-     * this is also used for the storage_id column in the media provider.
-     *
-     * @return MTP storage ID
-     * @hide
-     */
-    public int getStorageId() {
-        return mStorageId;
-    }
-
-    /**
-     * Number of megabytes of space to leave unallocated by MTP.
-     * MTP will subtract this value from the free space it reports back
-     * to the host via GetStorageInfo, and will not allow new files to
-     * be added via MTP if there is less than this amount left free in the storage.
-     * If MTP has dedicated storage this value should be zero, but if MTP is
-     * sharing storage with the rest of the system, set this to a positive value
-     * to ensure that MTP activity does not result in the storage being
-     * too close to full.
-     *
-     * @return MTP reserve space
-     * @hide
-     */
-    public int getMtpReserveSpace() {
-        return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
-    }
-
-    /**
      * Returns true if this volume can be shared via USB mass storage.
      *
      * @return whether mass storage is allowed
@@ -385,13 +350,11 @@
         pw.println("StorageVolume:");
         pw.increaseIndent();
         pw.printPair("mId", mId);
-        pw.printPair("mStorageId", mStorageId);
         pw.printPair("mPath", mPath);
         pw.printPair("mDescription", mDescription);
         pw.printPair("mPrimary", mPrimary);
         pw.printPair("mRemovable", mRemovable);
         pw.printPair("mEmulated", mEmulated);
-        pw.printPair("mMtpReserveSize", mMtpReserveSize);
         pw.printPair("mAllowMassStorage", mAllowMassStorage);
         pw.printPair("mMaxFileSize", mMaxFileSize);
         pw.printPair("mOwner", mOwner);
@@ -420,13 +383,11 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(mId);
-        parcel.writeInt(mStorageId);
         parcel.writeString(mPath.toString());
         parcel.writeString(mDescription);
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
-        parcel.writeLong(mMtpReserveSize);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 76f79f1..d3877ca 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -343,9 +343,7 @@
 
         String description = null;
         String derivedFsUuid = fsUuid;
-        long mtpReserveSize = 0;
         long maxFileSize = 0;
-        int mtpStorageId = StorageVolume.STORAGE_ID_INVALID;
 
         if (type == TYPE_EMULATED) {
             emulated = true;
@@ -356,12 +354,6 @@
                 derivedFsUuid = privateVol.fsUuid;
             }
 
-            if (isPrimary()) {
-                mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
-            }
-
-            mtpReserveSize = storage.getStorageLowBytes(userPath);
-
             if (ID_EMULATED_INTERNAL.equals(id)) {
                 removable = false;
             } else {
@@ -374,14 +366,6 @@
 
             description = storage.getBestVolumeDescription(this);
 
-            if (isPrimary()) {
-                mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
-            } else {
-                // Since MediaProvider currently persists this value, we need a
-                // value that is stable over time.
-                mtpStorageId = buildStableMtpStorageId(fsUuid);
-            }
-
             if ("vfat".equals(fsType)) {
                 maxFileSize = 4294967295L;
             }
@@ -394,8 +378,8 @@
             description = context.getString(android.R.string.unknownName);
         }
 
-        return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
-                emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
+        return new StorageVolume(id, userPath, description, isPrimary(), removable,
+                emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
                 derivedFsUuid, envState);
     }
 
diff --git a/core/java/android/privacy/DifferentialPrivacyConfig.java b/core/java/android/privacy/DifferentialPrivacyConfig.java
new file mode 100644
index 0000000..e14893e
--- /dev/null
+++ b/core/java/android/privacy/DifferentialPrivacyConfig.java
@@ -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.
+ */
+
+package android.privacy;
+
+/**
+ * An interface for differential privacy configuration.
+ * {@link DifferentialPrivacyEncoder} will apply this configuration to do differential privacy
+ * encoding.
+ *
+ * @hide
+ */
+public interface DifferentialPrivacyConfig {
+
+    /**
+     * Returns the name of the algorithm used in differential privacy config.
+     *
+     * @return The name of the algorithm
+     */
+    String getAlgorithm();
+}
diff --git a/core/java/android/privacy/DifferentialPrivacyEncoder.java b/core/java/android/privacy/DifferentialPrivacyEncoder.java
new file mode 100644
index 0000000..9355d6a
--- /dev/null
+++ b/core/java/android/privacy/DifferentialPrivacyEncoder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+package android.privacy;
+
+/**
+ * An interface for differential privacy encoder.
+ * Applications can use it to convert privacy sensitive data to privacy protected report.
+ * There is no decoder implemented in Android as it is not possible decode a single report by
+ * design.
+ *
+ * <p>Each type of log should have its own encoder, otherwise it may leak
+ * some information about Permanent Randomized Response(PRR, is used to create a “noisy”
+ * answer which is memoized by the client and permanently reused in place of the real answer).
+ *
+ * <p>Some encoders may not support all encoding methods, and it will throw {@link
+ * UnsupportedOperationException} if you call unsupported encoding method.
+ *
+ * <p><b>WARNING:</b> Privacy protection works only when encoder uses a suitable DP configuration,
+ * and the configuration and algorithm that is suitable is highly dependent on the use case.
+ * If the configuration is not suitable for the use case, it may hurt privacy or utility or both.
+ *
+ * @hide
+ */
+public interface DifferentialPrivacyEncoder {
+
+    /**
+     * Apply differential privacy to encode a string.
+     *
+     * @param original An arbitrary string
+     * @return Differential privacy encoded bytes derived from the string
+     */
+    byte[] encodeString(String original);
+
+    /**
+     * Apply differential privacy to encode a boolean.
+     *
+     * @param original An arbitrary boolean.
+     * @return Differential privacy encoded bytes derived from the boolean
+     */
+    byte[] encodeBoolean(boolean original);
+
+    /**
+     * Apply differential privacy to encode sequence of bytes.
+     *
+     * @param original An arbitrary byte array.
+     * @return Differential privacy encoded bytes derived from the bytes
+     */
+    byte[] encodeBits(byte[] original);
+
+    /**
+     * Returns the configuration that this encoder is using.
+     */
+    DifferentialPrivacyConfig getConfig();
+
+    /**
+     * Return True if the output from encoder is NOT securely randomized, otherwise encoder should
+     * be secure to randomize input.
+     *
+     * <b> A non-secure encoder is intended only for testing only and must not be used to process
+     * real data.
+     * </b>
+     */
+    boolean isInsecureEncoderForTest();
+}
diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java
new file mode 100644
index 0000000..ee910fc
--- /dev/null
+++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+package android.privacy.internal.longitudinalreporting;
+
+import android.privacy.DifferentialPrivacyConfig;
+import android.privacy.internal.rappor.RapporConfig;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A class to store {@link LongitudinalReportingEncoder} configuration.
+ *
+ * <ul>
+ * <li> f is probability to flip input value, used in IRR.
+ * <li> p is probability to override input value, used in PRR1.
+ * <li> q is probability to set input value as 1 when result of PRR(p) is true, used in PRR2.
+ * </ul>
+ *
+ * @hide
+ */
+public class LongitudinalReportingConfig implements DifferentialPrivacyConfig {
+
+    private static final String ALGORITHM_NAME = "LongitudinalReporting";
+
+    // Probability to flip input value.
+    private final double mProbabilityF;
+
+    // Probability to override original value.
+    private final double mProbabilityP;
+    // Probability to override value with 1.
+    private final double mProbabilityQ;
+
+    // IRR config to randomize original value
+    private final RapporConfig mIRRConfig;
+
+    private final String mEncoderId;
+
+    /**
+     * Constructor to create {@link LongitudinalReportingConfig} used for {@link
+     * LongitudinalReportingEncoder}
+     *
+     * @param encoderId    Unique encoder id.
+     * @param probabilityF Probability F used in Longitudinal Reporting algorithm.
+     * @param probabilityP Probability P used in Longitudinal Reporting algorithm. This will be
+     *                     quantized to the nearest 1/256.
+     * @param probabilityQ Probability Q used in Longitudinal Reporting algorithm. This will be
+     *                     quantized to the nearest 1/256.
+     */
+    public LongitudinalReportingConfig(String encoderId, double probabilityF,
+            double probabilityP, double probabilityQ) {
+        Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1,
+                "probabilityF must be in range [0.0, 1.0]");
+        this.mProbabilityF = probabilityF;
+        Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1,
+                "probabilityP must be in range [0.0, 1.0]");
+        this.mProbabilityP = probabilityP;
+        Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1,
+                "probabilityQ must be in range [0.0, 1.0]");
+        this.mProbabilityQ = probabilityQ;
+        Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty");
+        mEncoderId = encoderId;
+        mIRRConfig = new RapporConfig(encoderId, 1, 0.0, probabilityF, 1 - probabilityF, 1, 1);
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return ALGORITHM_NAME;
+    }
+
+    RapporConfig getIRRConfig() {
+        return mIRRConfig;
+    }
+
+    double getProbabilityP() {
+        return mProbabilityP;
+    }
+
+    double getProbabilityQ() {
+        return mProbabilityQ;
+    }
+
+    String getEncoderId() {
+        return mEncoderId;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("EncoderId: %s, ProbabilityF: %.3f, ProbabilityP: %.3f"
+                        + ", ProbabilityQ: %.3f",
+                mEncoderId, mProbabilityF, mProbabilityP, mProbabilityQ);
+    }
+}
diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java
new file mode 100644
index 0000000..219868d
--- /dev/null
+++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+package android.privacy.internal.longitudinalreporting;
+
+import android.privacy.DifferentialPrivacyEncoder;
+import android.privacy.internal.rappor.RapporConfig;
+import android.privacy.internal.rappor.RapporEncoder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Differential privacy encoder by using Longitudinal Reporting algorithm.
+ *
+ * <b>
+ * Notes: It supports encodeBoolean() only for now.
+ * </b>
+ *
+ * <p>
+ * Definition:
+ * PRR = Permanent Randomized Response
+ * IRR = Instantaneous Randomized response
+ *
+ * Algorithm:
+ * Step 1: Create long-term secrets x(ignoreOriginalInput)=Ber(P), y=Ber(Q), where Ber denotes
+ * Bernoulli distribution on {0, 1}, and we use it as a long-term secret, we implement Ber(x) by
+ * using PRR(2x, 0) when x < 1/2, PRR(2(1-x), 1) when x >= 1/2.
+ * Step 2: If x is 0, report IRR(F, original), otherwise report IRR(F, y)
+ * </p>
+ *
+ * Reference: go/bit-reporting-with-longitudinal-privacy
+ * TODO: Add a public blog / site to explain how it works.
+ *
+ * @hide
+ */
+public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder {
+
+    // Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some
+    // other Rappor encoder may re-use the same encoder id.
+    private static final String PRR1_ENCODER_ID = "prr1_encoder_id";
+    private static final String PRR2_ENCODER_ID = "prr2_encoder_id";
+
+    private final LongitudinalReportingConfig mConfig;
+
+    // IRR encoder to encode input value.
+    private final RapporEncoder mIRREncoder;
+
+    // A value that used to replace original value as input, so there's always a chance we are
+    // doing IRR on a fake value not actual original value.
+    // Null if original value does not need to be replaced.
+    private final Boolean mFakeValue;
+
+    // True if encoder is securely randomized.
+    private final boolean mIsSecure;
+
+    /**
+     * Create {@link LongitudinalReportingEncoder} with
+     * {@link LongitudinalReportingConfig} provided.
+     *
+     * @param config     Longitudinal Reporting parameters to encode input
+     * @param userSecret User generated secret that used to generate PRR
+     * @return {@link LongitudinalReportingEncoder} instance
+     */
+    public static LongitudinalReportingEncoder createEncoder(LongitudinalReportingConfig config,
+            byte[] userSecret) {
+        return new LongitudinalReportingEncoder(config, true, userSecret);
+    }
+
+    /**
+     * Create <strong>insecure</strong> {@link LongitudinalReportingEncoder} with
+     * {@link LongitudinalReportingConfig} provided.
+     * Should not use it to process sensitive data.
+     *
+     * @param config Rappor parameters to encode input.
+     * @return {@link LongitudinalReportingEncoder} instance.
+     */
+    @VisibleForTesting
+    public static LongitudinalReportingEncoder createInsecureEncoderForTest(
+            LongitudinalReportingConfig config) {
+        return new LongitudinalReportingEncoder(config, false, null);
+    }
+
+    private LongitudinalReportingEncoder(LongitudinalReportingConfig config,
+            boolean secureEncoder, byte[] userSecret) {
+        mConfig = config;
+        mIsSecure = secureEncoder;
+        final boolean ignoreOriginalInput = getLongTermRandomizedResult(config.getProbabilityP(),
+                secureEncoder, userSecret, config.getEncoderId() + PRR1_ENCODER_ID);
+
+        if (ignoreOriginalInput) {
+            mFakeValue = getLongTermRandomizedResult(config.getProbabilityQ(),
+                    secureEncoder, userSecret, config.getEncoderId() + PRR2_ENCODER_ID);
+        } else {
+            // Not using fake value, so IRR will be processed on real input value.
+            mFakeValue = null;
+        }
+
+        final RapporConfig irrConfig = config.getIRRConfig();
+        mIRREncoder = secureEncoder
+                ? RapporEncoder.createEncoder(irrConfig, userSecret)
+                : RapporEncoder.createInsecureEncoderForTest(irrConfig);
+    }
+
+    @Override
+    public byte[] encodeString(String original) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public byte[] encodeBoolean(boolean original) {
+        if (mFakeValue != null) {
+            // Use the fake value generated in PRR.
+            original = mFakeValue.booleanValue();
+        }
+        return mIRREncoder.encodeBoolean(original);
+    }
+
+    @Override
+    public byte[] encodeBits(byte[] bits) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public LongitudinalReportingConfig getConfig() {
+        return mConfig;
+    }
+
+    @Override
+    public boolean isInsecureEncoderForTest() {
+        return !mIsSecure;
+    }
+
+    /**
+     * Get PRR result that with probability p is 1, probability 1-p is 0.
+     */
+    @VisibleForTesting
+    public static boolean getLongTermRandomizedResult(double p, boolean secureEncoder,
+            byte[] userSecret, String encoderId) {
+        // Use Rappor to get PRR result. Rappor's P and Q are set to 0 and 1 so IRR will not be
+        // effective.
+        // As Rappor has rapporF/2 chance returns 0, rapporF/2 chance returns 1, and 1-rapporF
+        // chance returns original input.
+        // If p < 0.5, setting rapporF=2p and input=0 will make Rappor has p chance to return 1
+        // P(output=1 | input=0) = rapporF/2 = 2p/2 = p.
+        // If p >= 0.5, setting rapporF=2(1-p) and input=1 will make Rappor has p chance
+        // to return 1.
+        // P(output=1 | input=1) = rapporF/2 + (1 - rapporF) = 2(1-p)/2 + (1 - 2(1-p)) = p.
+        final double effectiveF = p < 0.5f ? p * 2 : (1 - p) * 2;
+        final boolean prrInput = p < 0.5f ? false : true;
+        final RapporConfig prrConfig = new RapporConfig(encoderId, 1, effectiveF,
+                0, 1, 1, 1);
+        final RapporEncoder encoder = secureEncoder
+                ? RapporEncoder.createEncoder(prrConfig, userSecret)
+                : RapporEncoder.createInsecureEncoderForTest(prrConfig);
+        return encoder.encodeBoolean(prrInput)[0] > 0;
+    }
+}
diff --git a/core/java/android/privacy/internal/rappor/RapporConfig.java b/core/java/android/privacy/internal/rappor/RapporConfig.java
new file mode 100644
index 0000000..221999b
--- /dev/null
+++ b/core/java/android/privacy/internal/rappor/RapporConfig.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package android.privacy.internal.rappor;
+
+import android.privacy.DifferentialPrivacyConfig;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A class to store {@link RapporEncoder} config.
+ *
+ * @hide
+ */
+public class RapporConfig implements DifferentialPrivacyConfig {
+
+    private static final String ALGORITHM_NAME = "Rappor";
+
+    final String mEncoderId;
+    final int mNumBits;
+    final double mProbabilityF;
+    final double mProbabilityP;
+    final double mProbabilityQ;
+    final int mNumCohorts;
+    final int mNumBloomHashes;
+
+    /**
+     * Constructor for {@link RapporConfig}.
+     *
+     * @param encoderId      Unique id for encoder.
+     * @param numBits        Number of bits to be encoded in Rappor algorithm.
+     * @param probabilityF   Probability F that used in Rappor algorithm. This will be
+     *                       quantized to the nearest 1/128.
+     * @param probabilityP   Probability P that used in Rappor algorithm.
+     * @param probabilityQ   Probability Q that used in Rappor algorithm.
+     * @param numCohorts     Number of cohorts that used in Rappor algorithm.
+     * @param numBloomHashes Number of bloom hashes that used in Rappor algorithm.
+     */
+    public RapporConfig(String encoderId, int numBits, double probabilityF,
+            double probabilityP, double probabilityQ, int numCohorts, int numBloomHashes) {
+        Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty");
+        this.mEncoderId = encoderId;
+        Preconditions.checkArgument(numBits > 0, "numBits needs to be > 0");
+        this.mNumBits = numBits;
+        Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1,
+                "probabilityF must be in range [0.0, 1.0]");
+        this.mProbabilityF = probabilityF;
+        Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1,
+                "probabilityP must be in range [0.0, 1.0]");
+        this.mProbabilityP = probabilityP;
+        Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1,
+                "probabilityQ must be in range [0.0, 1.0]");
+        this.mProbabilityQ = probabilityQ;
+        Preconditions.checkArgument(numCohorts > 0, "numCohorts needs to be > 0");
+        this.mNumCohorts = numCohorts;
+        Preconditions.checkArgument(numBloomHashes > 0, "numBloomHashes needs to be > 0");
+        this.mNumBloomHashes = numBloomHashes;
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return ALGORITHM_NAME;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "EncoderId: %s, NumBits: %d, ProbabilityF: %.3f, ProbabilityP: %.3f"
+                        + ", ProbabilityQ: %.3f, NumCohorts: %d, NumBloomHashes: %d",
+                mEncoderId, mNumBits, mProbabilityF, mProbabilityP, mProbabilityQ,
+                mNumCohorts, mNumBloomHashes);
+    }
+}
diff --git a/core/java/android/privacy/internal/rappor/RapporEncoder.java b/core/java/android/privacy/internal/rappor/RapporEncoder.java
new file mode 100644
index 0000000..2eca4c98
--- /dev/null
+++ b/core/java/android/privacy/internal/rappor/RapporEncoder.java
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+package android.privacy.internal.rappor;
+
+import android.privacy.DifferentialPrivacyEncoder;
+
+import com.google.android.rappor.Encoder;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Differential privacy encoder by using
+ * <a href="https://research.google.com/pubs/pub42852.html">RAPPOR</a>
+ * algorithm.
+ *
+ * @hide
+ */
+public class RapporEncoder implements DifferentialPrivacyEncoder {
+
+    // Hard-coded seed and secret for insecure encoder
+    private static final long INSECURE_RANDOM_SEED = 0x12345678L;
+    private static final byte[] INSECURE_SECRET = new byte[]{
+            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54
+    };
+    private static final SecureRandom sSecureRandom = new SecureRandom();
+
+    private final RapporConfig mConfig;
+
+    // Rappor encoder
+    private final Encoder mEncoder;
+    // True if encoder is secure (seed is securely randomized)
+    private final boolean mIsSecure;
+
+
+    private RapporEncoder(RapporConfig config, boolean secureEncoder, byte[] userSecret) {
+        mConfig = config;
+        mIsSecure = secureEncoder;
+        final Random random;
+        if (secureEncoder) {
+            // Use SecureRandom as random generator.
+            random = sSecureRandom;
+        } else {
+            // Hard-coded random generator, to have deterministic result.
+            random = new Random(INSECURE_RANDOM_SEED);
+            userSecret = INSECURE_SECRET;
+        }
+        mEncoder = new Encoder(random, null, null,
+                userSecret, config.mEncoderId, config.mNumBits,
+                config.mProbabilityF, config.mProbabilityP, config.mProbabilityQ,
+                config.mNumCohorts, config.mNumBloomHashes);
+    }
+
+    /**
+     * Create {@link RapporEncoder} with {@link RapporConfig} and user secret provided.
+     *
+     * @param config     Rappor parameters to encode input.
+     * @param userSecret Per device unique secret key.
+     * @return {@link RapporEncoder} instance.
+     */
+    public static RapporEncoder createEncoder(RapporConfig config, byte[] userSecret) {
+        return new RapporEncoder(config, true, userSecret);
+    }
+
+    /**
+     * Create <strong>insecure</strong> {@link RapporEncoder} with {@link RapporConfig} provided.
+     * Should not use it to process sensitive data.
+     *
+     * @param config Rappor parameters to encode input.
+     * @return {@link RapporEncoder} instance.
+     */
+    public static RapporEncoder createInsecureEncoderForTest(RapporConfig config) {
+        return new RapporEncoder(config, false, null);
+    }
+
+    @Override
+    public byte[] encodeString(String original) {
+        return mEncoder.encodeString(original);
+    }
+
+    @Override
+    public byte[] encodeBoolean(boolean original) {
+        return mEncoder.encodeBoolean(original);
+    }
+
+    @Override
+    public byte[] encodeBits(byte[] bits) {
+        return mEncoder.encodeBits(bits);
+    }
+
+    @Override
+    public RapporConfig getConfig() {
+        return mConfig;
+    }
+
+    @Override
+    public boolean isInsecureEncoderForTest() {
+        return !mIsSecure;
+    }
+}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 766ad84..60df467 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -212,16 +212,19 @@
         public static final String FEATURES = "features";
 
         /** Call had video. */
-        public static final int FEATURES_VIDEO = 0x1;
+        public static final int FEATURES_VIDEO = 1 << 0;
 
         /** Call was pulled externally. */
-        public static final int FEATURES_PULLED_EXTERNALLY = 0x2;
+        public static final int FEATURES_PULLED_EXTERNALLY = 1 << 1;
 
         /** Call was HD. */
-        public static final int FEATURES_HD_CALL = 0x4;
+        public static final int FEATURES_HD_CALL = 1 << 2;
 
         /** Call was WIFI call. */
-        public static final int FEATURES_WIFI = 0x8;
+        public static final int FEATURES_WIFI = 1 << 3;
+
+        /** Call was on RTT at some point */
+        public static final int FEATURES_RTT = 1 << 4;
 
         /**
          * Indicates the call underwent Assisted Dialing.
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 32d68cd..d9808a3 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -63,15 +63,6 @@
 
     private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
 
-   /**
-     * Broadcast Action:  A broadcast to indicate the end of an MTP session with the host.
-     * This broadcast is only sent if MTP activity has modified the media database during the
-     * most recent MTP session.
-     *
-     * @hide
-     */
-    public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
-
     /**
      * The method name used by the media scanner and mtp to tell the media provider to
      * rescan and reclassify that have become unhidden because of renaming folders or
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index abc16ce..c57eba2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5328,48 +5328,55 @@
         public static final String AUTOFILL_SERVICE = "autofill_service";
 
         /**
-         * Experimental autofill feature.
+         * Boolean indicating if Autofill supports field classification.
          *
-         * <p>TODO(b/67867469): document (or remove) once feature is finished
+         * @see android.service.autofill.AutofillService
+         *
          * @hide
          */
+        @SystemApi
         @TestApi
         public static final String AUTOFILL_FEATURE_FIELD_CLASSIFICATION =
                 "autofill_field_classification";
 
         /**
-         * Experimental autofill feature.
+         * Defines value returned by {@link android.service.autofill.UserData#getMaxUserDataSize()}.
          *
-         * <p>TODO(b/67867469): document (or remove) once feature is finished
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
                 "autofill_user_data_max_user_data_size";
 
         /**
-         * Experimental autofill feature.
+         * Defines value returned by
+         * {@link android.service.autofill.UserData#getMaxFieldClassificationIdsSize()}.
          *
-         * <p>TODO(b/67867469): document (or remove) once feature is finished
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
                 "autofill_user_data_max_field_classification_size";
 
         /**
-         * Experimental autofill feature.
+         * Defines value returned by {@link android.service.autofill.UserData#getMaxValueLength()}.
          *
-         * <p>TODO(b/67867469): document (or remove) once feature is finished
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
                 "autofill_user_data_max_value_length";
 
         /**
-         * Experimental autofill feature.
+         * Defines value returned by {@link android.service.autofill.UserData#getMinValueLength()}.
          *
-         * <p>TODO(b/67867469): document (or remove) once feature is finished
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
                 "autofill_user_data_min_value_length";
 
@@ -9463,6 +9470,7 @@
          * service_min_restart_time_between     (long)
          * service_max_inactivity               (long)
          * service_bg_start_timeout             (long)
+         * process_start_async                  (boolean)
          * </pre>
          *
          * <p>
@@ -9563,6 +9571,31 @@
         public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
 
         /**
+         * Battery tip specific settings
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true,"
+         * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * battery_tip_enabled              (boolean)
+         * summary_enabled                  (boolean)
+         * battery_saver_tip_enabled        (boolean)
+         * high_usage_enabled               (boolean)
+         * high_usage_app_count             (int)
+         * app_restriction_enabled          (boolean)
+         * reduced_battery_enabled          (boolean)
+         * reduced_battery_percent          (int)
+         * low_battery_enabled              (boolean)
+         * low_battery_hour                 (int)
+         * </pre>
+         * @hide
+         */
+        public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants";
+
+        /**
          * Always on display(AOD) specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -9584,9 +9617,10 @@
         /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
-         *
+         * <p>
          * "idle_duration=5000,parole_interval=4500"
-         *
+         * <p>
+         * All durations are in millis.
          * The following keys are supported:
          *
          * <pre>
@@ -9736,6 +9770,15 @@
         public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
 
         /**
+         * Whether or not App Standby feature is enabled. This controls throttling of apps
+         * based on usage patterns and predictions.
+         * Type: int (0 for false, 1 for true)
+         * Default: 1
+         * @hide
+         */
+        public static final java.lang.String APP_STANDBY_ENABLED = "app_standby_enabled";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
@@ -11090,6 +11133,7 @@
             INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES);
             INSTANT_APP_SETTINGS.add(WTF_IS_FATAL);
             INSTANT_APP_SETTINGS.add(SEND_ACTION_APP_ERROR);
+            INSTANT_APP_SETTINGS.add(ZEN_MODE);
         }
 
         /**
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 2205c41..978e60e 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
     /**
      * Creates instance of the class to to derive key using salted SHA256 hash.
      */
-    public KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+    public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
         return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
     }
 
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
index 1058463a..1674e51 100644
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
+++ b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.keystore.recoverablekeystore;
+package android.security.recoverablekeystore;
 
 /* @hide */
 parcelable KeyEntryRecoveryData;
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index 0510320..d8fb03f 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -18,38 +18,57 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.security.KeyStore;
+import android.util.AndroidException;
 
 import com.android.internal.widget.ILockSettings;
 
 import java.util.List;
+import java.util.Map;
 
 /**
- * A wrapper around KeyStore which lets key be exported to
- * trusted hardware on server side and recovered later.
+ * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
+ * recovered later.
  *
  * @hide
  */
-public class RecoverableKeyStoreLoader  {
+public class RecoverableKeyStoreLoader {
+
+    public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
+
+    public static final int NO_ERROR = KeyStore.NO_ERROR;
+    public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
+    public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+    /**
+     * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+     * can have its own number of user secret guesses allowed.
+     *
+     * @hide
+     */
+    public static final int RATE_LIMIT_EXCEEDED = 21;
+
+    /** Key has been successfully synced. */
+    public static final int RECOVERY_STATUS_SYNCED = 0;
+    /** Waiting for recovery agent to sync the key. */
+    public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+    /** Recovery account is not available. */
+    public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+    /** Key cannot be synced. */
+    public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
 
     private final ILockSettings mBinder;
 
-    // Exception codes, should be in sync with {@code KeyStoreException}.
-    public static final int SYSTEM_ERROR = 4;
-
-    public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
-
-    // Too many updates to recovery public key or server parameters.
-    public static final int RATE_LIMIT_EXCEEDED = 21;
-
     private RecoverableKeyStoreLoader(ILockSettings binder) {
         mBinder = binder;
     }
 
-    /**
-     * @hide
-     */
+    /** @hide */
     public static RecoverableKeyStoreLoader getInstance() {
         ILockSettings lockSettings =
                 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
@@ -57,29 +76,69 @@
     }
 
     /**
+     * Exceptions returned by {@link RecoverableKeyStoreLoader}.
+     *
      * @hide
      */
-    public static class RecoverableKeyStoreLoaderException extends Exception {
-        private final int mErrorCode;
+    public static class RecoverableKeyStoreLoaderException extends AndroidException {
+        private int mErrorCode;
 
-        public RecoverableKeyStoreLoaderException(int errorCode, String message) {
-            super(message);
-            mErrorCode = errorCode;
+        /**
+         * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
+         *
+         * @param errorCode
+         * @hide
+         */
+        public static RecoverableKeyStoreLoaderException fromErrorCode(int errorCode) {
+            return new RecoverableKeyStoreLoaderException(
+                    errorCode, getMessageFromErrorCode(errorCode));
         }
 
+        /**
+         * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link
+         * ServiceSpecificException}.
+         *
+         * @param e exception thrown on service side.
+         * @hide
+         */
+        static RecoverableKeyStoreLoaderException fromServiceSpecificException(
+                ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
+            throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode);
+        }
+
+        private RecoverableKeyStoreLoaderException(int errorCode, String message) {
+            super(message);
+        }
+
+        /** Returns errorCode. */
         public int getErrorCode() {
             return mErrorCode;
         }
+
+        /** @hide */
+        private static String getMessageFromErrorCode(int errorCode) {
+            switch (errorCode) {
+                case NO_ERROR:
+                    return "OK";
+                case SYSTEM_ERROR:
+                    return "System error";
+                case UNINITIALIZED_RECOVERY_PUBLIC_KEY:
+                    return "Recovery service is not initialized";
+                case RATE_LIMIT_EXCEEDED:
+                    return "Rate limit exceeded";
+                default:
+                    return String.valueOf("Unknown error code " + errorCode);
+            }
+        }
     }
 
     /**
      * Initializes key recovery service for the calling application. RecoverableKeyStoreLoader
-     * randomly chooses one of the keys from the list
-     * and keeps it to use for future key export operations. Collection of all keys
-     * in the list must be signed by the provided {@code rootCertificateAlias}, which must also be
-     * present in the list of root certificates preinstalled on the device. The random selection
-     * allows RecoverableKeyStoreLoader to select which of a set of remote recovery service
-     * devices will be used.
+     * randomly chooses one of the keys from the list and keeps it to use for future key export
+     * operations. Collection of all keys in the list must be signed by the provided {@code
+     * rootCertificateAlias}, which must also be present in the list of root certificates
+     * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select
+     * which of a set of remote recovery service devices will be used.
      *
      * <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between
      * consecutive initialization attempts, to limit the ability of an attacker to often switch
@@ -88,141 +147,319 @@
      * @param rootCertificateAlias alias of a root certificate preinstalled on the device
      * @param signedPublicKeyList binary blob a list of X509 certificates and signature
      * @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate
-     * limited.
+     *     limited.
      * @hide
      */
-    public void initRecoveryService(@NonNull String rootCertificateAlias,
-            @NonNull byte[] signedPublicKeyList)
+    public void initRecoveryService(
+            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
-        // TODO: extend widget/ILockSettings.aidl
-        /* try {
-            mBinder.initRecoveryService(rootCertificate, publicKeyList);
-        } catch (RemoteException  e) {
+        try {
+            mBinder.initRecoveryService(
+                    rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
-        } */
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
-     * Returns data necessary to store all recoverable keys for given account.
-     * Key material is encrypted with user secret and recovery public key.
+     * Returns data necessary to store all recoverable keys for given account. Key material is
+     * encrypted with user secret and recovery public key.
+     *
+     * @param account specific to Recovery agent.
+     * @return Data necessary to recover keystore.
+     * @hide
      */
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            KeyStoreRecoveryData recoveryData =
+                    mBinder.getRecoveryData(account, UserHandle.getCallingUserId());
+            return recoveryData;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+     * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+     * most one registered listener at any time.
+     *
+     * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+     *     {@code null}.
+     * @hide
+     */
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+     * version. Version zero is used, if no snapshots were created for the account.
+     *
+     * @return Map from recovery agent accounts to snapshot versions.
+     * @see KeyStoreRecoveryData#getSnapshotVersion
+     * @hide
+     */
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<byte[], Integer> result =
+                    (Map<byte[], Integer>)
+                            mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
      * Server parameters used to generate new recovery key blobs. This value will be included in
-     * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}.
-     * The same value must be included in vaultParams  {@link startRecoverySession}
+     * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
+     * in vaultParams {@link #startRecoverySession}
      *
+     * @param serverParameters included in recovery key blob.
      * @see #getRecoveryData
      * @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited.
+     * @hide
      */
-    public void updateServerParameters(long serverParameters)
+    public void setServerParameters(long serverParameters)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
-     * Updates recovery status for given keys.
-     * It is used to notify keystore that key was successfully stored on the server or
-     * there were an error. Returned as a part of KeyInfo data structure.
+     * Updates recovery status for given keys. It is used to notify keystore that key was
+     * successfully stored on the server or there were an error. Application can check this value
+     * using {@code getRecoveyStatus}.
      *
      * @param packageName Application whose recoverable keys' statuses are to be updated.
      * @param aliases List of application-specific key aliases. If the array is empty, updates the
-     * status for all existing recoverable keys.
+     *     status for all existing recoverable keys.
      * @param status Status specific to recovery agent.
      */
-    public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
-            int status) throws NameNotFoundException, RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+    public void setRecoveryStatus(
+            @NonNull String packageName, @Nullable String[] aliases, int status)
+            throws NameNotFoundException, RecoverableKeyStoreLoaderException {
+        try {
+            mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
-     * Specifies a set of secret types used for end-to-end keystore encryption.
-     * Knowing all of them is necessary to recover data.
+     * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+     * Negative status values are reserved for recovery agent specific codes. List of common codes:
      *
-     * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or
-     * {@link KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+     * <ul>
+     *   <li>{@link #RECOVERY_STATUS_SYNCED}
+     *   <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+     *   <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+     *   <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+     * </ul>
+     *
+     * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+     *     {@code null} caller's package will be used.
+     * @return {@code Map} from KeyStore alias to recovery status.
+     * @see #setRecoveryStatus
+     * @hide
      */
-    public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
-            int[] secretTypes) throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+    public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<String, Integer> result =
+                    (Map<String, Integer>)
+                            mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
-     * Defines a set of secret types used for end-to-end keystore encryption.
-     * Knowing all of them is necessary to generate KeyStoreRecoveryData.
+     * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
+     * is necessary to recover data.
+     *
+     * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link
+     *     KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+     */
+    public void setRecoverySecretTypes(
+            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
+     * necessary to generate KeyStoreRecoveryData.
+     *
+     * @return list of recovery secret types
      * @see KeyStoreRecoveryData
      */
     public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
      * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
-     * When user enters a secret of a pending type
-     * {@link #recoverySecretAvailable} should be called.
+     * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
+     * called.
+     *
+     * @return list of recovery secret types
      */
     public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
-     * Method notifies KeyStore that a user-generated secret is available.
-     * This method generates a symmetric session key which a trusted remote device can use
-     * to return a recovery key.
-     * Caller should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value
-     * in memory.
+     * Method notifies KeyStore that a user-generated secret is available. This method generates a
+     * symmetric session key which a trusted remote device can use to return a recovery key. Caller
+     * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in
+     * memory.
      *
-     * @param recoverySecret user generated secret together with parameters necessary to
-     * regenerate it on a new device.
+     * @param recoverySecret user generated secret together with parameters necessary to regenerate
+     *     it on a new device.
      */
     public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
      * Initializes recovery session and returns a blob with proof of recovery secrets possession.
-     * The method generates symmetric key for a session, which trusted remote device can use
-     * to return recovery key.
+     * The method generates symmetric key for a session, which trusted remote device can use to
+     * return recovery key.
      *
      * @param sessionId ID for recovery session.
-     * @param verifierPublicKey Certificate with Public key used to create the recovery blob on
-     * the source device. Keystore will verify the certificate using root of trust.
+     * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the
+     *     source device. Keystore will verify the certificate using root of trust.
      * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
-     * Used to limit number of guesses.
+     *     Used to limit number of guesses.
      * @param vaultChallenge Data passed from server for this recovery session and used to prevent
-     * replay attacks
+     *     replay attacks
      * @param secrets Secrets provided by user, the method only uses type and secret fields.
-     * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and
-     * contains a proof of user secrets, session symmetric key and parameters necessary to identify
-     * the counter with the number of failed recovery attempts.
+     * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
+     *     a proof of user secrets, session symmetric key and parameters necessary to identify the
+     *     counter with the number of failed recovery attempts.
      */
-    public @NonNull byte[] startRecoverySession(@NonNull String sessionId,
-            @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
-            @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets)
+    public @NonNull byte[] startRecoverySession(
+            @NonNull String sessionId,
+            @NonNull byte[] verifierPublicKey,
+            @NonNull byte[] vaultParams,
+            @NonNull byte[] vaultChallenge,
+            @NonNull List<KeyStoreRecoveryMetadata> secrets)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            byte[] recoveryClaim =
+                    mBinder.startRecoverySession(
+                            sessionId,
+                            verifierPublicKey,
+                            vaultParams,
+                            vaultChallenge,
+                            secrets,
+                            UserHandle.getCallingUserId());
+            return recoveryClaim;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 
     /**
      * Imports keys.
      *
-     * @param sessionId Id for recovery session, same as in = {@link startRecoverySession}.
+     * @param sessionId Id for recovery session, same as in
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)} on}.
      * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
      * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
-     * and session. KeyStore only uses package names from the application info in
-     * {@link KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+     *     and session. KeyStore only uses package names from the application info in {@link
+     *     KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+     * @return Map from alias to raw key material.
      */
-    public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
+    public Map<String, byte[]> recoverKeys(
+            @NonNull String sessionId,
+            @NonNull byte[] recoveryKeyBlob,
             @NonNull List<KeyEntryRecoveryData> applicationKeys)
             throws RecoverableKeyStoreLoaderException {
-        throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+        try {
+            return (Map<String, byte[]>) mBinder.recoverKeys(
+                    sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+     * raw material of the key.
+     *
+     * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
+     *     key.
+     */
+    public byte[] generateAndStoreKey(String alias) throws RecoverableKeyStoreLoaderException {
+        try {
+            return mBinder.generateAndStoreKey(alias);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
     }
 }
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 97fdcef..917efa8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,7 +438,7 @@
  *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
  *
  *  save(username, password);
- * </pre>
+ *  </pre>
  *
  * <a name="Privacy"></a>
  * <h3>Privacy</h3>
@@ -453,13 +453,46 @@
  * email address), the service should only use it locally (i.e., in the app's process) for
  * heuristics purposes, but it should not be sent to external servers.
  *
- * <a name="FieldsClassification"></a>
- * <h3>Metrics and fields classification</h3
+ * <a name="FieldClassification"></a>
+ * <h3>Metrics and field classification</h3
  *
- * <p>TODO(b/67867469): document it or remove this section; in particular, document the relationship
- * between set/getUserData(), FillResponse.setFieldClassificationIds(), and
- * FillEventHistory.getFieldsClassification.
+ * <p>The service can call {@link #getFillEventHistory()} to get metrics representing the user
+ * actions, and then use these metrics to improve its heuristics.
+ *
+ * <p>Prior to Android {@link android.os.Build.VERSION_CODES#P}, the metrics covered just the
+ * scenarios where the service knew how to autofill an activity, but Android
+ * {@link android.os.Build.VERSION_CODES#P} introduced a new mechanism called field classification,
+ * which allows the service to dinamically classify the meaning of fields based on the existing user
+ * data known by the service.
+ *
+ * <p>Typically, field classification can be used to detect fields that can be autofilled with
+ * user data that is not associated with a specific app&mdash;such as email and physical
+ * address. Once the service identifies that a such field was manually filled by the user, the
+ * service could use this signal to improve its heuristics, either locally (i.e., in the same
+ * device) or globally (i.e., by crowdsourcing the results back to the service's server so it can
+ * be used by other users).
+ *
+ * <p>The field classification workflow involves 4 steps:
+ *
+ * <ol>
+ *   <li>Set the user data through {@link AutofillManager#setUserData(UserData)}. This data is
+ *   cached until the system restarts (or the service is disabled), so it doesn't need to be set for
+ *   all requests.
+ *   <li>Identify which fields should be analysed by calling
+ *   {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)}.
+ *   <li>Verify the results through {@link FillEventHistory.Event#getFieldsClassification()}.
+ *   <li>Use the results to dynamically create {@link Dataset} or {@link SaveInfo} objects in future
+ *   requests.
+ * </ol>
+ *
+ * <p>The field classification is an expensive operation and should be used carefully, otherwise it
+ * can reach its rate limit and get blocked by the Android System. Ideally, it should be used just
+ * in cases where the service could not determine how an activity can be autofilled, but it has a
+ * strong suspicious that it could. For example, if an activity has four or more fields and one of
+ * them is a list, chances are that these are address fields (like address, city, state, and
+ * zip code).
  */
+// TODO(b/70407264): add code snippets above???
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
 
diff --git a/core/java/android/service/autofill/EditDistanceScorer.java b/core/java/android/service/autofill/EditDistanceScorer.java
index e25cd04..0706b37 100644
--- a/core/java/android/service/autofill/EditDistanceScorer.java
+++ b/core/java/android/service/autofill/EditDistanceScorer.java
@@ -16,7 +16,6 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.autofill.AutofillValue;
@@ -24,14 +23,8 @@
 /**
  * Helper used to calculate the classification score between an actual {@link AutofillValue} filled
  * by the user and the expected value predicted by an autofill service.
- *
- * TODO(b/67867469):
- * - improve javadoc
- * - document algorithm / copy from InternalScorer
- * - unhide / remove testApi
- * @hide
  */
-@TestApi
+// TODO(b/70291841): explain algorithm once it's fully implemented
 public final class EditDistanceScorer extends InternalScorer implements Scorer, Parcelable {
 
     private static final EditDistanceScorer sInstance = new EditDistanceScorer();
@@ -46,10 +39,11 @@
     private EditDistanceScorer() {
     }
 
+    /** @hide */
     @Override
     public float getScore(@NonNull AutofillValue actualValue, @NonNull String userData) {
         if (actualValue == null || !actualValue.isText() || userData == null) return 0;
-        // TODO(b/67867469): implement edit distance - currently it's returning either 0, 100%, or
+        // TODO(b/70291841): implement edit distance - currently it's returning either 0, 100%, or
         // partial match when number of chars match
         final String textValue = actualValue.getTextValue().toString();
         final int total = textValue.length();
diff --git a/core/java/android/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java
index 0a60208..001b291 100644
--- a/core/java/android/service/autofill/FieldClassification.java
+++ b/core/java/android/service/autofill/FieldClassification.java
@@ -19,38 +19,40 @@
 import static android.view.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.autofill.Helper;
 
 import com.android.internal.util.Preconditions;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
- * Gets the <a href="#FieldsClassification">fields classification</a> results for a given field.
- *
- * TODO(b/67867469):
- * - improve javadoc
- * - unhide / remove testApi
- *
- * @hide
+ * Represents the <a href="AutofillService.html#FieldClassification">field classification</a>
+ * results for a given field.
  */
-@TestApi
-public final class FieldClassification implements Parcelable {
+public final class FieldClassification {
 
-    private final Match mMatch;
+    private final ArrayList<Match> mMatches;
 
     /** @hide */
-    public FieldClassification(@NonNull Match match) {
-        mMatch = Preconditions.checkNotNull(match);
+    public FieldClassification(@NonNull ArrayList<Match> matches) {
+        mMatches = Preconditions.checkNotNull(matches);
+        Collections.sort(mMatches, new Comparator<Match>() {
+            @Override
+            public int compare(Match o1, Match o2) {
+                if (o1.mScore > o2.mScore) return -1;
+                if (o1.mScore < o2.mScore) return 1;
+                return 0;
+            }}
+        );
     }
 
     /**
-     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores}.
+     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in
+     * descending order).
      *
      * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact,
      * the Android System might return just the top match to minimize the impact of field
@@ -58,55 +60,56 @@
      */
     @NonNull
     public List<Match> getMatches() {
-        return Lists.newArrayList(mMatch);
+        return mMatches;
     }
 
     @Override
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return "FieldClassification: " + mMatch;
+        return "FieldClassification: " + mMatches;
     }
 
-    /////////////////////////////////////
-    // Parcelable "contract" methods. //
-    /////////////////////////////////////
-
-    @Override
-    public int describeContents() {
-        return 0;
+    private void writeToParcel(Parcel parcel) {
+        parcel.writeInt(mMatches.size());
+        for (int i = 0; i < mMatches.size(); i++) {
+            mMatches.get(i).writeToParcel(parcel);
+        }
     }
 
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeParcelable(mMatch, flags);
-    }
-
-    public static final Parcelable.Creator<FieldClassification> CREATOR =
-            new Parcelable.Creator<FieldClassification>() {
-
-        @Override
-        public FieldClassification createFromParcel(Parcel parcel) {
-            return new FieldClassification(parcel.readParcelable(null));
+    private static FieldClassification readFromParcel(Parcel parcel) {
+        final int size = parcel.readInt();
+        final ArrayList<Match> matches = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            matches.add(i, Match.readFromParcel(parcel));
         }
 
-        @Override
-        public FieldClassification[] newArray(int size) {
-            return new FieldClassification[size];
+        return new FieldClassification(matches);
+    }
+
+    static FieldClassification[] readArrayFromParcel(Parcel parcel) {
+        final int length = parcel.readInt();
+        final FieldClassification[] fcs = new FieldClassification[length];
+        for (int i = 0; i < length; i++) {
+            fcs[i] = readFromParcel(parcel);
         }
-    };
+        return fcs;
+    }
+
+    static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) {
+        parcel.writeInt(fcs.length);
+        for (int i = 0; i < fcs.length; i++) {
+            fcs[i].writeToParcel(parcel);
+        }
+    }
 
     /**
-     * Gets the score of a {@link UserData} entry for the field.
+     * Represents the score of a {@link UserData} entry for the field.
      *
-     * TODO(b/67867469):
-     * - improve javadoc
-     * - unhide / remove testApi
-     *
-     * @hide
+     * <p>The score is defined by {@link #getScore()} and the entry is identified by
+     * {@link #getRemoteId()}.
      */
-    @TestApi
-    public static final class Match implements Parcelable {
+    public static final class Match {
 
         private final String mRemoteId;
         private final float mScore;
@@ -126,26 +129,19 @@
         }
 
         /**
-         * Gets a score between the value of this field and the value of the {@link UserData} entry.
+         * Gets a classification score for the value of this field compared to the value of the
+         * {@link UserData} entry.
          *
-         * <p>The score is based in a case-insensitive comparisson of all characters from both the
-         * field value and the user data entry, and it ranges from {@code 0} to {@code 1000000}:
+         * <p>The score is based in a comparison of the field value and the user data entry, and it
+         * ranges from {@code 0.0F} to {@code 1.0F}:
          * <ul>
-         *   <li>{@code 1.0} represents a full match ({@code 100%}).
-         *   <li>{@code 0.0} represents a full mismatch ({@code 0%}).
+         *   <li>{@code 1.0F} represents a full match ({@code 100%}).
+         *   <li>{@code 0.0F} represents a full mismatch ({@code 0%}).
          *   <li>Any other value is a partial match.
          * </ul>
          *
-         * <p>How the score is calculated depends on the algorithm used by the Android System.
-         * For example, if the user  data is {@code "abc"} and the field value us {@code " abc"},
-         * the result could be:
-         * <ul>
-         *   <li>{@code 1.0} if the algorithm trims the values.
-         *   <li>{@code 0.0} if the algorithm compares the values sequentially.
-         *   <li>{@code 0.75} if the algorithm consideres that 3/4 (75%) of the characters match.
-         * </ul>
-         *
-         * <p>Currently, the autofill service cannot configure the algorithm.
+         * <p>How the score is calculated depends on the algorithm used by the {@link Scorer}
+         * implementation.
          */
         public float getScore() {
             return mScore;
@@ -160,33 +156,13 @@
             return string.append(", score=").append(mScore).toString();
         }
 
-        /////////////////////////////////////
-        // Parcelable "contract" methods. //
-        /////////////////////////////////////
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel parcel, int flags) {
+        private void writeToParcel(@NonNull Parcel parcel) {
             parcel.writeString(mRemoteId);
             parcel.writeFloat(mScore);
         }
 
-        @SuppressWarnings("hiding")
-        public static final Parcelable.Creator<Match> CREATOR = new Parcelable.Creator<Match>() {
-
-            @Override
-            public Match createFromParcel(Parcel parcel) {
-                return new Match(parcel.readString(), parcel.readFloat());
-            }
-
-            @Override
-            public Match[] newArray(int size) {
-                return new Match[size];
-            }
-        };
+        private static Match readFromParcel(@NonNull Parcel parcel) {
+            return new Match(parcel.readString(), parcel.readFloat());
+        }
     }
 }
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index a527f16..df62446 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -21,12 +21,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.autofill.FieldClassification.Match;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -158,7 +156,8 @@
                 final AutofillId[] detectedFields = event.mDetectedFieldIds;
                 parcel.writeParcelableArray(detectedFields, flags);
                 if (detectedFields != null) {
-                    parcel.writeParcelableArray(event.mDetectedMatches, flags);
+                    FieldClassification.writeArrayToParcel(parcel,
+                            event.mDetectedFieldClassifications);
                 }
             }
         }
@@ -208,6 +207,7 @@
          *       ({@link #getIgnoredDatasetIds()}).
          *   <li>Which fields in the selected datasets were changed by the user after the dataset
          *       was selected ({@link #getChangedFields()}.
+         *   <li>Which fields match the {@link UserData} set by the service.
          * </ul>
          *
          * <p><b>Note: </b>This event is only generated when:
@@ -222,7 +222,6 @@
          * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
          * contexts.
          */
-        // TODO(b/67867469): update with field detection behavior
         public static final int TYPE_CONTEXT_COMMITTED = 4;
 
         /** @hide */
@@ -252,7 +251,7 @@
         @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
 
         @Nullable private final AutofillId[] mDetectedFieldIds;
-        @Nullable private final Match[] mDetectedMatches;
+        @Nullable private final FieldClassification[] mDetectedFieldClassifications;
 
         /**
          * Returns the type of the event.
@@ -356,19 +355,13 @@
         }
 
         /**
-         * Gets the <a href="#FieldsClassification">fields classification</a> results.
+         * Gets the <a href="AutofillService.html#FieldClassification">field classification</a>
+         * results.
          *
          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
          * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
-         * fields classification}.
-         *
-         * TODO(b/67867469):
-         *  - improve javadoc
-         *  - unhide / remove testApi
-         *
-         * @hide
+         * field classification}.
          */
-        @TestApi
         @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() {
             if (mDetectedFieldIds == null) {
                 return Collections.emptyMap();
@@ -377,11 +370,11 @@
             final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size);
             for (int i = 0; i < size; i++) {
                 final AutofillId id = mDetectedFieldIds[i];
-                final Match match = mDetectedMatches[i];
+                final FieldClassification fc = mDetectedFieldClassifications[i];
                 if (sVerbose) {
-                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match);
+                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc);
                 }
-                map.put(id, new FieldClassification(match));
+                map.put(id, fc);
             }
             return map;
         }
@@ -462,6 +455,8 @@
          * and belonged to datasets.
          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
          * respective entry on {@code manuallyFilledFieldIds}.
+         * @param detectedFieldClassifications the field classification matches.
+         *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
          * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
@@ -469,7 +464,6 @@
          *
          * @hide
          */
-        // TODO(b/67867469): document field classification parameters once stable
         public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
                 @Nullable List<String> selectedDatasetIds,
                 @Nullable ArraySet<String> ignoredDatasetIds,
@@ -477,7 +471,8 @@
                 @Nullable ArrayList<String> changedDatasetIds,
                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-                @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMaches) {
+                @Nullable AutofillId[] detectedFieldIds,
+                @Nullable FieldClassification[] detectedFieldClassifications) {
             mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
@@ -502,7 +497,7 @@
             mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
 
             mDetectedFieldIds = detectedFieldIds;
-            mDetectedMatches = detectedMaches;
+            mDetectedFieldClassifications = detectedFieldClassifications;
         }
 
         @Override
@@ -516,7 +511,8 @@
                     + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
                     + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
                     + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
-                    + ", detectedMaches =" + Arrays.toString(mDetectedMatches)
+                    + ", detectedFieldClassifications ="
+                        + Arrays.toString(mDetectedFieldClassifications)
                     + "]";
         }
     }
@@ -554,15 +550,16 @@
                         }
                         final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
                                 AutofillId.class);
-                        final Match[] detectedMatches = (detectedFieldIds != null)
-                                ? parcel.readParcelableArray(null, Match.class)
+                        final FieldClassification[] detectedFieldClassifications =
+                                (detectedFieldIds != null)
+                                ? FieldClassification.readArrayFromParcel(parcel)
                                 : null;
 
                         selection.addEvent(new Event(eventType, datasetId, clientState,
                                 selectedDatasetIds, ignoredDatasets,
                                 changedFieldIds, changedDatasetIds,
                                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                                detectedFieldIds, detectedMatches));
+                                detectedFieldIds, detectedFieldClassifications));
                     }
                     return selection;
                 }
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 014d3e8..3a4b6bb 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -41,7 +41,7 @@
 import java.util.List;
 
 /**
- * Response for a {@link
+ * Response for an {@link
  * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
  *
  * <p>See the main {@link AutofillService} documentation for more details and examples.
@@ -156,6 +156,7 @@
     }
 
     /** @hide */
+    @TestApi
     public int getFlags() {
         return mFlags;
     }
@@ -352,22 +353,18 @@
         }
 
         /**
-         * Sets which fields are used for <a href="#FieldsClassification">fields classification</a>
+         * Sets which fields are used for
+         * <a href="AutofillService.html#FieldClassification">field classification</a>
          *
+         * <p><b>Note:</b> This method automatically adds the
+         * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} to the {@link #setFlags(int) flags}.
+
          * @throws IllegalArgumentException is length of {@code ids} args is more than
          * {@link UserData#getMaxFieldClassificationIdsSize()}.
          * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was
          * already called.
          * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
-         *
-         * TODO(b/67867469):
-         *  - improve javadoc: explain relationship with UserData and how to check results
-         *  - unhide / remove testApi
-         *  - implement multiple ids
-         *
-         * @hide
          */
-        @TestApi
         public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
             throwIfDestroyed();
             throwIfDisableAutofillCalled();
@@ -375,6 +372,7 @@
             Preconditions.checkArgumentInRange(ids.length, 1,
                     UserData.getMaxFieldClassificationIdsSize(), "ids length");
             mFieldClassificationIds = ids;
+            mFlags |= FLAG_TRACK_CONTEXT_COMMITED;
             return this;
         }
 
@@ -423,9 +421,8 @@
          * @throws IllegalStateException if either {@link #addDataset(Dataset)},
          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
          *       {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
-         *       {link #setFieldClassificationIds(AutofillId...)} was already called.
+         *       {@link #setFieldClassificationIds(AutofillId...)} was already called.
          */
-        // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
         public Builder disableAutofill(long duration) {
             throwIfDestroyed();
             if (duration <= 0) {
@@ -506,14 +503,13 @@
          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
          *       {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
          *       {@link #setClientState(Bundle)},
-         *       or {link #setFieldClassificationIds(AutofillId...)}.
+         *       or {@link #setFieldClassificationIds(AutofillId...)}.
          *   <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called
          *       without any previous calls to {@link #addDataset(Dataset)}.
          * </ol>
          *
          * @return A built response.
          */
-        // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
         public FillResponse build() {
             throwIfDestroyed();
             if (mAuthentication == null && mDatasets == null && mSaveInfo == null
diff --git a/core/java/android/service/autofill/Scorer.java b/core/java/android/service/autofill/Scorer.java
index f6a802a..c401855 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/java/android/service/autofill/Scorer.java
@@ -15,21 +15,14 @@
  */
 package android.service.autofill;
 
-import android.annotation.TestApi;
-
 /**
  * Helper class used to calculate a score.
  *
- * <p>Typically used to calculate the field classification score between an actual
- * {@link android.view.autofill.AutofillValue}  filled by the user and the expected value predicted
- * by an autofill service.
- *
- * TODO(b/67867469):
- * - improve javadoc
- * - unhide / remove testApi
- * @hide
+ * <p>Typically used to calculate the
+ * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
+ * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
+ * predicted by an autofill service.
  */
-@TestApi
 public interface Scorer {
 
 }
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
index 0d37815..f0cc360 100644
--- a/core/java/android/service/autofill/UserData.java
+++ b/core/java/android/service/autofill/UserData.java
@@ -23,7 +23,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.content.ContentResolver;
 import android.os.Parcel;
@@ -38,15 +37,9 @@
 import java.util.ArrayList;
 
 /**
- * Class used by service to improve autofillable fields detection by tracking the meaning of fields
- * manually edited by the user (when they match values provided by the service).
- *
- * TODO(b/67867469):
- *  - improve javadoc / add link to section on AutofillService
- *  - unhide / remove testApi
- * @hide
+ * Defines the user data used for
+ * <a href="AutofillService.html#FieldClassification">field classification</a>.
  */
-@TestApi
 public final class UserData implements Parcelable {
 
     private static final String TAG = "UserData";
@@ -110,12 +103,7 @@
 
     /**
      * A builder for {@link UserData} objects.
-     *
-     * TODO(b/67867469): unhide / remove testApi
-     *
-     * @hide
      */
-    @TestApi
     public static final class Builder {
         private final InternalScorer mScorer;
         private final ArrayList<String> mRemoteIds;
@@ -123,7 +111,7 @@
         private boolean mDestroyed;
 
         /**
-         * Creates a new builder for the user data used for <a href="#FieldsClassification">fields
+         * Creates a new builder for the user data used for <a href="#FieldClassification">field
          * classification</a>.
          *
          * @throws IllegalArgumentException if any of the following occurs:
@@ -288,14 +276,16 @@
     }
 
     /**
-     * Gets the minimum length of values passed to {@link Builder#Builder(Scorer, String, String)}.
+     * Gets the minimum length of values passed to the builder's constructor or
+     * or {@link Builder#add(String, String)}.
      */
     public static int getMinValueLength() {
         return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH);
     }
 
     /**
-     * Gets the maximum length of values passed to {@link Builder#Builder(Scorer, String, String)}.
+     * Gets the maximum length of values passed to the builder's constructor or
+     * or {@link Builder#add(String, String)}.
      */
     public static int getMaxValueLength() {
         return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH);
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index fb6f637..b8f2191 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -27,6 +27,7 @@
     void setDesiredSize(int width, int height);
     void setDisplayPadding(in Rect padding);
     void setVisibility(boolean visible);
+    void setInAmbientMode(boolean inAmbientDisplay);
     void dispatchPointer(in MotionEvent event);
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index e5ab3e1..595bfb7 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -102,6 +102,7 @@
     private static final int DO_DETACH = 20;
     private static final int DO_SET_DESIRED_SIZE = 30;
     private static final int DO_SET_DISPLAY_PADDING = 40;
+    private static final int DO_IN_AMBIENT_MODE = 50;
 
     private static final int MSG_UPDATE_SURFACE = 10000;
     private static final int MSG_VISIBILITY_CHANGED = 10010;
@@ -195,6 +196,7 @@
         float mPendingYOffsetStep;
         boolean mPendingSync;
         MotionEvent mPendingMove;
+        boolean mIsInAmbientMode;
 
         // Needed for throttling onComputeColors.
         private long mLastColorInvalidation;
@@ -431,6 +433,15 @@
         public boolean isPreview() {
             return mIWallpaperEngine.mIsPreview;
         }
+
+        /**
+         * Returns true if this engine is running in ambient mode -- that is,
+         * it is being shown in low power mode, in always on display.
+         * @hide
+         */
+        public boolean isInAmbientMode() {
+            return mIsInAmbientMode;
+        }
         
         /**
          * Control whether this wallpaper will receive raw touch events
@@ -549,6 +560,15 @@
         }
 
         /**
+         * Called when the device enters or exits ambient mode.
+         *
+         * @param inAmbientMode {@code true} if in ambient mode.
+         * @hide
+         */
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+        }
+
+        /**
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
@@ -631,6 +651,16 @@
             return null;
         }
 
+        /**
+         * Sets internal engine state. Only for testing.
+         * @param created {@code true} or {@code false}.
+         * @hide
+         */
+        @VisibleForTesting
+        public void setCreated(boolean created) {
+            mCreated = created;
+        }
+
         protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
             out.print(prefix); out.print("mInitializing="); out.print(mInitializing);
                     out.print(" mDestroyed="); out.println(mDestroyed);
@@ -683,7 +713,8 @@
                 }
                 Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
                 mCaller.sendMessage(msg);
-            } else {event.recycle();
+            } else {
+                event.recycle();
             }
         }
 
@@ -986,6 +1017,26 @@
             updateSurface(false, false, false);
         }
 
+        /**
+         * Executes life cycle event and updates internal ambient mode state based on
+         * message sent from handler.
+         *
+         * @param inAmbientMode True if in ambient mode.
+         * @hide
+         */
+        @VisibleForTesting
+        public void doAmbientModeChanged(boolean inAmbientMode) {
+            if (!mDestroyed) {
+                if (DEBUG) {
+                    Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + "): " + this);
+                }
+                mIsInAmbientMode = inAmbientMode;
+                if (mCreated) {
+                    onAmbientModeChanged(inAmbientMode);
+                }
+            }
+        }
+
         void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
             if (!mDestroyed) {
                 if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
@@ -1226,6 +1277,12 @@
             mCaller.sendMessage(msg);
         }
 
+        @Override
+        public void setInAmbientMode(boolean inAmbientDisplay) throws RemoteException {
+            Message msg = mCaller.obtainMessageI(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0);
+            mCaller.sendMessage(msg);
+        }
+
         public void dispatchPointer(MotionEvent event) {
             if (mEngine != null) {
                 mEngine.dispatchPointer(event);
@@ -1263,6 +1320,7 @@
             mCaller.sendMessage(msg);
         }
 
+        @Override
         public void executeMessage(Message message) {
             switch (message.what) {
                 case DO_ATTACH: {
@@ -1289,6 +1347,11 @@
                 }
                 case DO_SET_DISPLAY_PADDING: {
                     mEngine.doDisplayPaddingChanged((Rect) message.obj);
+                    return;
+                }
+                case DO_IN_AMBIENT_MODE: {
+                    mEngine.doAmbientModeChanged(message.arg1 != 0);
+                    return;
                 }
                 case MSG_UPDATE_SURFACE:
                     mEngine.updateSurface(true, false, false);
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index fba358c..6bca37a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -42,8 +42,7 @@
  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
  *  Canvas.drawText()} directly.</p>
  */
-public class DynamicLayout extends Layout
-{
+public class DynamicLayout extends Layout {
     private static final int PRIORITY = 128;
     private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
 
@@ -303,8 +302,9 @@
     }
 
     /**
-     * Make a layout for the specified text that will be updated as the text is changed.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -315,9 +315,9 @@
     }
 
     /**
-     * Make a layout for the transformed text (password transformation being the primary example of
-     * a transformation) that will be updated as the base text is changed.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -328,10 +328,9 @@
     }
 
     /**
-     * Make a layout for the transformed text (password transformation being the primary example of
-     * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
-     * the Layout will ellipsize the text down to ellipsizedWidth.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -351,7 +350,9 @@
      * the Layout will ellipsize the text down to ellipsizedWidth.
      *
      * @hide
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width,
@@ -492,7 +493,9 @@
         }
     }
 
-    private void reflow(CharSequence s, int where, int before, int after) {
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void reflow(CharSequence s, int where, int before, int after) {
         if (s != mBase)
             return;
 
@@ -805,8 +808,8 @@
             return;
         }
 
-        int firstBlock = -1;
-        int lastBlock = -1;
+        /*final*/ int firstBlock = -1;
+        /*final*/ int lastBlock = -1;
         for (int i = 0; i < mNumberOfBlocks; i++) {
             if (mBlockEndLines[i] >= startLine) {
                 firstBlock = i;
@@ -821,10 +824,10 @@
         }
         final int lastBlockEndLine = mBlockEndLines[lastBlock];
 
-        boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
+        final boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
                 mBlockEndLines[firstBlock - 1] + 1);
-        boolean createBlock = newLineCount > 0;
-        boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
+        final boolean createBlock = newLineCount > 0;
+        final boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
 
         int numAddedBlocks = 0;
         if (createBlockBefore) numAddedBlocks++;
@@ -863,12 +866,18 @@
 
         if (numAddedBlocks + numRemovedBlocks != 0 && mBlocksAlwaysNeedToBeRedrawn != null) {
             final ArraySet<Integer> set = new ArraySet<>();
+            final int changedBlockCount = numAddedBlocks - numRemovedBlocks;
             for (int i = 0; i < mBlocksAlwaysNeedToBeRedrawn.size(); i++) {
                 Integer block = mBlocksAlwaysNeedToBeRedrawn.valueAt(i);
-                if (block > firstBlock) {
-                    block += numAddedBlocks - numRemovedBlocks;
+                if (block < firstBlock) {
+                    // block index is before firstBlock add it since it did not change
+                    set.add(block);
                 }
-                set.add(block);
+                if (block > lastBlock) {
+                    // block index is after lastBlock, the index reduced to += changedBlockCount
+                    block += changedBlockCount;
+                    set.add(block);
+                }
             }
             mBlocksAlwaysNeedToBeRedrawn = set;
         }
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/java/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2e10fe8d..d69b119 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -454,6 +454,10 @@
         private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
+    /**
+     * @deprecated Use {@link Builder} instead.
+     */
+    @Deprecated
     public StaticLayout(CharSequence source, TextPaint paint,
                         int width,
                         Alignment align, float spacingmult, float spacingadd,
@@ -463,16 +467,9 @@
     }
 
     /**
-     * @hide
+     * @deprecated Use {@link Builder} instead.
      */
-    public StaticLayout(CharSequence source, TextPaint paint,
-            int width, Alignment align, TextDirectionHeuristic textDir,
-            float spacingmult, float spacingadd,
-            boolean includepad) {
-        this(source, 0, source.length(), paint, width, align, textDir,
-                spacingmult, spacingadd, includepad);
-    }
-
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
                         TextPaint paint, int outerwidth,
                         Alignment align,
@@ -483,17 +480,9 @@
     }
 
     /**
-     * @hide
+     * @deprecated Use {@link Builder} instead.
      */
-    public StaticLayout(CharSequence source, int bufstart, int bufend,
-            TextPaint paint, int outerwidth,
-            Alignment align, TextDirectionHeuristic textDir,
-            float spacingmult, float spacingadd,
-            boolean includepad) {
-        this(source, bufstart, bufend, paint, outerwidth, align, textDir,
-                spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
-}
-
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
             TextPaint paint, int outerwidth,
             Alignment align,
@@ -507,7 +496,9 @@
 
     /**
      * @hide
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
                         TextPaint paint, int outerwidth,
                         Alignment align, TextDirectionHeuristic textDir,
@@ -565,6 +556,9 @@
         Builder.recycle(b);
     }
 
+    /**
+     * Used by DynamicLayout.
+     */
     /* package */ StaticLayout(@Nullable CharSequence text) {
         super(text, null, 0, null, 0, 0);
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b7d33599..bfb5130 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -39,11 +39,12 @@
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("device_info_v2", "true");
         DEFAULT_FLAGS.put("new_settings_suggestion", "true");
-        DEFAULT_FLAGS.put("settings_search_v2", "false");
+        DEFAULT_FLAGS.put("settings_search_v2", "true");
         DEFAULT_FLAGS.put("settings_app_info_v2", "false");
         DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_v2", "false");
         DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
+        DEFAULT_FLAGS.put("settings_security_settings_v2", "false");
     }
 
     /**
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 4f76463..68d347c 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -117,7 +117,11 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Removes the mapping at the specified index.
+     * <p>
+     * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
+     */
     public void removeAt(int index) {
         System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
         System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
new file mode 100644
index 0000000..4805318
--- /dev/null
+++ b/core/java/android/util/StatsLog.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util;
+
+/**
+ * StatsLog provides an API for developers to send events to statsd. The events can be used to
+ * define custom metrics inside statsd. We will rate-limit how often the calls can be made inside
+ * statsd.
+ */
+public final class StatsLog extends StatsLogInternal {
+    private static final String TAG = "StatsManager";
+
+    private StatsLog() {}
+
+    /**
+     * Logs a start event.
+     *
+     * @param label developer-chosen label that is from [0, 16).
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStart(int label) {
+        if (label >= 0 && label < 16) {
+            StatsLog.write(APP_HOOK, label, APP_HOOK__STATE__START);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Logs a stop event.
+     *
+     * @param label developer-chosen label that is from [0, 16).
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStop(int label) {
+        if (label >= 0 && label < 16) {
+            StatsLog.write(APP_HOOK, label, APP_HOOK__STATE__STOP);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Logs an event that does not represent a start or stop boundary.
+     *
+     * @param label developer-chosen label that is from [0, 16).
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logEvent(int label) {
+        if (label >= 0 && label < 16) {
+            StatsLog.write(APP_HOOK, label, APP_HOOK__STATE__UNSPECIFIED);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index a74a882..0a54f3a 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -97,12 +97,30 @@
      */
     public static X509Certificate[][] verify(String apkFile)
             throws SignatureNotFoundException, SecurityException, IOException {
-        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
-            return verify(apk);
-        }
+        return verify(apkFile, true);
     }
 
     /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme V2
+     * Block while gathering signer information.  The APK contents are not verified.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, false);
+    }
+
+    private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk, verifyIntegrity);
+        }
+    }
+    /**
      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
      * associated with each signer.
      *
@@ -111,10 +129,10 @@
      *         verify.
      * @throws IOException if an I/O error occurs while reading the APK file.
      */
-    private static X509Certificate[][] verify(RandomAccessFile apk)
+    private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity)
             throws SignatureNotFoundException, SecurityException, IOException {
         SignatureInfo signatureInfo = findSignature(apk);
-        return verify(apk.getFD(), signatureInfo);
+        return verify(apk.getFD(), signatureInfo, verifyIntegrity);
     }
 
     /**
@@ -161,7 +179,8 @@
      */
     private static X509Certificate[][] verify(
             FileDescriptor apkFileDescriptor,
-            SignatureInfo signatureInfo) throws SecurityException {
+            SignatureInfo signatureInfo,
+            boolean doVerifyIntegrity) throws SecurityException {
         int signerCount = 0;
         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
         List<X509Certificate[]> signerCerts = new ArrayList<>();
@@ -198,13 +217,15 @@
             throw new SecurityException("No content digests found");
         }
 
-        verifyIntegrity(
-                contentDigests,
-                apkFileDescriptor,
-                signatureInfo.apkSigningBlockOffset,
-                signatureInfo.centralDirOffset,
-                signatureInfo.eocdOffset,
-                signatureInfo.eocd);
+        if (doVerifyIntegrity) {
+            verifyIntegrity(
+                    contentDigests,
+                    apkFileDescriptor,
+                    signatureInfo.apkSigningBlockOffset,
+                    signatureInfo.centralDirOffset,
+                    signatureInfo.eocdOffset,
+                    signatureInfo.eocd);
+        }
 
         return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
     }
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 73a9478..17b11a9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -58,51 +58,26 @@
     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
 
     /**
-     * Verifies the provided APK and returns the certificates associated with each signer.  Also
-     * ensures that the provided APK contains an AndroidManifest.xml file.
-     *
-     * @param systemDir systemDir apk contents are already trusted, so we don't need to enforce
-     *                  v2 stripping rollback protection, or verify integrity of the APK.
+     * Verifies the provided APK and returns the certificates associated with each signer.
      *
      * @throws PackageParserException if the APK's signature failed to verify.
-     * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater
-     * is not found, except in the case of no JAR signature.
      */
-    public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir)
-            throws PackageParserException, SignatureNotFoundException {
-        boolean verified = false;
-        Certificate[][] signerCerts;
-        int level = VERSION_APK_SIGNATURE_SCHEME_V2;
+    public static Result verify(String apkPath, int minSignatureSchemeVersion)
+            throws PackageParserException {
 
         // first try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
         try {
-            signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
+            Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
             Signature[] signerSigs = convertToSignatures(signerCerts);
 
-            // sanity check - must have an AndroidManifest file
-            StrictJarFile jarFile = null;
-            try {
-                jarFile = new StrictJarFile(apkPath, false, false);
-                final ZipEntry manifestEntry =
-                        jarFile.findEntry(PackageParser.ANDROID_MANIFEST_FILENAME);
-                if (manifestEntry == null) {
-                    throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                            "Package " + apkPath + " has no manifest");
-                }
-            } finally {
-                closeQuietly(jarFile);
-            }
-            return new Result(signerCerts, signerSigs);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
             if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
-                throw new SignatureNotFoundException(
-                        "No APK Signature Scheme v2 signature found for " + apkPath, e);
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v2 signature in package " + apkPath, e);
             }
-        } catch (PackageParserException e) {
-            // preserve any new exceptions explicitly thrown here
-            throw e;
         } catch (Exception e) {
             // APK Signature Scheme v2 signature found but did not verify
             throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -113,10 +88,17 @@
         }
 
         // v2 didn't work, try jarsigner
-        return verifyV1Signature(apkPath, systemDir);
+        return verifyV1Signature(apkPath, true);
     }
 
-    private static Result verifyV1Signature(String apkPath, boolean systemDir)
+    /**
+     * Verifies the provided APK and returns the certificates associated with each signer.
+     *
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     *
+     * @throws PackageParserException if there was a problem collecting certificates
+     */
+    private static Result verifyV1Signature(String apkPath, boolean verifyFull)
             throws PackageParserException {
         StrictJarFile jarFile = null;
 
@@ -125,10 +107,17 @@
             final Signature[] lastSigs;
 
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
-            jarFile = new StrictJarFile(apkPath, true, !systemDir);
+
+            // we still pass verify = true to ctor to collect certs, even though we're not checking
+            // the whole jar.
+            jarFile = new StrictJarFile(
+                    apkPath,
+                    true, // collect certs
+                    verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
             final List<ZipEntry> toVerify = new ArrayList<>();
 
-            // Always verify manifest, regardless of source
+            // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
+            // to not need to verify the whole APK when verifyFUll == false.
             final ZipEntry manifestEntry = jarFile.findEntry(
                     PackageParser.ANDROID_MANIFEST_FILENAME);
             if (manifestEntry == null) {
@@ -143,8 +132,8 @@
             }
             lastSigs = convertToSignatures(lastCerts);
 
-            // don't waste time on already-trusted packages
-            if (!systemDir) {
+            // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
+            if (verifyFull) {
                 final Iterator<ZipEntry> i = jarFile.iterator();
                 while (i.hasNext()) {
                     final ZipEntry entry = i.next();
@@ -157,9 +146,6 @@
                     toVerify.add(entry);
                 }
 
-                // Verify that entries are signed consistently with the first entry
-                // we encountered. Note that for splits, certificates may have
-                // already been populated during an earlier parse of a base APK.;
                 for (ZipEntry entry : toVerify) {
                     final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
                     if (ArrayUtils.isEmpty(entryCerts)) {
@@ -178,7 +164,7 @@
                     }
                 }
             }
-            return new Result(lastCerts, lastSigs);
+            return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME);
         } catch (GeneralSecurityException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                     "Failed to collect certificates from " + apkPath, e);
@@ -249,15 +235,54 @@
     }
 
     /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.
+     *
+     * @throws PackageParserException if the APK's signature failed to verify.
+     * or greater is not found, except in the case of no JAR signature.
+     */
+    public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
+            throws PackageParserException {
+
+        // first try v2
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
+        try {
+            Certificate[][] signerCerts =
+                    ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v2 signature in package " + apkPath, e);
+            }
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // v2 didn't work, try jarsigner
+        return verifyV1Signature(apkPath, false);
+    }
+
+    /**
      * Result of a successful APK verification operation.
      */
     public static class Result {
         public final Certificate[][] certs;
         public final Signature[] sigs;
+        public final int signatureSchemeVersion;
 
-        public Result(Certificate[][] certs, Signature[] sigs) {
+        public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
             this.certs = certs;
             this.sigs = sigs;
+            this.signatureSchemeVersion = signingVersion;
         }
     }
 }
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 19cd42e..e448f14 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -21,40 +21,37 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import android.annotation.NonNull;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Represents a part of the display that is not functional for displaying content.
  *
  * <p>{@code DisplayCutout} is immutable.
- *
- * @hide will become API
  */
 public final class DisplayCutout {
 
-    private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
-    private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+    private static final Rect ZERO_RECT = new Rect();
+    private static final Region EMPTY_REGION = new Region();
 
     /**
-     * An instance where {@link #hasCutout()} returns {@code false}.
+     * An instance where {@link #isEmpty()} returns {@code true}.
      *
      * @hide
      */
-    public static final DisplayCutout NO_CUTOUT =
-            new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+    public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION);
 
     private final Rect mSafeInsets;
-    private final Rect mBoundingRect;
-    private final List<Point> mBoundingPolygon;
+    private final Region mBounds;
 
     /**
      * Creates a DisplayCutout instance.
@@ -64,22 +61,18 @@
      * @hide
      */
     @VisibleForTesting
-    public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+    public DisplayCutout(Rect safeInsets, Region bounds) {
         mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
-        mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
-        mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+        mBounds = bounds != null ? bounds : Region.obtain();
     }
 
     /**
-     * Returns whether there is a cutout.
+     * Returns true if there is no cutout or it is outside of the content view.
      *
-     * If false, the safe insets will all return zero, and the bounding box or polygon will be
-     * empty or outside the content view.
-     *
-     * @return {@code true} if there is a cutout, {@code false} otherwise
+     * @hide
      */
-    public boolean hasCutout() {
-        return !mSafeInsets.equals(ZERO_RECT);
+    public boolean isEmpty() {
+        return mSafeInsets.equals(ZERO_RECT);
     }
 
     /** Returns the inset from the top which avoids the display cutout. */
@@ -103,44 +96,41 @@
     }
 
     /**
-     * Obtains the safe insets in a rect.
+     * Returns the safe insets in a rect.
      *
-     * @param out a rect which is set to the safe insets.
+     * @return a rect which is set to the safe insets.
      * @hide
      */
-    public void getSafeInsets(@NonNull Rect out) {
-        out.set(mSafeInsets);
+    public Rect getSafeInsets() {
+        return new Rect(mSafeInsets);
     }
 
     /**
-     * Obtains the bounding rect of the cutout.
+     * Returns the bounding region of the cutout.
      *
-     * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+     * @return the bounding region of the cutout. Coordinates are relative
      *         to the top-left corner of the content view.
      */
-    public void getBoundingRect(@NonNull Rect outRect) {
-        outRect.set(mBoundingRect);
+    public Region getBounds() {
+        return Region.obtain(mBounds);
     }
 
     /**
-     * Obtains the bounding polygon of the cutout.
+     * Returns the bounding rect of the cutout.
      *
-     * @param outPolygon is filled with a list of points representing the corners of a convex
-     *         polygon which covers the cutout. Coordinates are relative to the
-     *         top-left corner of the content view.
+     * @return the bounding rect of the cutout. Coordinates are relative
+     *         to the top-left corner of the content view.
+     * @hide
      */
-    public void getBoundingPolygon(List<Point> outPolygon) {
-        outPolygon.clear();
-        for (int i = 0; i < mBoundingPolygon.size(); i++) {
-            outPolygon.add(new Point(mBoundingPolygon.get(i)));
-        }
+    public Rect getBoundingRect() {
+        // TODO(roosa): Inline.
+        return mBounds.getBounds();
     }
 
     @Override
     public int hashCode() {
         int result = mSafeInsets.hashCode();
-        result = result * 31 + mBoundingRect.hashCode();
-        result = result * 31 + mBoundingPolygon.hashCode();
+        result = result * 31 + mBounds.getBounds().hashCode();
         return result;
     }
 
@@ -152,8 +142,7 @@
         if (o instanceof DisplayCutout) {
             DisplayCutout c = (DisplayCutout) o;
             return mSafeInsets.equals(c.mSafeInsets)
-                    && mBoundingRect.equals(c.mBoundingRect)
-                    && mBoundingPolygon.equals(c.mBoundingPolygon);
+                    && mBounds.equals(c.mBounds);
         }
         return false;
     }
@@ -161,7 +150,7 @@
     @Override
     public String toString() {
         return "DisplayCutout{insets=" + mSafeInsets
-                + " bounding=" + mBoundingRect
+                + " bounds=" + mBounds
                 + "}";
     }
 
@@ -172,15 +161,13 @@
      * @hide
      */
     public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
-        if (mBoundingRect.isEmpty()
+        if (mBounds.isEmpty()
                 || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
             return this;
         }
 
         Rect safeInsets = new Rect(mSafeInsets);
-        Rect boundingRect = new Rect(mBoundingRect);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        getBoundingPolygon(boundingPolygon);
+        Region bounds = Region.obtain(mBounds);
 
         // Note: it's not really well defined what happens when the inset is negative, because we
         // don't know if the safe inset needs to expand in general.
@@ -197,10 +184,9 @@
             safeInsets.right = atLeastZero(safeInsets.right - insetRight);
         }
 
-        boundingRect.offset(-insetLeft, -insetTop);
-        offset(boundingPolygon, -insetLeft, -insetTop);
+        bounds.translate(-insetLeft, -insetTop);
 
-        return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+        return new DisplayCutout(safeInsets, bounds);
     }
 
     /**
@@ -210,20 +196,17 @@
      * @hide
      */
     public DisplayCutout calculateRelativeTo(Rect frame) {
-        if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+        if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) {
             return NO_CUTOUT;
         }
 
-        Rect boundingRect = new Rect(mBoundingRect);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        getBoundingPolygon(boundingPolygon);
-
-        return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+        return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds));
     }
 
-    private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
-            ArrayList<Point> boundingPolygon) {
+    private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) {
+        Rect boundingRect = bounds.getBounds();
         Rect safeRect = new Rect();
+
         int bestArea = 0;
         int bestVariant = 0;
         for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
@@ -247,10 +230,9 @@
                     Math.max(0, frame.bottom - safeRect.bottom));
         }
 
-        boundingRect.offset(-frame.left, -frame.top);
-        offset(boundingPolygon, -frame.left, -frame.top);
+        bounds.translate(-frame.left, -frame.top);
 
-        return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+        return new DisplayCutout(safeRect, bounds);
     }
 
     private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
@@ -277,11 +259,6 @@
         return value < 0 ? 0 : value;
     }
 
-    private static void offset(ArrayList<Point> points, int dx, int dy) {
-        for (int i = 0; i < points.size(); i++) {
-            points.get(i).offset(dx, dy);
-        }
-    }
 
     /**
      * Creates an instance from a bounding polygon.
@@ -289,20 +266,28 @@
      * @hide
      */
     public static DisplayCutout fromBoundingPolygon(List<Point> points) {
-        Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
-                Integer.MIN_VALUE, Integer.MIN_VALUE);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        Region bounds = Region.obtain();
+        Path path = new Path();
 
+        path.reset();
         for (int i = 0; i < points.size(); i++) {
             Point point = points.get(i);
-            boundingRect.left = Math.min(boundingRect.left, point.x);
-            boundingRect.right = Math.max(boundingRect.right, point.x);
-            boundingRect.top = Math.min(boundingRect.top, point.y);
-            boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
-            boundingPolygon.add(new Point(point));
+            if (i == 0) {
+                path.moveTo(point.x, point.y);
+            } else {
+                path.lineTo(point.x, point.y);
+            }
         }
+        path.close();
 
-        return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+        RectF clipRect = new RectF();
+        path.computeBounds(clipRect, false /* unused */);
+        Region clipRegion = Region.obtain();
+        clipRegion.set((int) clipRect.left, (int) clipRect.top,
+                (int) clipRect.right, (int) clipRect.bottom);
+
+        bounds.setPath(path, clipRegion);
+        return new DisplayCutout(ZERO_RECT, bounds);
     }
 
     /**
@@ -336,8 +321,7 @@
             } else {
                 out.writeInt(1);
                 out.writeTypedObject(mInner.mSafeInsets, flags);
-                out.writeTypedObject(mInner.mBoundingRect, flags);
-                out.writeTypedList(mInner.mBoundingPolygon, flags);
+                out.writeTypedObject(mInner.mBounds, flags);
             }
         }
 
@@ -368,13 +352,10 @@
                 return NO_CUTOUT;
             }
 
-            ArrayList<Point> boundingPolygon = new ArrayList<>();
-
             Rect safeInsets = in.readTypedObject(Rect.CREATOR);
-            Rect boundingRect = in.readTypedObject(Rect.CREATOR);
-            in.readTypedList(boundingPolygon, Point.CREATOR);
+            Region bounds = in.readTypedObject(Region.CREATOR);
 
-            return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+            return new DisplayCutout(safeInsets, bounds);
         }
 
         public DisplayCutout get() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02beee0..cc63a62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26890,7 +26890,7 @@
         if (mAttachInfo == null || mTooltipInfo == null) {
             return false;
         }
-        if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+        if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
             return false;
         }
         if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
@@ -26938,7 +26938,7 @@
         }
         switch(event.getAction()) {
             case MotionEvent.ACTION_HOVER_MOVE:
-                if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+                if ((mViewFlags & TOOLTIP) != TOOLTIP) {
                     break;
                 }
                 if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3f8da093..6c5091c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -327,6 +327,8 @@
     // This is used to reduce the race between window focus changes being dispatched from
     // the window manager and input events coming through the input system.
     @GuardedBy("this")
+    boolean mWindowFocusChanged;
+    @GuardedBy("this")
     boolean mUpcomingWindowFocus;
     @GuardedBy("this")
     boolean mUpcomingInTouchMode;
@@ -1600,7 +1602,7 @@
         if (!layoutInCutout) {
             // Window is either not laid out in cutout or the status bar inset takes care of
             // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
-            insets = insets.consumeCutout();
+            insets = insets.consumeDisplayCutout();
         }
         host.dispatchApplyWindowInsets(insets);
     }
@@ -2472,14 +2474,14 @@
         final boolean hasWindowFocus;
         final boolean inTouchMode;
         synchronized (this) {
+            if (!mWindowFocusChanged) {
+                return;
+            }
+            mWindowFocusChanged = false;
             hasWindowFocus = mUpcomingWindowFocus;
             inTouchMode = mUpcomingInTouchMode;
         }
 
-        if (mAttachInfo.mHasWindowFocus == hasWindowFocus) {
-            return;
-        }
-
         if (mAdded) {
             profileRendering(hasWindowFocus);
 
@@ -7181,6 +7183,7 @@
 
     public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
         synchronized (this) {
+            mWindowFocusChanged = true;
             mUpcomingWindowFocus = hasFocus;
             mUpcomingInTouchMode = inTouchMode;
         }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index df124ac..e5cbe96 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,7 +17,7 @@
 
 package android.view;
 
-import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Rect;
 
 /**
@@ -49,7 +49,7 @@
     private boolean mSystemWindowInsetsConsumed = false;
     private boolean mWindowDecorInsetsConsumed = false;
     private boolean mStableInsetsConsumed = false;
-    private boolean mCutoutConsumed = false;
+    private boolean mDisplayCutoutConsumed = false;
 
     private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
 
@@ -80,8 +80,9 @@
         mIsRound = isRound;
         mAlwaysConsumeNavBar = alwaysConsumeNavBar;
 
-        mCutoutConsumed = displayCutout == null;
-        mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
+        mDisplayCutoutConsumed = displayCutout == null;
+        mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
+                ? null : displayCutout;
     }
 
     /**
@@ -99,7 +100,7 @@
         mIsRound = src.mIsRound;
         mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
         mDisplayCutout = src.mDisplayCutout;
-        mCutoutConsumed = src.mCutoutConsumed;
+        mDisplayCutoutConsumed = src.mDisplayCutoutConsumed;
     }
 
     /** @hide */
@@ -269,15 +270,16 @@
      */
     public boolean hasInsets() {
         return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
-                || mDisplayCutout.hasCutout();
+                || mDisplayCutout != null;
     }
 
     /**
-     * @return the display cutout
+     * Returns the display cutout if there is one.
+     *
+     * @return the display cutout or null if there is none
      * @see DisplayCutout
-     * @hide pending API
      */
-    @NonNull
+    @Nullable
     public DisplayCutout getDisplayCutout() {
         return mDisplayCutout;
     }
@@ -286,12 +288,11 @@
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
-     * @hide pending API
      */
-    public WindowInsets consumeCutout() {
+    public WindowInsets consumeDisplayCutout() {
         final WindowInsets result = new WindowInsets(this);
-        result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
-        result.mCutoutConsumed = true;
+        result.mDisplayCutout = null;
+        result.mDisplayCutoutConsumed = true;
         return result;
     }
 
@@ -311,7 +312,7 @@
      */
     public boolean isConsumed() {
         return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
-                && mCutoutConsumed;
+                && mDisplayCutoutConsumed;
     }
 
     /**
@@ -530,7 +531,7 @@
         return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
                 + " windowDecorInsets=" + mWindowDecorInsets
                 + " stableInsets=" + mStableInsets
-                + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+                + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
                 + (isRound() ? " round" : "")
                 + "}";
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 012e864..cbe012a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1286,7 +1286,6 @@
          * The window must correctly position its contents to take the display cutout into account.
          *
          * @see DisplayCutout
-         * @hide for now
          */
         public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
 
@@ -1294,7 +1293,6 @@
          * Various behavioral options/flags.  Default is none.
          *
          * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
-         * @hide for now
          */
         @Flags2 public long flags2;
 
@@ -2249,6 +2247,7 @@
             out.writeInt(y);
             out.writeInt(type);
             out.writeInt(flags);
+            out.writeLong(flags2);
             out.writeInt(privateFlags);
             out.writeInt(softInputMode);
             out.writeInt(gravity);
@@ -2304,6 +2303,7 @@
             y = in.readInt();
             type = in.readInt();
             flags = in.readInt();
+            flags2 = in.readLong();
             privateFlags = in.readInt();
             softInputMode = in.readInt();
             gravity = in.readInt();
@@ -2436,6 +2436,10 @@
                 flags = o.flags;
                 changes |= FLAGS_CHANGED;
             }
+            if (flags2 != o.flags2) {
+                flags2 = o.flags2;
+                changes |= FLAGS_CHANGED;
+            }
             if (privateFlags != o.privateFlags) {
                 privateFlags = o.privateFlags;
                 changes |= PRIVATE_FLAGS_CHANGED;
@@ -2689,6 +2693,11 @@
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
                     ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+            if (flags2 != 0) {
+                sb.append(System.lineSeparator());
+                // TODO(roosa): add a long overload for ViewDebug.flagsToString.
+                sb.append(prefix).append("  fl2=0x").append(Long.toHexString(flags2));
+            }
             if (privateFlags != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9c2f6bb..28ef697 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2325,7 +2325,7 @@
     /**
      * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
      * that {@code false} indicates that it is not explicitly marked, not that the node is not
-     * a focusable unit. Screen readers should generally used other signals, such as
+     * a focusable unit. Screen readers should generally use other signals, such as
      * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
      * focus.
      *
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index f11767d..ef1a3f3 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,7 @@
     private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
     private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+    private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
 
     // Housekeeping.
     private static final int MAX_POOL_SIZE = 10;
@@ -103,8 +104,7 @@
     private final Rect mBoundsInScreen = new Rect();
     private LongArray mChildIds;
     private CharSequence mTitle;
-    private int mAnchorId = UNDEFINED_WINDOW_ID;
-    private boolean mInPictureInPicture;
+    private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
 
     private int mConnectionId = UNDEFINED_WINDOW_ID;
 
@@ -202,7 +202,7 @@
      *
      * @hide
      */
-    public void setAnchorId(int anchorId) {
+    public void setAnchorId(long anchorId) {
         mAnchorId = anchorId;
     }
 
@@ -212,7 +212,8 @@
      * @return The anchor node, or {@code null} if none exists.
      */
     public AccessibilityNodeInfo getAnchor() {
-        if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+        if ((mConnectionId == UNDEFINED_WINDOW_ID)
+                || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
                 || (mParentId == UNDEFINED_WINDOW_ID)) {
             return null;
         }
@@ -224,17 +225,7 @@
 
     /** @hide */
     public void setPictureInPicture(boolean pictureInPicture) {
-        mInPictureInPicture = pictureInPicture;
-    }
-
-    /**
-     * Check if the window is in picture-in-picture mode.
-     *
-     * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
-     * @removed
-     */
-    public boolean inPictureInPicture() {
-        return isInPictureInPictureMode();
+        setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
     }
 
     /**
@@ -243,7 +234,7 @@
      * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
      */
     public boolean isInPictureInPictureMode() {
-        return mInPictureInPicture;
+        return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
     }
 
     /**
@@ -463,7 +454,6 @@
         infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
         infoClone.mTitle = info.mTitle;
         infoClone.mAnchorId = info.mAnchorId;
-        infoClone.mInPictureInPicture = info.mInPictureInPicture;
 
         if (info.mChildIds != null && info.mChildIds.size() > 0) {
             if (infoClone.mChildIds == null) {
@@ -520,8 +510,7 @@
         parcel.writeInt(mParentId);
         mBoundsInScreen.writeToParcel(parcel, flags);
         parcel.writeCharSequence(mTitle);
-        parcel.writeInt(mAnchorId);
-        parcel.writeInt(mInPictureInPicture ? 1 : 0);
+        parcel.writeLong(mAnchorId);
 
         final LongArray childIds = mChildIds;
         if (childIds == null) {
@@ -545,8 +534,7 @@
         mParentId = parcel.readInt();
         mBoundsInScreen.readFromParcel(parcel);
         mTitle = parcel.readCharSequence();
-        mAnchorId = parcel.readInt();
-        mInPictureInPicture = parcel.readInt() == 1;
+        mAnchorId = parcel.readLong();
 
         final int childCount = parcel.readInt();
         if (childCount > 0) {
@@ -593,7 +581,7 @@
         builder.append(", bounds=").append(mBoundsInScreen);
         builder.append(", focused=").append(isFocused());
         builder.append(", active=").append(isActive());
-        builder.append(", pictureInPicture=").append(inPictureInPicture());
+        builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
         if (DEBUG) {
             builder.append(", parent=").append(mParentId);
             builder.append(", children=[");
@@ -611,7 +599,8 @@
             builder.append(']');
         } else {
             builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
-            builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+            builder.append(", isAnchored=")
+                    .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
             builder.append(", hasChildren=").append(mChildIds != null
                     && mChildIds.size() > 0);
         }
@@ -633,8 +622,7 @@
             mChildIds.clear();
         }
         mConnectionId = UNDEFINED_WINDOW_ID;
-        mAnchorId = UNDEFINED_WINDOW_ID;
-        mInPictureInPicture = false;
+        mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
         mTitle = null;
     }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 6d84aa0..2697454 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,7 +24,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -531,10 +530,13 @@
      * @return whether autofill is enabled for the current user.
      */
     public boolean isEnabled() {
-        if (!hasAutofillFeature() || isDisabledByService()) {
+        if (!hasAutofillFeature()) {
             return false;
         }
         synchronized (mLock) {
+            if (isDisabledByServiceLocked()) {
+                return false;
+            }
             ensureServiceClientAddedIfNeededLocked();
             return mEnabled;
         }
@@ -606,19 +608,16 @@
     }
 
     private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
-        if (isDisabledByService()) {
+        if (isDisabledByServiceLocked()) {
             if (sVerbose) {
                 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
                         + ") on state " + getStateAsStringLocked());
             }
             return true;
         }
-        if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
-            if (sVerbose) {
-                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
-                        + ") on state " + getStateAsStringLocked());
-            }
-            return true;
+        if (sVerbose && isFinishedLocked()) {
+            Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                    + ") on state " + getStateAsStringLocked());
         }
         return false;
     }
@@ -1010,20 +1009,29 @@
     }
 
     /**
-     * Gets the user data used for <a href="#FieldsClassification">fields classification</a>.
+     * Returns the component name of the {@link AutofillService} that is enabled for the current
+     * user.
+     */
+    @Nullable
+    public ComponentName getAutofillServiceComponentName() {
+        if (mService == null) return null;
+
+        try {
+            return mService.getAutofillServiceComponentName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the user data used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
      *
      * <p><b>Note:</b> This method should only be called by an app providing an autofill service.
      *
-     * TODO(b/67867469):
-     *  - proper javadoc
-     *  - unhide / remove testApi
-     *
      * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
      * reset or if the caller currently does not have an enabled autofill service for the user.
-     *
-     * @hide
      */
-    @TestApi
     @Nullable public UserData getUserData() {
         try {
             return mService.getUserData();
@@ -1034,21 +1042,13 @@
     }
 
     /**
-     * Sets the user data used for <a href="#FieldsClassification">fields classification</a>.
+     * Sets the user data used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>
      *
      * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
      * and it's ignored if the caller currently doesn't have an enabled autofill service for
      * the user.
-     *
-     * TODO(b/67867469):
-     *  - proper javadoc
-     *  - unhide / remove testApi
-     *  - add unit tests:
-     *    - call set / get / verify
-     *
-     * @hide
      */
-    @TestApi
     public void setUserData(@Nullable UserData userData) {
         try {
             mService.setUserData(userData);
@@ -1058,13 +1058,17 @@
     }
 
     /**
-     * TODO(b/67867469):
-     * - proper javadoc
-     * - mention this method in other places
-     * - unhide / remove testApi
-     * @hide
+     * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is
+     * enabled.
+     *
+     * <p>As field classification is an expensive operation, it could be disabled, either
+     * temporarily (for example, because the service exceeded a rate-limit threshold) or
+     * permanently (for example, because the device is a low-level device).
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
      */
-    @TestApi
     public boolean isFieldClassificationEnabled() {
         try {
             return mService.isFieldClassificationEnabled();
@@ -1150,10 +1154,10 @@
             Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
                     + ", flags=" + flags + ", state=" + getStateAsStringLocked());
         }
-        if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+        if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
             if (sVerbose) {
                 Log.v(TAG, "not automatically starting session for " + id
-                        + " on state " + getStateAsStringLocked());
+                        + " on state " + getStateAsStringLocked() + " and flags " + flags);
             }
             return;
         }
@@ -1755,10 +1759,14 @@
         return mState == STATE_ACTIVE;
     }
 
-    private boolean isDisabledByService() {
+    private boolean isDisabledByServiceLocked() {
         return mState == STATE_DISABLED_BY_SERVICE;
     }
 
+    private boolean isFinishedLocked() {
+        return mState == STATE_FINISHED;
+    }
+
     private void post(Runnable runnable) {
         final AutofillClient client = getClient();
         if (client == null) {
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f49aa5b..38bb311 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -57,4 +57,5 @@
     UserData getUserData();
     void setUserData(in UserData userData);
     boolean isFieldClassificationEnabled();
+    ComponentName getAutofillServiceComponentName();
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9f033d7..80d7b6b7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -281,10 +281,10 @@
     boolean mActive = false;
 
     /**
-     * Set whenever this client becomes inactive, to know we need to reset
-     * state with the IME the next time we receive focus.
+     * {@code true} if next {@link #onPostWindowFocus(View, View, int, boolean, int)} needs to
+     * restart input.
      */
-    boolean mHasBeenInactive = true;
+    boolean mRestartOnNextWindowFocus = true;
 
     /**
      * As reported by IME through InputConnection.
@@ -489,7 +489,7 @@
                             // Some other client has starting using the IME, so note
                             // that this happened and make sure our own editor's
                             // state is reset.
-                            mHasBeenInactive = true;
+                            mRestartOnNextWindowFocus = true;
                             try {
                                 // Note that finishComposingText() is allowed to run
                                 // even when we are not active.
@@ -500,7 +500,7 @@
                         // Check focus again in case that "onWindowFocus" is called before
                         // handling this message.
                         if (mServedView != null && mServedView.hasWindowFocus()) {
-                            if (checkFocusNoStartInput(mHasBeenInactive)) {
+                            if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
                                 final int reason = active ?
                                         InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS :
                                         InputMethodClient.START_INPUT_REASON_DEACTIVATED_BY_IMMS;
@@ -1336,46 +1336,28 @@
                         windowFlags, tba, servedContext, missingMethodFlags,
                         view.getContext().getApplicationInfo().targetSdkVersion);
                 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
-                if (res != null) {
-                    if (res.id != null) {
-                        setInputChannelLocked(res.channel);
-                        mBindSequence = res.sequence;
-                        mCurMethod = res.method;
-                        mCurId = res.id;
-                        mNextUserActionNotificationSequenceNumber =
-                                res.userActionNotificationSequenceNumber;
-                    } else {
-                        if (res.channel != null && res.channel != mCurChannel) {
-                            res.channel.dispose();
-                        }
-                        if (mCurMethod == null) {
-                            // This means there is no input method available.
-                            if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
-                            return true;
-                        }
-                    }
-                } else {
-                    if (startInputReason
-                            == InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN) {
-                        // We are here probably because of an obsolete window-focus-in message sent
-                        // to windowGainingFocus.  Since IMMS determines whether a Window can have
-                        // IME focus or not by using the latest window focus state maintained in the
-                        // WMS, this kind of race condition cannot be avoided.  One obvious example
-                        // would be that we have already received a window-focus-out message but the
-                        // UI thread is still handling previous window-focus-in message here.
-                        // TODO: InputBindResult should have the error code.
-                        if (DEBUG) Log.w(TAG, "startInputOrWindowGainedFocus failed. "
-                                + "Window focus may have already been lost. "
-                                + "win=" + windowGainingFocus + " view=" + dumpViewInfo(view));
-                        if (!mActive) {
-                            // mHasBeenInactive is a latch switch to forcefully refresh IME focus
-                            // state when an inactive (mActive == false) client is gaining window
-                            // focus. In case we have unnecessary disable the latch due to this
-                            // spurious wakeup, we re-enable the latch here.
-                            // TODO: Come up with more robust solution.
-                            mHasBeenInactive = true;
-                        }
-                    }
+                if (res == null) {
+                    Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+                            + " null. startInputReason="
+                            + InputMethodClient.getStartInputReason(startInputReason)
+                            + " editorInfo=" + tba
+                            + " controlFlags=#" + Integer.toHexString(controlFlags));
+                    return false;
+                }
+                if (res.id != null) {
+                    setInputChannelLocked(res.channel);
+                    mBindSequence = res.sequence;
+                    mCurMethod = res.method;
+                    mCurId = res.id;
+                    mNextUserActionNotificationSequenceNumber =
+                            res.userActionNotificationSequenceNumber;
+                } else if (res.channel != null && res.channel != mCurChannel) {
+                    res.channel.dispose();
+                }
+                switch (res.result) {
+                    case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+                        mRestartOnNextWindowFocus = true;
+                        break;
                 }
                 if (mCurMethod != null && mCompletions != null) {
                     try {
@@ -1551,9 +1533,9 @@
                     + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
                     + " first=" + first + " flags=#"
                     + Integer.toHexString(windowFlags));
-            if (mHasBeenInactive) {
-                if (DEBUG) Log.v(TAG, "Has been inactive!  Starting fresh");
-                mHasBeenInactive = false;
+            if (mRestartOnNextWindowFocus) {
+                if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
+                mRestartOnNextWindowFocus = false;
                 forceNewFocus = true;
             }
             focusInLocked(focusedView != null ? focusedView : rootView);
@@ -2485,7 +2467,7 @@
         p.println("  mMainLooper=" + mMainLooper);
         p.println("  mIInputContext=" + mIInputContext);
         p.println("  mActive=" + mActive
-                + " mHasBeenInactive=" + mHasBeenInactive
+                + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus
                 + " mBindSequence=" + mBindSequence
                 + " mCurId=" + mCurId);
         p.println("  mFullscreenMode=" + mFullscreenMode);
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index fdc9f92..ed60430 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -16,17 +16,23 @@
 
 package android.view.textclassifier;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.annotation.WorkerThread;
 import android.os.LocaleList;
+import android.util.ArraySet;
 
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Interface for providing text classification related features.
@@ -58,6 +64,20 @@
     })
     @interface EntityType {}
 
+    /** Designates that the TextClassifier should identify all entity types it can. **/
+    int ENTITY_PRESET_ALL = 0;
+    /** Designates that the TextClassifier should identify no entities. **/
+    int ENTITY_PRESET_NONE = 1;
+    /** Designates that the TextClassifier should identify a base set of entities determined by the
+     * TextClassifier. **/
+    int ENTITY_PRESET_BASE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ENTITY_CONFIG_" },
+            value = {ENTITY_PRESET_ALL, ENTITY_PRESET_NONE, ENTITY_PRESET_BASE})
+    @interface EntityPreset {}
+
     /**
      * No-op TextClassifier.
      * This may be used to turn off TextClassifier features.
@@ -217,6 +237,8 @@
      * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
      * information.
      *
+     * If no options are supplied, default values will be used, determined by the TextClassifier.
+     *
      * @param text the text to generate annotations for
      * @param options configuration for link generation
      *
@@ -251,6 +273,16 @@
     }
 
     /**
+     * Returns a {@link Collection} of the entity types in the specified preset.
+     *
+     * @see #ENTITIES_ALL
+     * @see #ENTITIES_NONE
+     */
+    default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) {
+        return Collections.EMPTY_LIST;
+    }
+
+    /**
      * Logs a TextClassifier event.
      *
      * @param source the text classifier used to generate this event
@@ -268,6 +300,62 @@
         return TextClassifierConstants.DEFAULT;
     }
 
+    /**
+     * Configuration object for specifying what entities to identify.
+     *
+     * Configs are initially based on a predefined preset, and can be modified from there.
+     */
+    final class EntityConfig {
+        private final @TextClassifier.EntityPreset int mEntityPreset;
+        private final Collection<String> mExcludedEntityTypes;
+        private final Collection<String> mIncludedEntityTypes;
+
+        public EntityConfig(@TextClassifier.EntityPreset int mEntityPreset) {
+            this.mEntityPreset = mEntityPreset;
+            mExcludedEntityTypes = new ArraySet<>();
+            mIncludedEntityTypes = new ArraySet<>();
+        }
+
+        /**
+         * Specifies an entity to include in addition to any specified by the enity preset.
+         *
+         * Note that if an entity has been excluded, the exclusion will take precedence.
+         */
+        public EntityConfig includeEntities(String... entities) {
+            for (String entity : entities) {
+                mIncludedEntityTypes.add(entity);
+            }
+            return this;
+        }
+
+        /**
+         * Specifies an entity to be excluded.
+         */
+        public EntityConfig excludeEntities(String... entities) {
+            for (String entity : entities) {
+                mExcludedEntityTypes.add(entity);
+            }
+            return this;
+        }
+
+        /**
+         * Returns an unmodifiable list of the final set of entities to find.
+         */
+        public List<String> getEntities(TextClassifier textClassifier) {
+            ArrayList<String> entities = new ArrayList<>();
+            for (String entity : textClassifier.getEntitiesForPreset(mEntityPreset)) {
+                if (!mExcludedEntityTypes.contains(entity)) {
+                    entities.add(entity);
+                }
+            }
+            for (String entity : mIncludedEntityTypes) {
+                if (!mExcludedEntityTypes.contains(entity) && !entities.contains(entity)) {
+                    entities.add(entity);
+                }
+            }
+            return Collections.unmodifiableList(entities);
+        }
+    }
 
     /**
      * Utility functions for TextClassifier methods.
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index d7aaee7..aea3cb0 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -42,6 +42,9 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -66,6 +69,18 @@
     private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model";
     private static final String UPDATED_MODEL_FILE_PATH =
             "/data/misc/textclassifier/textclassifier.smartselection.model";
+    private static final List<String> ENTITY_TYPES_ALL =
+            Collections.unmodifiableList(Arrays.asList(
+                    TextClassifier.TYPE_ADDRESS,
+                    TextClassifier.TYPE_EMAIL,
+                    TextClassifier.TYPE_PHONE,
+                    TextClassifier.TYPE_URL));
+    private static final List<String> ENTITY_TYPES_BASE =
+            Collections.unmodifiableList(Arrays.asList(
+                    TextClassifier.TYPE_ADDRESS,
+                    TextClassifier.TYPE_EMAIL,
+                    TextClassifier.TYPE_PHONE,
+                    TextClassifier.TYPE_URL));
 
     private final Context mContext;
 
@@ -168,17 +183,23 @@
 
     @Override
     public TextLinks generateLinks(
-            @NonNull CharSequence text, @NonNull TextLinks.Options options) {
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
         Utils.validateInput(text);
         final String textString = text.toString();
         final TextLinks.Builder builder = new TextLinks.Builder(textString);
         try {
-            LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+            final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+            final Collection<String> entitiesToIdentify =
+                    options != null && options.getEntityConfig() != null
+                            ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL;
             final SmartSelection smartSelection = getSmartSelection(defaultLocales);
             final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
             for (SmartSelection.AnnotatedSpan span : annotations) {
-                final Map<String, Float> entityScores = new HashMap<>();
                 final SmartSelection.ClassificationResult[] results = span.getClassification();
+                if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) {
+                    continue;
+                }
+                final Map<String, Float> entityScores = new HashMap<>();
                 for (int i = 0; i < results.length; i++) {
                     entityScores.put(results[i].mCollection, results[i].mScore);
                 }
@@ -193,6 +214,20 @@
     }
 
     @Override
+    public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) {
+        switch (entityPreset) {
+            case TextClassifier.ENTITY_PRESET_NONE:
+                return Collections.emptyList();
+            case TextClassifier.ENTITY_PRESET_BASE:
+                return ENTITY_TYPES_BASE;
+            case TextClassifier.ENTITY_PRESET_ALL:
+                // fall through
+            default:
+                return ENTITY_TYPES_ALL;
+        }
+    }
+
+    @Override
     public void logEvent(String source, String event) {
         if (LOG_TAG.equals(source)) {
             mMetricsLogger.count(event, 1);
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 0e039e3..6c587cf 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -22,6 +22,8 @@
 import android.os.LocaleList;
 import android.text.SpannableString;
 import android.text.style.ClickableSpan;
+import android.view.View;
+import android.widget.TextView;
 
 import com.android.internal.util.Preconditions;
 
@@ -159,11 +161,12 @@
     public static final class Options {
 
         private LocaleList mDefaultLocales;
+        private TextClassifier.EntityConfig mEntityConfig;
 
         /**
-         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
-         *      the provided text. If no locale preferences exist, set this to null or an empty
-         *      locale list.
+         * @param defaultLocales ordered list of locale preferences that may be used to
+         *                       disambiguate the provided text. If no locale preferences exist,
+         *                       set this to null or an empty locale list.
          */
         public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
             mDefaultLocales = defaultLocales;
@@ -171,6 +174,17 @@
         }
 
         /**
+         * Sets the entity configuration to use. This determines what types of entities the
+         * TextClassifier will look for.
+         *
+         * @param entityConfig EntityConfig to use
+         */
+        public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
+            mEntityConfig = entityConfig;
+            return this;
+        }
+
+        /**
          * @return ordered list of locale preferences that can be used to disambiguate
          *      the provided text.
          */
@@ -178,6 +192,15 @@
         public LocaleList getDefaultLocales() {
             return mDefaultLocales;
         }
+
+        /**
+         * @return The config representing the set of entities to look for.
+         * @see #setEntityConfig(TextClassifier.EntityConfig)
+         */
+        @Nullable
+        public TextClassifier.EntityConfig getEntityConfig() {
+            return mEntityConfig;
+        }
     }
 
     /**
@@ -189,9 +212,14 @@
      * @hide
      */
     public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
-            textLink -> {
-                // TODO: Implement.
-                throw new UnsupportedOperationException("Not yet implemented");
+            textLink -> new ClickableSpan() {
+                @Override
+                public void onClick(View widget) {
+                    if (widget instanceof TextView) {
+                        final TextView textView = (TextView) widget;
+                        textView.requestActionMode(textLink);
+                    }
+                }
             };
 
     /**
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 63519a6..03ff0ca 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.P;
 
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index e985923..de5a822 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2081,6 +2081,48 @@
     }
 
     /**
+     * Define the directory used to store WebView data for the current process.
+     * The provided suffix will be used when constructing data and cache
+     * directory paths. If this API is not called, no suffix will be used.
+     * Each directory can be used by only one process in the application. If more
+     * than one process in an app wishes to use WebView, only one process can use
+     * the default directory, and other processes must call this API to define
+     * a unique suffix.
+     * <p>
+     * This API must be called before any instances of WebView are created in
+     * this process and before any other methods in the android.webkit package
+     * are called by this process.
+     *
+     * @param suffix The directory name suffix to be used for the current
+     *               process. Must not contain a path separator.
+     * @throws IllegalStateException if WebView has already been initialized
+     *                               in the current process.
+     * @throws IllegalArgumentException if the suffix contains a path separator.
+     */
+    public static void setDataDirectorySuffix(String suffix) {
+        WebViewFactory.setDataDirectorySuffix(suffix);
+    }
+
+    /**
+     * Indicate that the current process does not intend to use WebView, and
+     * that an exception should be thrown if a WebView is created or any other
+     * methods in the android.webkit package are used.
+     * <p>
+     * Applications with multiple processes may wish to call this in processes
+     * which are not intended to use WebView to prevent potential data directory
+     * conflicts (see {@link #setDataDirectorySuffix}) and to avoid accidentally
+     * incurring the memory usage of initializing WebView in long-lived
+     * processes which have no need for it.
+     *
+     * @throws IllegalStateException if WebView has already been initialized
+     *                               in the current process.
+     */
+    public static void disableWebView() {
+        WebViewFactory.disableWebView();
+    }
+
+
+    /**
      * @deprecated This was used for Gears, which has been deprecated.
      * @hide
      */
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 7339931..f067091 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -218,4 +218,11 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns the data directory suffix to use, or null for none.
+     */
+    public String getDataDirectorySuffix() {
+        return WebViewFactory.getDataDirectorySuffix();
+    }
 }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 9db0e8d..b3522ec 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -33,6 +33,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.io.File;
 import java.lang.reflect.Method;
 
 /**
@@ -46,7 +47,7 @@
     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
     /** @hide */
     private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1";
+            "com.android.webview.chromium.WebViewChromiumFactoryProviderForP";
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
@@ -63,6 +64,8 @@
     private static final Object sProviderLock = new Object();
     private static PackageInfo sPackageInfo;
     private static Boolean sWebViewSupported;
+    private static boolean sWebViewDisabled;
+    private static String sDataDirectorySuffix; // stored here so it can be set without loading WV
 
     // Error codes for loadWebViewNativeLibraryFromPackage
     public static final int LIBLOAD_SUCCESS = 0;
@@ -115,6 +118,45 @@
     /**
      * @hide
      */
+    static void disableWebView() {
+        synchronized (sProviderLock) {
+            if (sProviderInstance != null) {
+                throw new IllegalStateException(
+                        "Can't disable WebView: WebView already initialized");
+            }
+            sWebViewDisabled = true;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    static void setDataDirectorySuffix(String suffix) {
+        synchronized (sProviderLock) {
+            if (sProviderInstance != null) {
+                throw new IllegalStateException(
+                        "Can't set data directory suffix: WebView already initialized");
+            }
+            if (suffix.indexOf(File.separatorChar) >= 0) {
+                throw new IllegalArgumentException("Suffix " + suffix
+                                                   + " contains a path separator");
+            }
+            sDataDirectorySuffix = suffix;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    static String getDataDirectorySuffix() {
+        synchronized (sProviderLock) {
+            return sDataDirectorySuffix;
+        }
+    }
+
+    /**
+     * @hide
+     */
     public static String getWebViewLibrary(ApplicationInfo ai) {
         if (ai.metaData != null)
             return ai.metaData.getString("com.android.webview.WebViewLibrary");
@@ -204,6 +246,11 @@
                 throw new UnsupportedOperationException();
             }
 
+            if (sWebViewDisabled) {
+                throw new IllegalStateException(
+                        "WebView.disableWebView() was called: WebView is disabled");
+            }
+
             StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index de0b97d..eb2b6bc 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -123,10 +123,11 @@
                 throw new IllegalArgumentException(
                         "Native library paths to the WebView RelRo process must not be null!");
             }
-            int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
-                    RelroFileCreator.class.getName(), new String[] { nativeLib.path },
-                    "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
-            if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
+            boolean success = LocalServices.getService(ActivityManagerInternal.class)
+                    .startIsolatedProcess(
+                            RelroFileCreator.class.getName(), new String[] { nativeLib.path },
+                            "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
+            if (!success) throw new Exception("Failed to start the relro file creator process");
         } catch (Throwable t) {
             // Log and discard errors as we must not crash the system server.
             Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a440398..05d18d1 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -107,6 +107,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextLinks;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.TextView.Drawables;
 import android.widget.TextView.OnEditorActionListener;
@@ -174,6 +175,13 @@
         int SELECTION_END = 2;
     }
 
+    @IntDef({TextActionMode.SELECTION, TextActionMode.INSERTION, TextActionMode.TEXT_LINK})
+    @interface TextActionMode {
+        int SELECTION = 0;
+        int INSERTION = 1;
+        int TEXT_LINK = 2;
+    }
+
     // Each Editor manages its own undo stack.
     private final UndoManager mUndoManager = new UndoManager();
     private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -2053,7 +2061,7 @@
         stopTextActionMode();
 
         ActionMode.Callback actionModeCallback =
-                new TextActionModeCallback(false /* hasSelection */);
+                new TextActionModeCallback(TextActionMode.INSERTION);
         mTextActionMode = mTextView.startActionMode(
                 actionModeCallback, ActionMode.TYPE_FLOATING);
         if (mTextActionMode != null && getInsertionController() != null) {
@@ -2079,7 +2087,23 @@
      * Asynchronously starts a selection action mode using the TextClassifier.
      */
     void startSelectionActionModeAsync(boolean adjustSelection) {
-        getSelectionActionModeHelper().startActionModeAsync(adjustSelection);
+        getSelectionActionModeHelper().startSelectionActionModeAsync(adjustSelection);
+    }
+
+    void startLinkActionModeAsync(TextLinks.TextLink link) {
+        Preconditions.checkNotNull(link);
+        if (!(mTextView.getText() instanceof Spannable)) {
+            return;
+        }
+        Spannable text = (Spannable) mTextView.getText();
+        stopTextActionMode();
+        if (mTextView.isTextSelectable()) {
+            Selection.setSelection((Spannable) text, link.getStart(), link.getEnd());
+        } else {
+            //TODO: Nonselectable text
+        }
+
+        getSelectionActionModeHelper().startLinkActionModeAsync(link);
     }
 
     /**
@@ -2145,7 +2169,7 @@
         return true;
     }
 
-    boolean startSelectionActionModeInternal() {
+    boolean startActionModeInternal(@TextActionMode int actionMode) {
         if (extractedTextModeWillBeStarted()) {
             return false;
         }
@@ -2159,8 +2183,7 @@
             return false;
         }
 
-        ActionMode.Callback actionModeCallback =
-                new TextActionModeCallback(true /* hasSelection */);
+        ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
         mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
 
         final boolean selectionStarted = mTextActionMode != null;
@@ -3828,8 +3851,9 @@
         private final int mHandleHeight;
         private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
 
-        public TextActionModeCallback(boolean hasSelection) {
-            mHasSelection = hasSelection;
+        TextActionModeCallback(@TextActionMode int mode) {
+            mHasSelection = mode == TextActionMode.SELECTION
+                    || (mTextIsSelectable && mode == TextActionMode.TEXT_LINK);
             if (mHasSelection) {
                 SelectionModifierCursorController selectionController = getSelectionController();
                 if (selectionController.mStartHandle == null) {
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
new file mode 100644
index 0000000..8f0d02f
--- /dev/null
+++ b/core/java/android/widget/OWNERS
@@ -0,0 +1,12 @@
+per-file TextView.java = siyamed@google.com
+per-file TextView.java = nona@google.com
+per-file TextView.java = clarabayarri@google.com
+per-file TextView.java = toki@google.com
+per-file EditText.java = siyamed@google.com
+per-file EditText.java = nona@google.com
+per-file EditText.java = clarabayarri@google.com
+per-file EditText.java = toki@google.com
+per-file Editor.java = siyamed@google.com
+per-file Editor.java = nona@google.com
+per-file Editor.java = clarabayarri@google.com
+per-file Editor.java = toki@google.com
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index d0ad27a..2c6466c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -35,6 +35,7 @@
 import android.view.ActionMode;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
 import android.view.textclassifier.logging.SmartSelectionEventTracker;
 import android.view.textclassifier.logging.SmartSelectionEventTracker.SelectionEvent;
@@ -97,7 +98,10 @@
         }
     }
 
-    public void startActionModeAsync(boolean adjustSelection) {
+    /**
+     * Starts Selection ActionMode.
+     */
+    public void startSelectionActionModeAsync(boolean adjustSelection) {
         // Check if the smart selection should run for editable text.
         adjustSelection &= !mTextView.isTextEditable()
                 || mTextView.getTextClassifier().getSettings()
@@ -109,7 +113,7 @@
                 mTextView.getSelectionEnd());
         cancelAsyncTask();
         if (skipTextClassification()) {
-            startActionMode(null);
+            startSelectionActionMode(null);
         } else {
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
@@ -119,8 +123,27 @@
                             ? mTextClassificationHelper::suggestSelection
                             : mTextClassificationHelper::classifyText,
                     mSmartSelectSprite != null
-                            ? this::startActionModeWithSmartSelectAnimation
-                            : this::startActionMode)
+                            ? this::startSelectionActionModeWithSmartSelectAnimation
+                            : this::startSelectionActionMode)
+                    .execute();
+        }
+    }
+
+    /**
+     * Starts Link ActionMode.
+     */
+    public void startLinkActionModeAsync(TextLinks.TextLink textLink) {
+        //TODO: tracking/logging
+        cancelAsyncTask();
+        if (skipTextClassification()) {
+            startLinkActionMode(null);
+        } else {
+            resetTextClassificationHelper(textLink.getStart(), textLink.getEnd());
+            mTextClassificationAsyncTask = new TextClassificationAsyncTask(
+                    mTextView,
+                    mTextClassificationHelper.getTimeoutDuration(),
+                    mTextClassificationHelper::classifyText,
+                    this::startLinkActionMode)
                     .execute();
         }
     }
@@ -200,9 +223,19 @@
         return noOpTextClassifier || noSelection || password;
     }
 
-    private void startActionMode(@Nullable SelectionResult result) {
+    private void startLinkActionMode(@Nullable SelectionResult result) {
+        startActionMode(Editor.TextActionMode.TEXT_LINK, result);
+    }
+
+    private void startSelectionActionMode(@Nullable SelectionResult result) {
+        startActionMode(Editor.TextActionMode.SELECTION, result);
+    }
+
+    private void startActionMode(
+            @Editor.TextActionMode int actionMode, @Nullable SelectionResult result) {
         final CharSequence text = getText(mTextView);
-        if (result != null && text instanceof Spannable) {
+        if (result != null && text instanceof Spannable
+                && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
             // Do not change the selection if TextClassifier should be dark launched.
             if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
                 Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
@@ -211,12 +244,13 @@
         } else {
             mTextClassification = null;
         }
-        if (mEditor.startSelectionActionModeInternal()) {
+        if (mEditor.startActionModeInternal(actionMode)) {
             final SelectionModifierCursorController controller = mEditor.getSelectionController();
-            if (controller != null) {
+            if (controller != null
+                    && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
                 controller.show();
             }
-            if (result != null) {
+            if (result != null && actionMode == Editor.TextActionMode.SELECTION) {
                 mSelectionTracker.onSmartSelection(result);
             }
         }
@@ -224,10 +258,11 @@
         mTextClassificationAsyncTask = null;
     }
 
-    private void startActionModeWithSmartSelectAnimation(@Nullable SelectionResult result) {
+    private void startSelectionActionModeWithSmartSelectAnimation(
+            @Nullable SelectionResult result) {
         final Layout layout = mTextView.getLayout();
 
-        final Runnable onAnimationEndCallback = () -> startActionMode(result);
+        final Runnable onAnimationEndCallback = () -> startSelectionActionMode(result);
         // TODO do not trigger the animation if the change included only non-printable characters
         final boolean didSelectionChange =
                 result != null && (mTextView.getSelectionStart() != result.mStart
@@ -386,15 +421,24 @@
         mTextClassificationAsyncTask = null;
     }
 
-    private void resetTextClassificationHelper() {
+    private void resetTextClassificationHelper(int selectionStart, int selectionEnd) {
+        if (selectionStart < 0 || selectionEnd < 0) {
+            // Use selection indices
+            selectionStart = mTextView.getSelectionStart();
+            selectionEnd = mTextView.getSelectionEnd();
+        }
         mTextClassificationHelper.init(
                 mTextView.getContext(),
                 mTextView.getTextClassifier(),
                 getText(mTextView),
-                mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
+                selectionStart, selectionEnd,
                 mTextView.getTextLocales());
     }
 
+    private void resetTextClassificationHelper() {
+        resetTextClassificationHelper(-1, -1);
+    }
+
     private void cancelSmartSelectAnimation() {
         if (mSmartSelectSprite != null) {
             mSmartSelectSprite.cancelAnimation();
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 7156300..53318c9 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -20,6 +20,7 @@
 import static android.widget.RemoteViews.RemoteView;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -141,6 +142,9 @@
     private boolean mShowCurrentUserTime;
 
     private ContentObserver mFormatChangeObserver;
+    // Used by tests to stop time change events from triggering the text update
+    private boolean mStopTicking;
+
     private class FormatChangeObserver extends ContentObserver {
 
         public FormatChangeObserver(Handler handler) {
@@ -163,6 +167,9 @@
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            if (mStopTicking) {
+                return; // Test disabled the clock ticks
+            }
             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
                 final String timeZone = intent.getStringExtra("time-zone");
                 createTime(timeZone);
@@ -173,6 +180,9 @@
 
     private final Runnable mTicker = new Runnable() {
         public void run() {
+            if (mStopTicking) {
+                return; // Test disabled the clock ticks
+            }
             onTimeChanged();
 
             long now = SystemClock.uptimeMillis();
@@ -546,6 +556,15 @@
         }
     }
 
+    /**
+     * Used by tests to stop the clock tick from updating the text.
+     * @hide
+     */
+    @TestApi
+    public void disableClockTick() {
+        mStopTicking = true;
+    }
+
     private void registerReceiver() {
         final IntentFilter filter = new IntentFilter();
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 903d3ca..1e17f34 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -160,6 +160,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.view.textservice.SpellCheckerSubtype;
 import android.view.textservice.TextServicesManager;
 import android.widget.RemoteViews.RemoteView;
@@ -168,6 +169,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FastMath;
+import com.android.internal.util.Preconditions;
 import com.android.internal.widget.EditableInputConnection;
 
 import libcore.util.EmptyArray;
@@ -4865,6 +4867,10 @@
      * Sets line spacing for this TextView.  Each line other than the last line will have its height
      * multiplied by {@code mult} and have {@code add} added to it.
      *
+     * @param add The value in pixels that should be added to each line other than the last line.
+     *            This will be applied after the multiplier
+     * @param mult The value by which each line height other than the last line will be multiplied
+     *             by
      *
      * @attr ref android.R.styleable#TextView_lineSpacingExtra
      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
@@ -11151,6 +11157,20 @@
     }
 
     /**
+     * Starts an ActionMode for the specified TextLink.
+     *
+     * @return Whether or not we're attempting to start the action mode.
+     * @hide
+     */
+    public boolean requestActionMode(@NonNull TextLinks.TextLink link) {
+        Preconditions.checkNotNull(link);
+        if (mEditor != null) {
+            mEditor.startLinkActionModeAsync(link);
+            return true;
+        }
+        return false;
+    }
+    /**
      * @hide
      */
     protected void stopTextActionMode() {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index f405231..ab118ba 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -22,6 +22,7 @@
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.os.ParcelFileDescriptor;
 import android.os.WorkSource;
+import android.os.connectivity.CellularBatteryStats;
 import android.os.health.HealthStatsParceler;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -134,6 +135,9 @@
     void noteResetBleScan();
     void noteBleScanResults(in WorkSource ws, int numNewResults);
 
+    /** {@hide} */
+    CellularBatteryStats getCellularBatteryStats();
+
     HealthStatsParceler takeUidSnapshot(int uid);
     HealthStatsParceler[] takeUidSnapshots(in int[] uid);
 
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 46f47a3..d138241 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -199,7 +199,7 @@
                 text.setTextLocale(item.getLocale());
                 text.setContentDescription(item.getContentDescription(mCountryMode));
                 if (mCountryMode) {
-                    int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
+                    int layoutDir = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
                     //noinspection ResourceType
                     convertView.setLayoutDirection(layoutDir);
                     text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 8016a65..2eadaf3 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
-            UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget);
+            UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
         }
     }
 
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 6fb02b1..efc9c02 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -91,14 +91,14 @@
         STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         STATE_BACKUP,                   // ActivityManager.PROCESS_STATE_BACKUP
-        STATE_HEAVY_WEIGHT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         STATE_SERVICE,                  // ActivityManager.PROCESS_STATE_SERVICE
         STATE_RECEIVER,                 // ActivityManager.PROCESS_STATE_RECEIVER
+        STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        STATE_HEAVY_WEIGHT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         STATE_HOME,                     // ActivityManager.PROCESS_STATE_HOME
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 2ce7936..96ba2b0 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -81,10 +81,10 @@
     public static final int STATE_IMPORTANT_FOREGROUND = 2;
     public static final int STATE_IMPORTANT_BACKGROUND = 3;
     public static final int STATE_BACKUP = 4;
-    public static final int STATE_HEAVY_WEIGHT = 5;
-    public static final int STATE_SERVICE = 6;
-    public static final int STATE_SERVICE_RESTARTING = 7;
-    public static final int STATE_RECEIVER = 8;
+    public static final int STATE_SERVICE = 5;
+    public static final int STATE_SERVICE_RESTARTING = 6;
+    public static final int STATE_RECEIVER = 7;
+    public static final int STATE_HEAVY_WEIGHT = 8;
     public static final int STATE_HOME = 9;
     public static final int STATE_LAST_ACTIVITY = 10;
     public static final int STATE_CACHED_ACTIVITY = 11;
@@ -141,8 +141,8 @@
 
     public static final int[] NON_CACHED_PROC_STATES = new int[] {
             STATE_PERSISTENT, STATE_TOP, STATE_IMPORTANT_FOREGROUND,
-            STATE_IMPORTANT_BACKGROUND, STATE_BACKUP, STATE_HEAVY_WEIGHT,
-            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER
+            STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
+            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
     };
 
     public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -152,13 +152,13 @@
 
     public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
             STATE_TOP, STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
-            STATE_HEAVY_WEIGHT, STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
-            STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
+            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
+            STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
             STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
     };
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 22;
+    private static final int PARCEL_VERSION = 23;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535454;
 
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index d49d572..a075705 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -236,6 +236,7 @@
         moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true));
 
         if (!TextUtils.equals(docId, afterDocId)) {
+            scanFile(after);
             return afterDocId;
         } else {
             return null;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5d6d5bc..8966585 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -30,6 +30,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
+import android.os.connectivity.CellularBatteryStats;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBatteryPropertiesRegistrar;
@@ -44,6 +45,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -51,6 +53,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LogWriter;
 import android.util.LongSparseArray;
@@ -77,6 +80,7 @@
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
 
+import java.util.List;
 import libcore.util.EmptyArray;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -120,7 +124,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 170 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -188,6 +192,8 @@
     @VisibleForTesting
     protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
             new KernelUidCpuFreqTimeReader();
+    @VisibleForTesting
+    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
@@ -196,6 +202,18 @@
         return mKernelMemoryStats;
     }
 
+    @GuardedBy("this")
+    public boolean mPerProcStateCpuTimesAvailable = true;
+
+    /**
+     * Uids for which per-procstate cpu times need to be updated.
+     *
+     * Contains uid -> procState mappings.
+     */
+    @GuardedBy("this")
+    @VisibleForTesting
+    protected final SparseIntArray mPendingUids = new SparseIntArray();
+
     /** Container for Resource Power Manager stats. Updated by updateRpmStatsLocked. */
     private final RpmStats mTmpRpmStats = new RpmStats();
     /** The soonest the RPM stats can be updated after it was last updated. */
@@ -268,6 +286,138 @@
         }
     }
 
+    /**
+     * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+     */
+    public void updateProcStateCpuTimes() {
+        final SparseIntArray uidStates;
+        synchronized (BatteryStatsImpl.this) {
+            if(!initKernelSingleUidTimeReaderLocked()) {
+                return;
+            }
+
+            if (mPendingUids.size() == 0) {
+                return;
+            }
+            uidStates = mPendingUids.clone();
+            mPendingUids.clear();
+        }
+        for (int i = uidStates.size() - 1; i >= 0; --i) {
+            final int uid = uidStates.keyAt(i);
+            final int procState = uidStates.valueAt(i);
+            final int[] isolatedUids;
+            final Uid u;
+            final boolean onBattery;
+            synchronized (BatteryStatsImpl.this) {
+                // It's possible that uid no longer exists and any internal references have
+                // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
+                // creating an UidStats object if it doesn't already exist.
+                u = getAvailableUidStatsLocked(uid);
+                if (u == null) {
+                    continue;
+                }
+                if (u.mChildUids == null) {
+                    isolatedUids = null;
+                } else {
+                    isolatedUids = u.mChildUids.toArray();
+                    for (int j = isolatedUids.length - 1; j >= 0; --j) {
+                        isolatedUids[j] = u.mChildUids.get(j);
+                    }
+                }
+                onBattery = mOnBatteryInternal;
+            }
+            long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
+            if (isolatedUids != null) {
+                for (int j = isolatedUids.length - 1; j >= 0; --j) {
+                    cpuTimesMs = addCpuTimes(cpuTimesMs,
+                            mKernelSingleUidTimeReader.readDeltaMs(isolatedUids[j]));
+                }
+            }
+            if (onBattery && cpuTimesMs != null) {
+                synchronized (BatteryStatsImpl.this) {
+                    u.addProcStateTimesMs(procState, cpuTimesMs);
+                    u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+                }
+            }
+        }
+    }
+
+    /**
+     * When the battery/screen state changes, we don't attribute the cpu times to any process
+     * but we still need to snapshots of all uids to get correct deltas later on. Since we
+     * already read this data for updating per-freq cpu times, we can use the same data for
+     * per-procstate cpu times.
+     */
+    public void copyFromAllUidsCpuTimes() {
+        synchronized (BatteryStatsImpl.this) {
+            if(!initKernelSingleUidTimeReaderLocked()) {
+                return;
+            }
+
+            final SparseArray<long[]> allUidCpuFreqTimesMs =
+                    mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+            for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
+                final int uid = allUidCpuFreqTimesMs.keyAt(i);
+                final Uid u = getAvailableUidStatsLocked(mapUid(uid));
+                if (u == null) {
+                    continue;
+                }
+                final long[] cpuTimesMs = allUidCpuFreqTimesMs.valueAt(i);
+                if (cpuTimesMs == null) {
+                    continue;
+                }
+                final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
+                        uid, cpuTimesMs.clone());
+                if (mOnBatteryInternal && deltaTimesMs != null) {
+                    final int procState;
+                    final int idx = mPendingUids.indexOfKey(uid);
+                    if (idx >= 0) {
+                        procState = mPendingUids.valueAt(idx);
+                        mPendingUids.removeAt(idx);
+                    } else {
+                        procState = u.mProcessState;
+                    }
+                    if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
+                        u.addProcStateTimesMs(procState, deltaTimesMs);
+                        u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+                    }
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public long[] addCpuTimes(long[] timesA, long[] timesB) {
+        if (timesA != null && timesB != null) {
+            for (int i = timesA.length - 1; i >= 0; --i) {
+                timesA[i] += timesB[i];
+            }
+            return timesA;
+        }
+        return timesA == null ? (timesB == null ? null : timesB) : timesA;
+    }
+
+    @GuardedBy("this")
+    private boolean initKernelSingleUidTimeReaderLocked() {
+        if (mKernelSingleUidTimeReader == null) {
+            if (mPowerProfile == null) {
+                return false;
+            }
+            if (mCpuFreqs == null) {
+                mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+            }
+            if (mCpuFreqs != null) {
+                mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
+            } else {
+                mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+                return false;
+            }
+        }
+        mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+                && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
+        return true;
+    }
+
     public interface Clocks {
         public long elapsedRealtime();
         public long uptimeMillis();
@@ -293,9 +443,11 @@
 
         Future<?> scheduleSync(String reason, int flags);
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+        Future<?> scheduleReadProcStateCpuTimes();
+        Future<?> scheduleCopyFromAllUidsCpuTimes();
     }
 
-    public final MyHandler mHandler;
+    public Handler mHandler;
     private ExternalStatsSync mExternalSync = null;
     @VisibleForTesting
     protected UserInfoProvider mUserInfoProvider = null;
@@ -3623,6 +3775,7 @@
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
+            mExternalSync.scheduleCopyFromAllUidsCpuTimes();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
             if (updateOnBatteryTimeBase) {
@@ -3652,6 +3805,8 @@
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         mIsolatedUids.put(isolatedUid, appUid);
         StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
+        final Uid u = getUidStatsLocked(appUid);
+        u.addIsolatedUid(isolatedUid);
     }
 
     /**
@@ -3672,11 +3827,17 @@
      * @see #scheduleRemoveIsolatedUidLocked(int, int)
      */
     public void removeIsolatedUidLocked(int isolatedUid) {
-      StatsLog.write(
-          StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
-      mIsolatedUids.delete(isolatedUid);
-      mKernelUidCpuTimeReader.removeUid(isolatedUid);
-      mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+        StatsLog.write(
+            StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+        final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+        if (idx >= 0) {
+            final int ownerUid = mIsolatedUids.valueAt(idx);
+            final Uid u = getUidStatsLocked(ownerUid);
+            u.removeIsolatedUid(isolatedUid);
+            mIsolatedUids.removeAt(idx);
+        }
+        mKernelUidCpuTimeReader.removeUid(isolatedUid);
+        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
     }
 
     public int mapUid(int uid) {
@@ -3905,8 +4066,8 @@
     private String mInitialAcquireWakeName;
     private int mInitialAcquireWakeUid = -1;
 
-    public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
-            boolean unimportantForLogging, long elapsedRealtime, long uptime) {
+    public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+        int type, boolean unimportantForLogging, long elapsedRealtime, long uptime) {
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             // Only care about partial wake locks, since full wake locks
@@ -3954,12 +4115,21 @@
                 }
                 requestWakelockCpuUpdate();
             }
+
             getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
+
+            // TODO(statsd): Use the attribution chain specified in WorkChain instead of uid.
+            // The debug logging here can be deleted once statsd is wired up.
+            if (DEBUG) {
+                Slog.w(TAG, "StatsLog [start]: uid=" + uid + ", type=" + type + ", name=" + name
+                + ", wc=" + wc);
+            }
+            StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uid, type, name, 1);
         }
     }
 
-    public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type,
-            long elapsedRealtime, long uptime) {
+    public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+            int type, long elapsedRealtime, long uptime) {
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
@@ -3989,7 +4159,16 @@
                 }
                 requestWakelockCpuUpdate();
             }
+
             getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
+
+            // TODO(statsd): Use the attribution chain specified in WorkChain instead of uid.
+            // The debug logging here can be deleted once statsd is wired up.
+            if (DEBUG) {
+                Slog.w(TAG, "StatsLog [stop]: uid=" + uid + ", type=" + type + ", name=" + name
+                    + ", wc=" + wc);
+            }
+            StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uid, type, name, 0);
         }
     }
 
@@ -3999,8 +4178,17 @@
         final long uptime = mClocks.uptimeMillis();
         final int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
-                    elapsedRealtime, uptime);
+            noteStartWakeLocked(ws.get(i), pid, null, name, historyName, type,
+                    unimportantForLogging, elapsedRealtime, uptime);
+        }
+
+        List<WorkChain> wcs = ws.getWorkChains();
+        if (wcs != null) {
+            for (int i = 0; i < wcs.size(); ++i) {
+                final WorkChain wc = wcs.get(i);
+                noteStartWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+                        unimportantForLogging, elapsedRealtime, uptime);
+            }
         }
     }
 
@@ -4009,17 +4197,46 @@
             String newHistoryName, int newType, boolean newUnimportantForLogging) {
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
+
+        List<WorkChain>[] wcs = WorkSource.diffChains(ws, newWs);
+
         // For correct semantics, we start the need worksources first, so that we won't
         // make inappropriate history items as if all wake locks went away and new ones
         // appeared.  This is okay because tracking of wake locks allows nesting.
+        //
+        // First the starts :
         final int NN = newWs.size();
         for (int i=0; i<NN; i++) {
-            noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+            noteStartWakeLocked(newWs.get(i), newPid, null, newName, newHistoryName, newType,
                     newUnimportantForLogging, elapsedRealtime, uptime);
         }
+        if (wcs != null) {
+            List<WorkChain> newChains = wcs[0];
+            if (newChains != null) {
+                for (int i = 0; i < newChains.size(); ++i) {
+                    final WorkChain newChain = newChains.get(i);
+                    noteStartWakeLocked(newChain.getAttributionUid(), newPid, newChain, newName,
+                        newHistoryName, newType, newUnimportantForLogging, elapsedRealtime,
+                        uptime);
+                }
+            }
+        }
+
+        // Then the stops :
         final int NO = ws.size();
         for (int i=0; i<NO; i++) {
-            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+            noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+                    uptime);
+        }
+        if (wcs != null) {
+            List<WorkChain> goneChains = wcs[1];
+            if (goneChains != null) {
+                for (int i = 0; i < goneChains.size(); ++i) {
+                    final WorkChain goneChain = goneChains.get(i);
+                    noteStopWakeLocked(goneChain.getAttributionUid(), pid, goneChain, name,
+                            historyName, type, elapsedRealtime, uptime);
+                }
+            }
         }
     }
 
@@ -4029,7 +4246,17 @@
         final long uptime = mClocks.uptimeMillis();
         final int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+            noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+                    uptime);
+        }
+
+        List<WorkChain> wcs = ws.getWorkChains();
+        if (wcs != null) {
+            for (int i = 0; i < wcs.size(); ++i) {
+                final WorkChain wc = wcs.get(i);
+                noteStopWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+                        elapsedRealtime, uptime);
+            }
         }
     }
 
@@ -4273,10 +4500,10 @@
                 updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
                 // Fake a wake lock, so we consider the device waked as long as the screen is on.
-                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+                noteStartWakeLocked(-1, -1, null, "screen", null, WAKE_TYPE_PARTIAL, false,
                         elapsedRealtime, uptime);
             } else if (isScreenOn(oldState)) {
-                noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
+                noteStopWakeLocked(-1, -1, null, "screen", "screen", WAKE_TYPE_PARTIAL,
                         elapsedRealtime, uptime);
                 updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
@@ -5899,6 +6126,11 @@
         LongSamplingCounterArray mCpuFreqTimeMs;
         LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
 
+        LongSamplingCounterArray[] mProcStateTimeMs;
+        LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+        IntArray mChildUids;
+
         /**
          * The statistics we have collected for this uid's wake locks.
          */
@@ -5984,40 +6216,107 @@
             mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
         }
 
+        @VisibleForTesting
+        public void setProcessStateForTest(int procState) {
+            mProcessState = procState;
+        }
+
         @Override
         public long[] getCpuFreqTimes(int which) {
-            if (mCpuFreqTimeMs == null) {
+            return nullIfAllZeros(mCpuFreqTimeMs, which);
+        }
+
+        @Override
+        public long[] getScreenOffCpuFreqTimes(int which) {
+            return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
+        }
+
+        @Override
+        public long[] getCpuFreqTimes(int which, int procState) {
+            if (which < 0 || which >= NUM_PROCESS_STATE) {
                 return null;
             }
-            final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
-            if (cpuFreqTimes == null) {
+            if (mProcStateTimeMs == null) {
                 return null;
             }
-            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
-            for (int i = 0; i < cpuFreqTimes.length; ++i) {
-                if (cpuFreqTimes[i] != 0) {
-                    return cpuFreqTimes;
+            if (!mBsi.mPerProcStateCpuTimesAvailable) {
+                mProcStateTimeMs = null;
+                return null;
+            }
+            return nullIfAllZeros(mProcStateTimeMs[procState], which);
+        }
+
+        @Override
+        public long[] getScreenOffCpuFreqTimes(int which, int procState) {
+            if (which < 0 || which >= NUM_PROCESS_STATE) {
+                return null;
+            }
+            if (mProcStateScreenOffTimeMs == null) {
+                return null;
+            }
+            if (!mBsi.mPerProcStateCpuTimesAvailable) {
+                mProcStateScreenOffTimeMs = null;
+                return null;
+            }
+            return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which);
+        }
+
+        public void addIsolatedUid(int isolatedUid) {
+            if (mChildUids == null) {
+                mChildUids = new IntArray();
+            } else if (mChildUids.indexOf(isolatedUid) >= 0) {
+                return;
+            }
+            mChildUids.add(isolatedUid);
+        }
+
+        public void removeIsolatedUid(int isolatedUid) {
+            final int idx = mChildUids == null ? -1 : mChildUids.indexOf(isolatedUid);
+            if (idx < 0) {
+                return;
+            }
+            mChildUids.remove(idx);
+        }
+
+        private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
+            if (cpuTimesMs == null) {
+                return null;
+            }
+            final long[] counts = cpuTimesMs.getCountsLocked(which);
+            if (counts == null) {
+                return null;
+            }
+            // Return counts only if at least one of the elements is non-zero.
+            for (int i = counts.length - 1; i >= 0; --i) {
+                if (counts[i] != 0) {
+                    return counts;
                 }
             }
             return null;
         }
 
-        @Override
-        public long[] getScreenOffCpuFreqTimes(int which) {
-            if (mScreenOffCpuFreqTimeMs == null) {
-                return null;
+        private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+            if (mProcStateTimeMs == null) {
+                mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
-            final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
-            if (cpuFreqTimes == null) {
-                return null;
+            if (mProcStateTimeMs[procState] == null
+                    || mProcStateTimeMs[procState].getSize() != cpuTimesMs.length) {
+                mProcStateTimeMs[procState] = new LongSamplingCounterArray(
+                        mBsi.mOnBatteryTimeBase);
             }
-            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
-            for (int i = 0; i < cpuFreqTimes.length; ++i) {
-                if (cpuFreqTimes[i] != 0) {
-                    return cpuFreqTimes;
-                }
+            mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+        }
+
+        private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+            if (mProcStateScreenOffTimeMs == null) {
+                mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
-            return null;
+            if (mProcStateScreenOffTimeMs[procState] == null
+                    || mProcStateScreenOffTimeMs[procState].getSize() != cpuTimesMs.length) {
+                mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
+                        mBsi.mOnBatteryScreenOffTimeBase);
+            }
+            mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
         }
 
         @Override
@@ -6999,6 +7298,21 @@
                 mScreenOffCpuFreqTimeMs.reset(false);
             }
 
+            if (mProcStateTimeMs != null) {
+                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                    if (counters != null) {
+                        counters.reset(false);
+                    }
+                }
+            }
+            if (mProcStateScreenOffTimeMs != null) {
+                for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                    if (counters != null) {
+                        counters.reset(false);
+                    }
+                }
+            }
+
             resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
             resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
 
@@ -7189,6 +7503,20 @@
                     mScreenOffCpuFreqTimeMs.detach();
                 }
 
+                if (mProcStateTimeMs != null) {
+                    for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                        if (counters != null) {
+                            counters.detach();
+                        }
+                    }
+                }
+                if (mProcStateScreenOffTimeMs != null) {
+                    for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                        if (counters != null) {
+                            counters.detach();
+                        }
+                    }
+                }
                 detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
                 detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
             }
@@ -7449,6 +7777,22 @@
 
             LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
             LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+            if (mProcStateTimeMs != null) {
+                out.writeInt(mProcStateTimeMs.length);
+                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                    LongSamplingCounterArray.writeToParcel(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+            if (mProcStateScreenOffTimeMs != null) {
+                out.writeInt(mProcStateScreenOffTimeMs.length);
+                for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                    LongSamplingCounterArray.writeToParcel(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
 
             if (mMobileRadioApWakeupCount != null) {
                 out.writeInt(1);
@@ -7750,6 +8094,27 @@
             mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
                     in, mBsi.mOnBatteryScreenOffTimeBase);
 
+            int length = in.readInt();
+            if (length == NUM_PROCESS_STATE) {
+                mProcStateTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    mProcStateTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+                            in, mBsi.mOnBatteryTimeBase);
+                }
+            } else {
+                mProcStateTimeMs = null;
+            }
+            length = in.readInt();
+            if (length == NUM_PROCESS_STATE) {
+                mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    mProcStateScreenOffTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+                            in, mBsi.mOnBatteryScreenOffTimeBase);
+                }
+            } else {
+                mProcStateScreenOffTimeMs = null;
+            }
+
             if (in.readInt() != 0) {
                 mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
             } else {
@@ -8671,23 +9036,7 @@
             // Make special note of Foreground Services
             final boolean userAwareService =
                     (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
-            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
-                uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-            } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
-                uidRunningState = PROCESS_STATE_TOP;
-            } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                // Persistent and other foreground states go here.
-                uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
-            } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
-                uidRunningState = PROCESS_STATE_TOP_SLEEPING;
-            } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                // Persistent and other foreground states go here.
-                uidRunningState = PROCESS_STATE_FOREGROUND;
-            } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
-                uidRunningState = PROCESS_STATE_BACKGROUND;
-            } else {
-                uidRunningState = PROCESS_STATE_CACHED;
-            }
+            uidRunningState = BatteryStats.mapToInternalProcessState(procState);
 
             if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
                 return;
@@ -8699,6 +9048,18 @@
 
                 if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+
+                    if (mBsi.mPerProcStateCpuTimesAvailable) {
+                        if (mBsi.mPendingUids.size() == 0) {
+                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+                        }
+                        if (mBsi.mPendingUids.indexOfKey(mUid) < 0
+                                || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
+                            mBsi.mPendingUids.put(mUid, mProcessState);
+                        }
+                    } else {
+                        mBsi.mPendingUids.clear();
+                    }
                 }
                 mProcessState = uidRunningState;
                 if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
@@ -8934,8 +9295,6 @@
             Wakelock wl = mWakelockStats.startObject(name);
             if (wl != null) {
                 getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
-                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
@@ -8954,8 +9313,6 @@
                 StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
                 wlt.stopRunningLocked(elapsedRealtimeMs);
                 if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
                 }
             }
             if (type == WAKE_TYPE_PARTIAL) {
@@ -10938,6 +11295,7 @@
         final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
         final int numClusters = mPowerProfile.getNumCpuClusters();
         mWakeLockAllocationsUs = null;
+        final long startTimeMs = mClocks.uptimeMillis();
         mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
@@ -11003,6 +11361,11 @@
             }
         });
 
+        final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
+        if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
+            Slog.d(TAG, "Reading cpu freq times took " + elapsedTimeMs + "ms");
+        }
+
         if (mWakeLockAllocationsUs != null) {
             for (int i = 0; i < numWakelocks; ++i) {
                 final Uid u = partialTimers.get(i).mUid;
@@ -11591,6 +11954,51 @@
         return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
     }
 
+    /*@hide */
+    public CellularBatteryStats getCellularBatteryStats() {
+        CellularBatteryStats s = new CellularBatteryStats();
+        final int which = STATS_SINCE_CHARGED;
+        final long rawRealTime = SystemClock.elapsedRealtime() * 1000;
+        final ControllerActivityCounter counter = getModemControllerActivity();
+        final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
+        final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
+        final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
+        long[] timeInRatMs = new long[BatteryStats.NUM_DATA_CONNECTION_TYPES];
+        for (int i = 0; i < timeInRatMs.length; i++) {
+           timeInRatMs[i] = getPhoneDataConnectionTime(i, rawRealTime, which) / 1000;
+        }
+        long[] timeInRxSignalStrengthLevelMs = new long[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+        for (int i = 0; i < timeInRxSignalStrengthLevelMs.length; i++) {
+           timeInRxSignalStrengthLevelMs[i]
+               = getPhoneSignalStrengthTime(i, rawRealTime, which) / 1000;
+        }
+        long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS,
+            counter.getTxTimeCounters().length)];
+        long totalTxTimeMs = 0;
+        for (int i = 0; i < txTimeMs.length; i++) {
+            txTimeMs[i] = counter.getTxTimeCounters()[i].getCountLocked(which);
+            totalTxTimeMs += txTimeMs[i];
+        }
+        final long totalControllerActivityTimeMs
+            = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
+        final long sleepTimeMs
+            = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
+        s.setLoggingDurationMs(computeBatteryRealtime(rawRealTime, which) / 1000);
+        s.setKernelActiveTimeMs(getMobileRadioActiveTime(rawRealTime, which) / 1000);
+        s.setNumPacketsTx(getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+        s.setNumBytesTx(getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+        s.setNumPacketsRx(getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+        s.setNumBytesRx(getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+        s.setSleepTimeMs(sleepTimeMs);
+        s.setIdleTimeMs(idleTimeMs);
+        s.setRxTimeMs(rxTimeMs);
+        s.setEnergyConsumedMaMs(energyConsumedMaMs);
+        s.setTimeInRatMs(timeInRatMs);
+        s.setTimeInRxSignalStrengthLevelMs(timeInRxSignalStrengthLevelMs);
+        s.setTxTimeMs(txTimeMs);
+        return s;
+    }
+
     @Override
     public LevelStepTracker getChargeLevelStepTracker() {
         return mChargeStepTracker;
@@ -11761,11 +12169,23 @@
         return u;
     }
 
+    /**
+     * Retrieve the statistics object for a particular uid. Returns null if the object is not
+     * available.
+     */
+    public Uid getAvailableUidStatsLocked(int uid) {
+        Uid u = mUidStats.get(uid);
+        return u;
+    }
+
     public void onCleanupUserLocked(int userId) {
         final int firstUidForUser = UserHandle.getUid(userId, 0);
         final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
         mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
         mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        if (mKernelSingleUidTimeReader != null) {
+            mKernelSingleUidTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        }
     }
 
     public void onUserRemovedLocked(int userId) {
@@ -11784,6 +12204,9 @@
     public void removeUidStatsLocked(int uid) {
         mKernelUidCpuTimeReader.removeUid(uid);
         mKernelUidCpuFreqTimeReader.removeUid(uid);
+        if (mKernelSingleUidTimeReader != null) {
+            mKernelSingleUidTimeReader.removeUid(uid);
+        }
         mUidStats.remove(uid);
     }
 
@@ -12385,6 +12808,28 @@
                     in, mOnBatteryTimeBase);
             u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
                     in, mOnBatteryScreenOffTimeBase);
+            int length = in.readInt();
+            if (length == Uid.NUM_PROCESS_STATE) {
+                u.mProcStateTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    u.mProcStateTimeMs[procState]
+                            = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                                    in, mOnBatteryTimeBase);
+                }
+            } else {
+                u.mProcStateTimeMs = null;
+            }
+            length = in.readInt();
+            if (length == Uid.NUM_PROCESS_STATE) {
+                u.mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    u.mProcStateScreenOffTimeMs[procState]
+                            = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                                    in, mOnBatteryScreenOffTimeBase);
+                }
+            } else {
+                u.mProcStateScreenOffTimeMs = null;
+            }
 
             if (in.readInt() != 0) {
                 u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -12838,6 +13283,23 @@
             LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
             LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
 
+            if (u.mProcStateTimeMs != null) {
+                out.writeInt(u.mProcStateTimeMs.length);
+                for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+                    LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mProcStateScreenOffTimeMs != null) {
+                out.writeInt(u.mProcStateScreenOffTimeMs.length);
+                for (LongSamplingCounterArray counters : u.mProcStateScreenOffTimeMs) {
+                    LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+
             if (u.mMobileRadioApWakeupCount != null) {
                 out.writeInt(1);
                 u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/os/ByteTransferPipe.java b/core/java/com/android/internal/os/ByteTransferPipe.java
new file mode 100644
index 0000000..6489894
--- /dev/null
+++ b/core/java/com/android/internal/os/ByteTransferPipe.java
@@ -0,0 +1,49 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Helper class to get byte data through a pipe from a client app. Also {@see TransferPipe}.
+ */
+public class ByteTransferPipe extends TransferPipe {
+    static final String TAG = "ByteTransferPipe";
+
+    private ByteArrayOutputStream mOutputStream;
+
+    public ByteTransferPipe() throws IOException {
+        super();
+    }
+
+    public ByteTransferPipe(String bufferPrefix) throws IOException {
+        super(bufferPrefix, "ByteTransferPipe");
+    }
+
+    @Override
+    protected OutputStream getNewOutputStream() {
+        mOutputStream = new ByteArrayOutputStream();
+        return mOutputStream;
+    }
+
+    public byte[] get() throws IOException {
+        go(null);
+        return mOutputStream.toByteArray();
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..ca635a4
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -0,0 +1,204 @@
+/*
+ * 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.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+@VisibleForTesting(visibility = PACKAGE)
+public class KernelSingleUidTimeReader {
+    private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
+    private final boolean DBG = false;
+
+    private final String PROC_FILE_DIR = "/proc/uid/";
+    private final String PROC_FILE_NAME = "/time_in_state";
+
+    @VisibleForTesting
+    public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+    @GuardedBy("this")
+    private final int mCpuFreqsCount;
+
+    @GuardedBy("this")
+    private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+    @GuardedBy("this")
+    private int mReadErrorCounter;
+    @GuardedBy("this")
+    private boolean mSingleUidCpuTimesAvailable = true;
+
+    private final Injector mInjector;
+
+    KernelSingleUidTimeReader(int cpuFreqsCount) {
+        this(cpuFreqsCount, new Injector());
+    }
+
+    public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+        mInjector = injector;
+        mCpuFreqsCount = cpuFreqsCount;
+        if (mCpuFreqsCount == 0) {
+            mSingleUidCpuTimesAvailable = false;
+        }
+    }
+
+    public boolean singleUidCpuTimesAvailable() {
+        return mSingleUidCpuTimesAvailable;
+    }
+
+    public long[] readDeltaMs(int uid) {
+        synchronized (this) {
+            if (!mSingleUidCpuTimesAvailable) {
+                return null;
+            }
+            // Read total cpu times from the proc file.
+            final String procFile = new StringBuilder(PROC_FILE_DIR)
+                    .append(uid)
+                    .append(PROC_FILE_NAME).toString();
+            final long[] cpuTimesMs = new long[mCpuFreqsCount];
+            try {
+                final byte[] data = mInjector.readData(procFile);
+                final ByteBuffer buffer = ByteBuffer.wrap(data);
+                buffer.order(ByteOrder.nativeOrder());
+                for (int i = 0; i < mCpuFreqsCount; ++i) {
+                    // Times read will be in units of 10ms
+                    cpuTimesMs[i] = buffer.getLong() * 10;
+                }
+            } catch (Exception e) {
+                if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+                    mSingleUidCpuTimesAvailable = false;
+                }
+                if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+                return null;
+            }
+
+            return computeDelta(uid, cpuTimesMs);
+        }
+    }
+
+    /**
+     * Compute and return cpu times delta of an uid using previously read cpu times and
+     * {@param latestCpuTimesMs}.
+     *
+     * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+     */
+    public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+        synchronized (this) {
+            if (!mSingleUidCpuTimesAvailable) {
+                return null;
+            }
+            // Subtract the last read cpu times to get deltas.
+            final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+            final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+            if (deltaTimesMs == null) {
+                if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+                        + "; last=" + Arrays.toString(lastCpuTimesMs)
+                        + "; latest=" + Arrays.toString(latestCpuTimesMs));
+                return null;
+            }
+            // If all elements are zero, return null to avoid unnecessary work on the caller side.
+            boolean hasNonZero = false;
+            for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+                if (deltaTimesMs[i] > 0) {
+                    hasNonZero = true;
+                    break;
+                }
+            }
+            if (hasNonZero) {
+                mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+                return deltaTimesMs;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Returns null if the latest cpu times are not valid**, otherwise delta of
+     * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+     *
+     * **latest cpu times are considered valid if all the cpu times are +ve and
+     * greater than or equal to previously read cpu times.
+     */
+    @GuardedBy("this")
+    @VisibleForTesting(visibility = PACKAGE)
+    public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+            if (latestCpuTimesMs[i] < 0) {
+                return null;
+            }
+        }
+        if (lastCpuTimesMs == null) {
+            return latestCpuTimesMs;
+        }
+        final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+            deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+            if (deltaTimesMs[i] < 0) {
+                return null;
+            }
+        }
+        return deltaTimesMs;
+    }
+
+    public void removeUid(int uid) {
+        synchronized (this) {
+            mLastUidCpuTimeMs.delete(uid);
+        }
+    }
+
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        synchronized (this) {
+            mLastUidCpuTimeMs.put(startUid, null);
+            mLastUidCpuTimeMs.put(endUid, null);
+            final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+            final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+            mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+        }
+    }
+
+    @VisibleForTesting
+    public static class Injector {
+        public byte[] readData(String procFile) throws IOException {
+            return Files.readAllBytes(Paths.get(procFile));
+        }
+    }
+
+    @VisibleForTesting
+    public SparseArray<long[]> getLastUidCpuTimeMs() {
+        return mLastUidCpuTimeMs;
+    }
+
+    @VisibleForTesting
+    public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+        mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a39997d..b8982cc 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -66,13 +66,21 @@
     // start reading) and if it is not available, we simply ignore further read requests.
     private static final int TOTAL_READ_ERROR_COUNT = 5;
     private int mReadErrorCounter;
-    private boolean mProcFileAvailable;
     private boolean mPerClusterTimesAvailable;
+    private boolean mAllUidTimesAvailable = true;
 
     public boolean perClusterTimesAvailable() {
         return mPerClusterTimesAvailable;
     }
 
+    public boolean allUidTimesAvailable() {
+        return mAllUidTimesAvailable;
+    }
+
+    public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+        return mLastUidCpuFreqTimeMs;
+    }
+
     public long[] readFreqs(@NonNull PowerProfile powerProfile) {
         checkNotNull(powerProfile);
 
@@ -80,15 +88,16 @@
             // No need to read cpu freqs more than once.
             return mCpuFreqs;
         }
-        if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+        if (!mAllUidTimesAvailable) {
             return null;
         }
         final int oldMask = StrictMode.allowThreadDiskReadsMask();
         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
-            mProcFileAvailable = true;
             return readFreqs(reader, powerProfile);
         } catch (IOException e) {
-            mReadErrorCounter++;
+            if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+                mAllUidTimesAvailable = false;
+            }
             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
             return null;
         } finally {
@@ -107,7 +116,7 @@
     }
 
     public void readDelta(@Nullable Callback callback) {
-        if (!mProcFileAvailable) {
+        if (mCpuFreqs == null) {
             return;
         }
         final int oldMask = StrictMode.allowThreadDiskReadsMask();
diff --git a/core/java/com/android/internal/os/TransferPipe.java b/core/java/com/android/internal/os/TransferPipe.java
index 738ecc0b..1c09bd6 100644
--- a/core/java/com/android/internal/os/TransferPipe.java
+++ b/core/java/com/android/internal/os/TransferPipe.java
@@ -34,11 +34,12 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 
 /**
  * Helper for transferring data through a pipe from a client app.
  */
-public final class TransferPipe implements Runnable, Closeable {
+public class TransferPipe implements Runnable, Closeable {
     static final String TAG = "TransferPipe";
     static final boolean DEBUG = false;
 
@@ -64,7 +65,11 @@
     }
 
     public TransferPipe(String bufferPrefix) throws IOException {
-        mThread = new Thread(this, "TransferPipe");
+        this(bufferPrefix, "TransferPipe");
+    }
+
+    protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
+        mThread = new Thread(this, threadName);
         mFds = ParcelFileDescriptor.createPipe();
         mBufferPrefix = bufferPrefix;
     }
@@ -234,11 +239,15 @@
         }
     }
 
+    protected OutputStream getNewOutputStream() {
+          return new FileOutputStream(mOutFd);
+    }
+
     @Override
     public void run() {
         final byte[] buffer = new byte[1024];
         final FileInputStream fis;
-        final FileOutputStream fos;
+        final OutputStream fos;
 
         synchronized (this) {
             ParcelFileDescriptor readFd = getReadFd();
@@ -247,7 +256,7 @@
                 return;
             }
             fis = new FileInputStream(readFd.getFileDescriptor());
-            fos = new FileOutputStream(mOutFd);
+            fos = getNewOutputStream();
         }
 
         if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f983de1..7985e57 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -290,11 +290,11 @@
             if (cur instanceof ArraySet) {
                 ArraySet<T> arraySet = (ArraySet<T>) cur;
                 for (int i = 0; i < size; i++) {
-                    action.accept(arraySet.valueAt(i));
+                    action.acceptOrThrow(arraySet.valueAt(i));
                 }
             } else {
                 for (T t : cur) {
-                    action.accept(t);
+                    action.acceptOrThrow(t);
                 }
             }
         } catch (Exception e) {
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index eb92c1c..82ac241 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.util;
 
+import android.os.RemoteException;
+
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -25,6 +28,21 @@
     private FunctionalUtils() {}
 
     /**
+     * Converts a lambda expression that throws a checked exception(s) into a regular
+     * {@link Consumer} by propagating any checked exceptions as {@link RuntimeException}
+     */
+    public static <T> Consumer<T> uncheckExceptions(ThrowingConsumer<T> action) {
+        return action;
+    }
+
+    /**
+     *
+     */
+    public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
+        return action;
+    }
+
+    /**
      * An equivalent of {@link Runnable} that allows throwing checked exceptions
      *
      * This can be used to specify a lambda argument without forcing all the checked exceptions
@@ -47,13 +65,43 @@
     }
 
     /**
-     * An equivalent of {@link java.util.function.Consumer} that allows throwing checked exceptions
+     * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
      *
-     * This can be used to specify a lambda argument without forcing all the checked exceptions
-     * to be handled within it
+     * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+     * that throws a checked exception into a regular {@link Consumer}
      */
     @FunctionalInterface
-    public interface ThrowingConsumer<T> {
-        void accept(T t) throws Exception;
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface ThrowingConsumer<T> extends Consumer<T> {
+        void acceptOrThrow(T t) throws Exception;
+
+        @Override
+        default void accept(T t) {
+            try {
+                acceptOrThrow(t);
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * A {@link Consumer} that automatically ignores any {@link RemoteException}s.
+     *
+     * Used by {@link #ignoreRemoteException}
+     */
+    @FunctionalInterface
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
+        void acceptOrThrow(T t) throws RemoteException;
+
+        @Override
+        default void accept(T t) {
+            try {
+                acceptOrThrow(t);
+            } catch (RemoteException ex) {
+                // ignore
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index ac03d74..5e0a986b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -56,6 +56,7 @@
             in ResultReceiver resultReceiver);
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'attribute' is non-null then also does startInput.
+    // @NonNull
     InputBindResult startInputOrWindowGainedFocus(
             /* @InputMethodClient.StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken, int controlFlags,
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 3a3e56d..74dbaba 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -16,17 +16,134 @@
 
 package com.android.internal.view;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.view.InputChannel;
 
+import java.lang.annotation.Retention;
+
 /**
  * Bundle of information returned by input method manager about a successful
  * binding to an input method.
  */
 public final class InputBindResult implements Parcelable {
-    static final String TAG = "InputBindResult";
-    
+
+    @Retention(SOURCE)
+    @IntDef({
+            ResultCode.SUCCESS_WITH_IME_SESSION,
+            ResultCode.SUCCESS_WAITING_IME_SESSION,
+            ResultCode.SUCCESS_WAITING_IME_BINDING,
+            ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
+            ResultCode.ERROR_NULL,
+            ResultCode.ERROR_NO_IME,
+            ResultCode.ERROR_INVALID_PACKAGE_NAME,
+            ResultCode.ERROR_SYSTEM_NOT_READY,
+            ResultCode.ERROR_IME_NOT_CONNECTED,
+            ResultCode.ERROR_INVALID_USER,
+            ResultCode.ERROR_NULL_EDITOR_INFO,
+            ResultCode.ERROR_NOT_IME_TARGET_WINDOW,
+    })
+    public @interface ResultCode {
+        /**
+         * Indicates that everything in this result object including {@link #method} is valid.
+         */
+        int SUCCESS_WITH_IME_SESSION = 0;
+        /**
+         * Indicates that this is a temporary binding until the
+         * {@link android.inputmethodservice.InputMethodService} (IMS) establishes a valid session
+         * to {@link com.android.server.InputMethodManagerService} (IMMS).
+         *
+         * <p>Note that in this state the IMS is already bound to IMMS but the logical session
+         * is not yet established on top of the IPC channel.</p>
+         *
+         * <p>Some of fields such as {@link #channel} is not yet available.</p>
+         *
+         * @see android.inputmethodservice.InputMethodService##onCreateInputMethodSessionInterface()
+         **/
+        int SUCCESS_WAITING_IME_SESSION = 1;
+        /**
+         * Indicates that this is a temporary binding until the
+         * {@link android.inputmethodservice.InputMethodService} (IMS) establishes a valid session
+         * to {@link com.android.server.InputMethodManagerService} (IMMS).
+         *
+         * <p>Note that in this state the IMMS has already initiated a connection to the IMS but
+         * the binding process is not completed yet.</p>
+         *
+         * <p>Some of fields such as {@link #channel} is not yet available.</p>
+         * @see android.content.ServiceConnection#onServiceConnected(ComponentName, IBinder)
+         */
+        int SUCCESS_WAITING_IME_BINDING = 2;
+        /**
+         * Indicates that this is not intended for starting input but just for reporting window
+         * focus change from the application process.
+         *
+         * <p>All other fields do not have meaningful value.</p>
+         */
+        int SUCCESS_REPORT_WINDOW_FOCUS_ONLY = 3;
+        /**
+         * Indicates somehow
+         * {@link com.android.server.InputMethodManagerService#startInputOrWindowGainedFocus} is
+         * trying to return null {@link InputBindResult}, which must never happen.
+         */
+        int ERROR_NULL = 4;
+        /**
+         * Indicates that {@link com.android.server.InputMethodManagerService} recognizes no IME.
+         */
+        int ERROR_NO_IME = 5;
+        /**
+         * Indicates that {@link android.view.inputmethod.EditorInfo#packageName} does not match
+         * the caller UID.
+         *
+         * @see android.view.inputmethod.EditorInfo#packageName
+         */
+        int ERROR_INVALID_PACKAGE_NAME = 6;
+        /**
+         * Indicates that the system is still in an early stage of the boot process and any 3rd
+         * party application is not allowed to run.
+         *
+         * @see com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START
+         */
+        int ERROR_SYSTEM_NOT_READY = 7;
+        /**
+         * Indicates that {@link com.android.server.InputMethodManagerService} tried to connect to
+         * an {@link android.inputmethodservice.InputMethodService} but failed.
+         *
+         * @see android.content.Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+         */
+        int ERROR_IME_NOT_CONNECTED = 8;
+        /**
+         * Indicates that the caller is not the foreground user (or does not have
+         * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission).
+         */
+        int ERROR_INVALID_USER = 9;
+        /**
+         * Indicates that the caller should have specified non-null
+         * {@link android.view.inputmethod.EditorInfo}.
+         */
+        int ERROR_NULL_EDITOR_INFO = 10;
+        /**
+         * Indicates that the target window the client specified cannot be the IME target right now.
+         *
+         * <p>Due to the asynchronous nature of Android OS, we cannot completely avoid this error.
+         * The client should try to restart input when its {@link android.view.Window} is focused
+         * again.</p>
+         *
+         * @see com.android.server.wm.WindowManagerService#inputMethodClientHasFocus(IInputMethodClient)
+         */
+        int ERROR_NOT_IME_TARGET_WINDOW = 11;
+    }
+
+    @ResultCode
+    public final int result;
+
     /**
      * The input method service.
      */
@@ -53,16 +170,19 @@
      */
     public final int userActionNotificationSequenceNumber;
 
-    public InputBindResult(IInputMethodSession _method, InputChannel _channel,
+    public InputBindResult(@ResultCode int _result,
+            IInputMethodSession _method, InputChannel _channel,
             String _id, int _sequence, int _userActionNotificationSequenceNumber) {
+        result = _result;
         method = _method;
         channel = _channel;
         id = _id;
         sequence = _sequence;
         userActionNotificationSequenceNumber = _userActionNotificationSequenceNumber;
     }
-    
+
     InputBindResult(Parcel source) {
+        result = source.readInt();
         method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
         if (source.readInt() != 0) {
             channel = InputChannel.CREATOR.createFromParcel(source);
@@ -76,9 +196,9 @@
 
     @Override
     public String toString() {
-        return "InputBindResult{" + method + " " + id
-                + " sequence:" + sequence
-                + " userActionNotificationSequenceNumber:" + userActionNotificationSequenceNumber
+        return "InputBindResult{result=" + getResultString() + " method="+ method + " id=" + id
+                + " sequence=" + sequence
+                + " userActionNotificationSequenceNumber=" + userActionNotificationSequenceNumber
                 + "}";
     }
 
@@ -90,6 +210,7 @@
      */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(result);
         dest.writeStrongInterface(method);
         if (channel != null) {
             dest.writeInt(1);
@@ -122,4 +243,72 @@
     public int describeContents() {
         return channel != null ? channel.describeContents() : 0;
     }
+
+    public String getResultString() {
+        switch (result) {
+            case ResultCode.SUCCESS_WITH_IME_SESSION:
+                return "SUCCESS_WITH_IME_SESSION";
+            case ResultCode.SUCCESS_WAITING_IME_SESSION:
+                return "SUCCESS_WAITING_IME_SESSION";
+            case ResultCode.SUCCESS_WAITING_IME_BINDING:
+                return "SUCCESS_WAITING_IME_BINDING";
+            case ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY:
+                return "SUCCESS_REPORT_WINDOW_FOCUS_ONLY";
+            case ResultCode.ERROR_NULL:
+                return "ERROR_NULL";
+            case ResultCode.ERROR_NO_IME:
+                return "ERROR_NO_IME";
+            case ResultCode.ERROR_INVALID_PACKAGE_NAME:
+                return "ERROR_INVALID_PACKAGE_NAME";
+            case ResultCode.ERROR_SYSTEM_NOT_READY:
+                return "ERROR_SYSTEM_NOT_READY";
+            case ResultCode.ERROR_IME_NOT_CONNECTED:
+                return "ERROR_IME_NOT_CONNECTED";
+            case ResultCode.ERROR_INVALID_USER:
+                return "ERROR_INVALID_USER";
+            case ResultCode.ERROR_NULL_EDITOR_INFO:
+                return "ERROR_NULL_EDITOR_INFO";
+            case ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+                return "ERROR_NOT_IME_TARGET_WINDOW";
+            default:
+                return "Unknown(" + result + ")";
+        }
+    }
+
+    private static InputBindResult error(@ResultCode int result) {
+        return new InputBindResult(result, null, null, null, -1, -1);
+    }
+
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_NULL}.
+     */
+    public static final InputBindResult NULL = error(ResultCode.ERROR_NULL);
+    /**
+     * Predefined error object for {@link ResultCode#NO_IME}.
+     */
+    public static final InputBindResult NO_IME = error(ResultCode.ERROR_NO_IME);
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_INVALID_PACKAGE_NAME}.
+     */
+    public static final InputBindResult INVALID_PACKAGE_NAME =
+            error(ResultCode.ERROR_INVALID_PACKAGE_NAME);
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_NULL_EDITOR_INFO}.
+     */
+    public static final InputBindResult NULL_EDITOR_INFO = error(ResultCode.ERROR_NULL_EDITOR_INFO);
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_NOT_IME_TARGET_WINDOW}.
+     */
+    public static final InputBindResult NOT_IME_TARGET_WINDOW =
+            error(ResultCode.ERROR_NOT_IME_TARGET_WINDOW);
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_IME_NOT_CONNECTED}.
+     */
+    public static final InputBindResult IME_NOT_CONNECTED =
+            error(ResultCode.ERROR_IME_NOT_CONNECTED);
+    /**
+     * Predefined error object for {@link ResultCode#ERROR_INVALID_USER}.
+     */
+    public static final InputBindResult INVALID_USER = error(ResultCode.ERROR_INVALID_USER);
+
 }
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 8f80bfe..0d2b29b 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -50,6 +50,7 @@
     private TextView mShortcutView;
     private ImageView mSubMenuArrowView;
     private ImageView mGroupDivider;
+    private LinearLayout mContent;
 
     private Drawable mBackground;
     private int mTextAppearance;
@@ -114,6 +115,8 @@
             mSubMenuArrowView.setImageDrawable(mSubMenuArrow);
         }
         mGroupDivider = findViewById(com.android.internal.R.id.group_divider);
+
+        mContent = findViewById(com.android.internal.R.id.content);
     }
 
     public void initialize(MenuItemImpl itemData, int menuType) {
@@ -131,6 +134,18 @@
         setContentDescription(itemData.getContentDescription());
     }
 
+    private void addContentView(View v) {
+        addContentView(v, -1);
+    }
+
+    private void addContentView(View v, int index) {
+        if (mContent != null) {
+            mContent.addView(v, index);
+        } else {
+            addView(v, index);
+        }
+    }
+
     public void setForceShowIcon(boolean forceShow) {
         mPreserveIconSpacing = mForceShowIcon = forceShow;
     }
@@ -270,7 +285,7 @@
         LayoutInflater inflater = getInflater();
         mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
                 this, false);
-        addView(mIconView, 0);
+        addContentView(mIconView, 0);
     }
 
     private void insertRadioButton() {
@@ -278,7 +293,7 @@
         mRadioButton =
                 (RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
                 this, false);
-        addView(mRadioButton);
+        addContentView(mRadioButton);
     }
 
     private void insertCheckBox() {
@@ -286,7 +301,7 @@
         mCheckBox =
                 (CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
                 this, false);
-        addView(mCheckBox);
+        addContentView(mCheckBox);
     }
 
     public boolean prefersCondensedTitle() {
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index ee16ab6..77250eb 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,10 +16,17 @@
 
 package com.android.internal.widget;
 
+import android.app.PendingIntent;
 import android.app.trust.IStrongAuthTracker;
+import android.os.Bundle;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.VerifyCredentialResponse;
 
+import java.util.Map;
+
 /** {@hide} */
 interface ILockSettings {
     void setBoolean(in String key, in boolean value, in int userId);
@@ -52,4 +59,26 @@
     boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
             in byte[] token, int requestedQuality, int userId);
     void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
+
+    // RecoverableKeyStoreLoader methods.
+    // {@code ServiceSpecificException} may be thrown to signal an error, which caller can
+    // convert to  {@code RecoverableKeyStoreLoader}.
+    void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
+            int userId);
+    KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+    byte[] generateAndStoreKey(String alias);
+    void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
+    Map getRecoverySnapshotVersions(int userId);
+    void setServerParameters(long serverParameters, int userId);
+    void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+    Map getRecoveryStatus(in String packageName, int userId);
+    void setRecoverySecretTypes(in int[] secretTypes, int userId);
+    int[] getRecoverySecretTypes(int userId);
+    int[] getPendingRecoverySecretTypes(int userId);
+    void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId);
+    byte[] startRecoverySession(in String sessionId,
+            in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
+            in List<KeyStoreRecoveryMetadata> secrets, int userId);
+    Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
+            in List<KeyEntryRecoveryData> applicationKeys, int userId);
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8df0fb8..b3f66e9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -107,7 +107,6 @@
         "android_os_VintfRuntimeInfo.cpp",
         "android_net_LocalSocketImpl.cpp",
         "android_net_NetUtils.cpp",
-        "android_net_TrafficStats.cpp",
         "android_nio_utils.cpp",
         "android_util_AssetManager.cpp",
         "android_util_Binder.cpp",
@@ -123,6 +122,7 @@
         "android_graphics_Picture.cpp",
         "android/graphics/Bitmap.cpp",
         "android/graphics/BitmapFactory.cpp",
+        "android/graphics/ByteBufferStreamAdaptor.cpp",
         "android/graphics/Camera.cpp",
         "android/graphics/CanvasProperty.cpp",
         "android/graphics/ColorFilter.cpp",
@@ -134,6 +134,7 @@
         "android/graphics/GraphicBuffer.cpp",
         "android/graphics/Graphics.cpp",
         "android/graphics/HarfBuzzNGFaceSkia.cpp",
+        "android/graphics/ImageDecoder.cpp",
         "android/graphics/Interpolator.cpp",
         "android/graphics/MaskFilter.cpp",
         "android/graphics/Matrix.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9e907bd..6d7fe05 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -57,10 +57,12 @@
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
 extern int register_android_graphics_Camera(JNIEnv* env);
 extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
 extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
 extern int register_android_graphics_Graphics(JNIEnv* env);
+extern int register_android_graphics_ImageDecoder(JNIEnv*);
 extern int register_android_graphics_Interpolator(JNIEnv* env);
 extern int register_android_graphics_MaskFilter(JNIEnv* env);
 extern int register_android_graphics_Movie(JNIEnv* env);
@@ -174,7 +176,6 @@
 extern int register_android_os_SharedMemory(JNIEnv* env);
 extern int register_android_net_LocalSocketImpl(JNIEnv* env);
 extern int register_android_net_NetworkUtils(JNIEnv* env);
-extern int register_android_net_TrafficStats(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_Hyphenator(JNIEnv *env);
 extern int register_android_text_MeasuredText(JNIEnv* env);
@@ -644,6 +645,7 @@
     char methodTraceFileBuf[sizeof("-Xmethod-trace-file:") + PROPERTY_VALUE_MAX];
     char methodTraceFileSizeBuf[sizeof("-Xmethod-trace-file-size:") + PROPERTY_VALUE_MAX];
     std::string fingerprintBuf;
+    char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX];
 
     bool checkJni = false;
     property_get("dalvik.vm.checkjni", propBuf, "");
@@ -766,9 +768,15 @@
      * Set suspend=y to pause during VM init and use android ADB transport.
      */
     if (zygote) {
-      addOption("-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y");
+      addOption("-XjdwpOptions:suspend=n,server=y");
     }
 
+    // Set the JDWP provider. By default let the runtime choose.
+    parseRuntimeOption("dalvik.vm.jdwp-provider",
+                       jdwpProviderBuf,
+                       "-XjdwpProvider:",
+                       "default");
+
     parseRuntimeOption("dalvik.vm.lockprof.threshold",
                        lockProfThresholdBuf,
                        "-Xlockprofthreshold:");
@@ -1380,6 +1388,7 @@
     REG_JNI(register_android_graphics_Bitmap),
     REG_JNI(register_android_graphics_BitmapFactory),
     REG_JNI(register_android_graphics_BitmapRegionDecoder),
+    REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
     REG_JNI(register_android_graphics_Camera),
     REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
     REG_JNI(register_android_graphics_CanvasProperty),
@@ -1387,6 +1396,7 @@
     REG_JNI(register_android_graphics_DrawFilter),
     REG_JNI(register_android_graphics_FontFamily),
     REG_JNI(register_android_graphics_GraphicBuffer),
+    REG_JNI(register_android_graphics_ImageDecoder),
     REG_JNI(register_android_graphics_Interpolator),
     REG_JNI(register_android_graphics_MaskFilter),
     REG_JNI(register_android_graphics_Matrix),
@@ -1422,7 +1432,6 @@
     REG_JNI(register_android_os_UEventObserver),
     REG_JNI(register_android_net_LocalSocketImpl),
     REG_JNI(register_android_net_NetworkUtils),
-    REG_JNI(register_android_net_TrafficStats),
     REG_JNI(register_android_os_MemoryFile),
     REG_JNI(register_android_os_SharedMemory),
     REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 2e8c27a..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -47,9 +47,6 @@
 
 jfieldID gBitmap_ninePatchInsetsFieldID;
 
-jclass gInsetStruct_class;
-jmethodID gInsetStruct_constructorMethodID;
-
 jclass gBitmapConfig_class;
 jmethodID gBitmapConfig_nativeToConfigMethodID;
 
@@ -99,43 +96,6 @@
     return jstr;
 }
 
-static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
-    for (int i = 0; i < count; i++) {
-        divs[i] = int32_t(divs[i] * scale + 0.5f);
-        if (i > 0 && divs[i] == divs[i - 1]) {
-            divs[i]++; // avoid collisions
-        }
-    }
-
-    if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
-        // if the collision avoidance above put some divs outside the bounds of the bitmap,
-        // slide outer stretchable divs inward to stay within bounds
-        int highestAvailable = maxValue;
-        for (int i = count - 1; i >= 0; i--) {
-            divs[i] = highestAvailable;
-            if (i > 0 && divs[i] <= divs[i-1]){
-                // keep shifting
-                highestAvailable = divs[i] - 1;
-            } else {
-                break;
-            }
-        }
-    }
-}
-
-static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
-        int scaledWidth, int scaledHeight) {
-    chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
-    chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
-    chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
-    chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
-
-    // The max value for the divRange is one pixel less than the actual max to ensure that the size
-    // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
-    scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
-    scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
-}
-
 class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
 public:
     ScaleCheckingAllocator(float scale, int size)
@@ -425,10 +385,18 @@
             return nullObjectReturn("codec->getAndroidPixels() failed.");
     }
 
+    // This is weird so let me explain: we could use the scale parameter
+    // directly, but for historical reasons this is how the corresponding
+    // Dalvik code has always behaved. We simply recreate the behavior here.
+    // The result is slightly different from simply using scale because of
+    // the 0.5f rounding bias applied when computing the target image size
+    const float scaleX = scaledWidth / float(decodingBitmap.width());
+    const float scaleY = scaledHeight / float(decodingBitmap.height());
+
     jbyteArray ninePatchChunk = NULL;
     if (peeker.mPatch != NULL) {
         if (willScale) {
-            scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
+            peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
         }
 
         size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -448,12 +416,7 @@
 
     jobject ninePatchInsets = NULL;
     if (peeker.mHasInsets) {
-        ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
-                peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
-                peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
-                peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
-                peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
-                peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
+        ninePatchInsets = peeker.createNinePatchInsets(env, scale);
         if (ninePatchInsets == NULL) {
             return nullObjectReturn("nine patch insets == null");
         }
@@ -464,14 +427,6 @@
 
     SkBitmap outputBitmap;
     if (willScale) {
-        // This is weird so let me explain: we could use the scale parameter
-        // directly, but for historical reasons this is how the corresponding
-        // Dalvik code has always behaved. We simply recreate the behavior here.
-        // The result is slightly different from simply using scale because of
-        // the 0.5f rounding bias applied when computing the target image size
-        const float sx = scaledWidth / float(decodingBitmap.width());
-        const float sy = scaledHeight / float(decodingBitmap.height());
-
         // Set the allocator for the outputBitmap.
         SkBitmap::Allocator* outputAllocator;
         if (javaBitmap != nullptr) {
@@ -501,20 +456,14 @@
         paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
 
         SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
-        canvas.scale(sx, sy);
+        canvas.scale(scaleX, scaleY);
         canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
     } else {
         outputBitmap.swap(decodingBitmap);
     }
 
     if (padding) {
-        if (peeker.mPatch != NULL) {
-            GraphicsJNI::set_jrect(env, padding,
-                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
-                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
-        } else {
-            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
-        }
+        peeker.getPadding(env, padding);
     }
 
     // If we get here, the outputBitmap should have an installed pixelref.
@@ -705,11 +654,6 @@
     gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
             "Landroid/graphics/NinePatch$InsetStruct;");
 
-    gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
-        "android/graphics/NinePatch$InsetStruct"));
-    gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
-                                                        "(IIIIIIIIFIF)V");
-
     gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
             "android/graphics/Bitmap$Config"));
     gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
new file mode 100644
index 0000000..115edd4
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -0,0 +1,323 @@
+#include "ByteBufferStreamAdaptor.h"
+#include "core_jni_helpers.h"
+
+#include <SkStream.h>
+
+using namespace android;
+
+static jmethodID gByteBuffer_getMethodID;
+static jmethodID gByteBuffer_setPositionMethodID;
+
+static JNIEnv* get_env_or_die(JavaVM* jvm) {
+    JNIEnv* env;
+    if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
+    }
+    return env;
+}
+
+class ByteBufferStream : public SkStreamAsset {
+private:
+    ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
+                     jbyteArray storage)
+            : mJvm(jvm)
+            , mByteBuffer(jbyteBuffer)
+            , mPosition(0)
+            , mInitialPosition(initialPosition)
+            , mLength(length)
+            , mStorage(storage) {}
+
+public:
+    static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer,
+                                    size_t position, size_t length) {
+        // This object outlives its native method call.
+        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+        if (!jbyteBuffer) {
+            return nullptr;
+        }
+
+        jbyteArray storage = env->NewByteArray(kStorageSize);
+        if (!storage) {
+            env->DeleteGlobalRef(jbyteBuffer);
+            return nullptr;
+        }
+
+        // This object outlives its native method call.
+        storage = static_cast<jbyteArray>(env->NewGlobalRef(storage));
+        if (!storage) {
+            env->DeleteGlobalRef(jbyteBuffer);
+            return nullptr;
+        }
+
+        return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage);
+    }
+
+    ~ByteBufferStream() override {
+        auto* env = get_env_or_die(mJvm);
+        env->DeleteGlobalRef(mByteBuffer);
+        env->DeleteGlobalRef(mStorage);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        if (size > mLength - mPosition) {
+            size = mLength - mPosition;
+        }
+        if (!size) {
+            return 0;
+        }
+
+        if (!buffer) {
+            return this->setPosition(mPosition + size);
+        }
+
+        auto* env = get_env_or_die(mJvm);
+        size_t bytesRead = 0;
+        do {
+            const size_t requested = (size > kStorageSize) ? kStorageSize : size;
+            const jint jrequested = static_cast<jint>(requested);
+            env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested);
+            if (env->ExceptionCheck()) {
+                ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return bytesRead;
+            }
+
+            env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                ALOGE("Internal error in ByteBufferStream::read");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return bytesRead;
+            }
+
+            mPosition += requested;
+            buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested);
+            bytesRead += requested;
+            size -= requested;
+        } while (size);
+        return bytesRead;
+    }
+
+    bool isAtEnd() const override { return mLength == mPosition; }
+
+    // SkStreamRewindable overrides
+    bool rewind() override { return this->setPosition(0); }
+
+    SkStreamAsset* onDuplicate() const override {
+        // SkStreamRewindable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. A proper
+        // implementation would require duplicating the ByteBuffer, which has
+        // its own internal position state.
+        return nullptr;
+    }
+
+    // SkStreamSeekable overrides
+    size_t getPosition() const override { return mPosition; }
+
+    bool seek(size_t position) override {
+        return this->setPosition(position > mLength ? mLength : position);
+    }
+
+    bool move(long offset) override {
+        long newPosition = mPosition + offset;
+        if (newPosition < 0) {
+            return this->setPosition(0);
+        }
+        return this->seek(static_cast<size_t>(newPosition));
+    }
+
+    SkStreamAsset* onFork() const override {
+        // SkStreamSeekable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. A proper
+        // implementation would require duplicating the ByteBuffer, which has
+        // its own internal position state.
+        return nullptr;
+    }
+
+    // SkStreamAsset overrides
+    size_t getLength() const override { return mLength; }
+
+private:
+    JavaVM*          mJvm;
+    jobject          mByteBuffer;
+    // Logical position of the SkStream, between 0 and mLength.
+    size_t           mPosition;
+    // Initial position of mByteBuffer, treated as mPosition 0.
+    const size_t     mInitialPosition;
+    // Logical length of the SkStream, from mInitialPosition to
+    // mByteBuffer.limit().
+    const size_t     mLength;
+
+    // Range has already been checked by the caller.
+    bool setPosition(size_t newPosition) {
+        auto* env = get_env_or_die(mJvm);
+        env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
+                              newPosition + mInitialPosition);
+        if (env->ExceptionCheck()) {
+            ALOGE("Internal error in ByteBufferStream::setPosition");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            mPosition = mLength;
+            return false;
+        }
+        mPosition = newPosition;
+        return true;
+    }
+
+    // FIXME: This is an arbitrary storage size, which should be plenty for
+    // some formats (png, gif, many bmps). But for jpeg, the more we can supply
+    // in one call the better, and webp really wants all of the data. How to
+    // best choose the amount of storage used?
+    static constexpr size_t kStorageSize = 4096;
+    jbyteArray mStorage;
+};
+
+class ByteArrayStream : public SkStreamAsset {
+private:
+    ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length)
+            : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {}
+
+public:
+    static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset,
+                                   size_t length) {
+        // This object outlives its native method call.
+        jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray));
+        if (!jarray) {
+            return nullptr;
+        }
+        return new ByteArrayStream(jvm, jarray, offset, length);
+    }
+
+    ~ByteArrayStream() override {
+        auto* env = get_env_or_die(mJvm);
+        env->DeleteGlobalRef(mByteArray);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        if (size > mLength - mPosition) {
+            size = mLength - mPosition;
+        }
+        if (!size) {
+            return 0;
+        }
+
+        auto* env = get_env_or_die(mJvm);
+        if (buffer) {
+            env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
+                                    reinterpret_cast<jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                ALOGE("Internal error in ByteArrayStream::read");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return 0;
+            }
+        }
+
+        mPosition += size;
+        return size;
+    }
+
+    bool isAtEnd() const override { return mLength == mPosition; }
+
+    // SkStreamRewindable overrides
+    bool rewind() override {
+        mPosition = 0;
+        return true;
+    }
+    SkStreamAsset* onDuplicate() const override {
+        // SkStreamRewindable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. Note that a proper
+        // implementation is fairly straightforward
+        return nullptr;
+    }
+
+    // SkStreamSeekable overrides
+    size_t getPosition() const override { return mPosition; }
+
+    bool seek(size_t position) override {
+        mPosition = (position > mLength) ? mLength : position;
+        return true;
+    }
+
+    bool move(long offset) override {
+        long newPosition = mPosition + offset;
+        if (newPosition < 0) {
+            return this->seek(0);
+        }
+        return this->seek(static_cast<size_t>(newPosition));
+    }
+
+    SkStreamAsset* onFork() const override {
+        // SkStreamSeekable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. Note that a proper
+        // implementation is fairly straightforward
+        return nullptr;
+    }
+
+    // SkStreamAsset overrides
+    size_t getLength() const override { return mLength; }
+
+private:
+    JavaVM*      mJvm;
+    jbyteArray   mByteArray;
+    // Offset in mByteArray. Only used when communicating with Java.
+    const size_t mOffset;
+    // Logical position of the SkStream, between 0 and mLength.
+    size_t       mPosition;
+    const size_t mLength;
+};
+
+struct release_proc_context {
+    JavaVM* jvm;
+    jobject jbyteBuffer;
+};
+
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer,
+                                                        size_t position, size_t limit) {
+    JavaVM* jvm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+    const size_t length = limit - position;
+    void* addr = env->GetDirectBufferAddress(jbyteBuffer);
+    if (addr) {
+        addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position);
+        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+        if (!jbyteBuffer) {
+            return nullptr;
+        }
+
+        auto* context = new release_proc_context{jvm, jbyteBuffer};
+        auto releaseProc = [](const void*, void* context) {
+            auto* c = reinterpret_cast<release_proc_context*>(context);
+            JNIEnv* env = get_env_or_die(c->jvm);
+            env->DeleteGlobalRef(c->jbyteBuffer);
+            delete c;
+        };
+        auto data = SkData::MakeWithProc(addr, length, releaseProc, context);
+        // The new SkMemoryStream will read directly from addr.
+        return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data)));
+    }
+
+    // Non-direct, or direct access is not supported.
+    return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position,
+                                                              length));
+}
+
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset,
+                                                       size_t length) {
+    JavaVM* jvm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+    return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length));
+}
+
+int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) {
+    jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer");
+    gByteBuffer_getMethodID         = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;");
+    gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;");
+    return true;
+}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.h b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
new file mode 100644
index 0000000..367a48f
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
@@ -0,0 +1,37 @@
+#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+
+#include <jni.h>
+#include <memory>
+
+class SkStream;
+
+/**
+ * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream.
+ *
+ * This will special case direct ByteBuffers, but not the case where a byte[]
+ * can be used directly. For that, use CreateByteArrayStreamAdaptor.
+ *
+ * @param jbyteBuffer corresponding to the java ByteBuffer. This method will
+ *      add a global ref.
+ * @param initialPosition returned by ByteBuffer.position(). Decoding starts
+ *      from here.
+ * @param limit returned by ByteBuffer.limit().
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer,
+                                                        size_t initialPosition, size_t limit);
+
+/**
+ * Create an adaptor for treating a Java byte[] as an SkStream.
+ *
+ * @param offset into the byte[] of the beginning of the data to use.
+ * @param length of data to use, starting from offset.
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset,
+                                                       size_t length);
+
+#endif  // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
new file mode 100644
index 0000000..bacab2a
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -0,0 +1,473 @@
+/*
+ * 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.
+ */
+
+#include "Bitmap.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
+
+#include <SkAndroidCodec.h>
+#include <SkEncodedImageFormat.h>
+#include <SkStream.h>
+
+#include <androidfw/Asset.h>
+#include <jni.h>
+
+using namespace android;
+
+static jclass    gImageDecoder_class;
+static jclass    gPoint_class;
+static jclass    gIncomplete_class;
+static jclass    gCorrupt_class;
+static jclass    gCanvas_class;
+static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gPoint_constructorMethodID;
+static jmethodID gIncomplete_constructorMethodID;
+static jmethodID gCorrupt_constructorMethodID;
+static jmethodID gCallback_onExceptionMethodID;
+static jmethodID gPostProcess_postProcessMethodID;
+static jmethodID gCanvas_constructorMethodID;
+static jmethodID gCanvas_releaseMethodID;
+
+struct ImageDecoder {
+    // These need to stay in sync with ImageDecoder.java's Allocator constants.
+    enum Allocator {
+        kDefault_Allocator      = 0,
+        kSoftware_Allocator     = 1,
+        kSharedMemory_Allocator = 2,
+        kHardware_Allocator     = 3,
+    };
+
+    // These need to stay in sync with PixelFormat.java's Format constants.
+    enum PixelFormat {
+        kUnknown     =  0,
+        kTranslucent = -3,
+        kOpaque      = -1,
+    };
+
+    std::unique_ptr<SkAndroidCodec> mCodec;
+    NinePatchPeeker mPeeker;
+};
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
+    if (!stream.get()) {
+        return nullObjectReturn("Failed to create a stream");
+    }
+    std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+    decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker);
+    if (!decoder->mCodec.get()) {
+        // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like
+        // SkCodec? Then this can print a more informative error message.
+        // (Or we can print one from within SkCodec.)
+        ALOGE("Failed to create an SkCodec");
+        return nullptr;
+    }
+
+    const auto& info = decoder->mCodec->getInfo();
+    const int width = info.width();
+    const int height = info.height();
+    return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
+                          reinterpret_cast<jlong>(decoder.release()), width, height);
+}
+
+static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
+    Asset* asset = reinterpret_cast<Asset*>(assetPtr);
+    std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
+    return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer,
+                                              jint initialPosition, jint limit) {
+    std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
+                                                                     initialPosition, limit);
+    if (!stream) {
+        return nullptr;
+    }
+    return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray,
+                                             jint offset, jint length) {
+    std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
+    return native_create(env, std::move(stream));
+}
+
+static bool supports_any_down_scale(const SkAndroidCodec* codec) {
+    return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
+}
+
+static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                          jobject jcallback, jobject jpostProcess,
+                                          jint desiredWidth, jint desiredHeight, jobject jsubset,
+                                          jboolean requireMutable, jint allocator,
+                                          jboolean requireUnpremul, jboolean preferRamOverQuality,
+                                          jboolean asAlphaMask) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    SkAndroidCodec* codec = decoder->mCodec.get();
+    SkImageInfo decodeInfo = codec->getInfo();
+    bool scale = false;
+    int sampleSize = 1;
+    if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) {
+        bool match = false;
+        if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) {
+            if (supports_any_down_scale(codec)) {
+                match = true;
+                decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight);
+            } else {
+                int sampleX = decodeInfo.width()  / desiredWidth;
+                int sampleY = decodeInfo.height() / desiredHeight;
+                sampleSize = std::min(sampleX, sampleY);
+                SkISize sampledSize = codec->getSampledDimensions(sampleSize);
+                decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height());
+                if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) {
+                    match = true;
+                }
+            }
+        }
+        if (!match) {
+            scale = true;
+            if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+                doThrowISE(env, "Cannot scale unpremultiplied pixels!");
+                return nullptr;
+            }
+        }
+    }
+
+    switch (decodeInfo.alphaType()) {
+        case kUnpremul_SkAlphaType:
+            if (!requireUnpremul) {
+                decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
+            }
+            break;
+        case kPremul_SkAlphaType:
+            if (requireUnpremul) {
+                decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
+            }
+            break;
+        case kOpaque_SkAlphaType:
+            break;
+        case kUnknown_SkAlphaType:
+            return nullObjectReturn("Unknown alpha type");
+    }
+
+    SkColorType colorType = kN32_SkColorType;
+    if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+        // We have to trick Skia to decode this to a single channel.
+        colorType = kGray_8_SkColorType;
+    } else if (preferRamOverQuality) {
+        // FIXME: The post-process might add alpha, which would make a 565
+        // result incorrect. If we call the postProcess before now and record
+        // to a picture, we can know whether alpha was added, and if not, we
+        // can still use 565.
+        if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+            // If the final result will be hardware, decoding to 565 and then
+            // uploading to the gpu as 8888 will not save memory. This still
+            // may save us from using F16, but do not go down to 565.
+            if (allocator != ImageDecoder::kHardware_Allocator &&
+               (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+                colorType = kRGB_565_SkColorType;
+            }
+        }
+        // Otherwise, stick with N32
+    } else {
+        // This is currently the only way to know that we should decode to F16.
+        colorType = codec->computeOutputColorType(colorType);
+    }
+    sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType);
+    decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+
+    SkBitmap bm;
+    auto bitmapInfo = decodeInfo;
+    if (asAlphaMask && colorType == kGray_8_SkColorType) {
+        bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+    }
+    if (!bm.setInfo(bitmapInfo)) {
+        return nullObjectReturn("Failed to setInfo properly");
+    }
+
+    sk_sp<Bitmap> nativeBitmap;
+    // If we are going to scale or subset, we will create a new bitmap later on,
+    // so use the heap for the temporary.
+    // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+    if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+        nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+    } else {
+        nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+    }
+    if (!nativeBitmap) {
+        ALOGE("OOM allocating Bitmap with dimensions %i x %i",
+              decodeInfo.width(), decodeInfo.height());
+        doThrowOOME(env);
+        return nullptr;
+    }
+
+    jobject jexception = nullptr;
+    SkAndroidCodec::AndroidOptions options;
+    options.fSampleSize = sampleSize;
+    auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+    switch (result) {
+        case SkCodec::kSuccess:
+            break;
+        case SkCodec::kIncompleteInput:
+            if (jcallback) {
+                jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID);
+            }
+            break;
+        case SkCodec::kErrorInInput:
+            if (jcallback) {
+                jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID);
+            }
+            break;
+        default:
+            ALOGE("getPixels failed with error %i", result);
+            return nullptr;
+    }
+
+    if (jexception) {
+        if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) ||
+            env->ExceptionCheck()) {
+            return nullptr;
+        }
+    }
+
+    float scaleX = 1.0f;
+    float scaleY = 1.0f;
+    if (scale) {
+        scaleX = (float) desiredWidth  / decodeInfo.width();
+        scaleY = (float) desiredHeight / decodeInfo.height();
+    }
+
+    jbyteArray ninePatchChunk = nullptr;
+    jobject ninePatchInsets = nullptr;
+
+    // Ignore ninepatch when post-processing.
+    if (!jpostProcess) {
+        // FIXME: Share more code with BitmapFactory.cpp.
+        if (decoder->mPeeker.mPatch != nullptr) {
+            if (scale) {
+                decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+            }
+            size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
+            if (ninePatchChunk == nullptr) {
+                return nullObjectReturn("ninePatchChunk == null");
+            }
+
+            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
+                                    reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+        }
+
+        if (decoder->mPeeker.mHasInsets) {
+            ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+            if (ninePatchInsets == nullptr) {
+                return nullObjectReturn("nine patch insets == null");
+            }
+        }
+    }
+
+    if (scale || jsubset) {
+        int translateX = 0;
+        int translateY = 0;
+        if (jsubset) {
+            SkIRect subset;
+            GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+
+            // FIXME: If there is no scale, should this instead call
+            // SkBitmap::extractSubset? If we could upload a subset
+            // (b/70626068), this would save memory and time. Even for a
+            // software Bitmap, the extra speed might be worth the memory
+            // tradeoff if the subset is large?
+            translateX    = -subset.fLeft;
+            translateY    = -subset.fTop;
+            desiredWidth  =  subset.width();
+            desiredHeight =  subset.height();
+        }
+        SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
+        SkBitmap scaledBm;
+        if (!scaledBm.setInfo(scaledInfo)) {
+            nullObjectReturn("Failed scaled setInfo");
+        }
+
+        sk_sp<Bitmap> scaledPixelRef;
+        if (allocator == ImageDecoder::kSharedMemory_Allocator) {
+            scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
+        } else {
+            scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
+        }
+        if (!scaledPixelRef) {
+            ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i",
+                  desiredWidth, desiredHeight);
+            doThrowOOME(env);
+            return nullptr;
+        }
+
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        paint.setFilterQuality(kLow_SkFilterQuality);  // bilinear filtering
+
+        SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+        canvas.translate(translateX, translateY);
+        canvas.scale(scaleX, scaleY);
+        canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
+
+        bm.swap(scaledBm);
+        nativeBitmap = scaledPixelRef;
+    }
+
+    if (jpostProcess) {
+        std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
+        if (!canvas) {
+            return nullObjectReturn("Failed to create Canvas for PostProcess!");
+        }
+        jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+                                         reinterpret_cast<jlong>(canvas.get()));
+        if (!jcanvas) {
+            return nullObjectReturn("Failed to create Java Canvas for PostProcess!");
+        }
+        // jcanvas will now own canvas.
+        canvas.release();
+
+        jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID,
+                                              jcanvas, bm.width(), bm.height());
+        if (env->ExceptionCheck()) {
+            return nullptr;
+        }
+
+        // The Canvas objects are no longer needed, and will not remain valid.
+        env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID);
+        if (env->ExceptionCheck()) {
+            return nullptr;
+        }
+
+        SkAlphaType newAlphaType = bm.alphaType();
+        switch (pixelFormat) {
+            case ImageDecoder::kUnknown:
+                break;
+            case ImageDecoder::kTranslucent:
+                newAlphaType = kPremul_SkAlphaType;
+                break;
+            case ImageDecoder::kOpaque:
+                newAlphaType = kOpaque_SkAlphaType;
+                break;
+            default:
+                ALOGE("invalid return from postProcess: %i", pixelFormat);
+                doThrowIAE(env);
+                return nullptr;
+        }
+
+        if (newAlphaType != bm.alphaType()) {
+            if (!bm.setAlphaType(newAlphaType)) {
+                ALOGE("incompatible return from postProcess: %i", pixelFormat);
+                doThrowIAE(env);
+                return nullptr;
+            }
+            nativeBitmap->setAlphaType(newAlphaType);
+        }
+    }
+
+    int bitmapCreateFlags = 0x0;
+    if (!requireUnpremul) {
+        // Even if the image is opaque, setting this flag means that
+        // if alpha is added (e.g. by PostProcess), it will be marked as
+        // premultiplied.
+        bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied;
+    }
+
+    if (requireMutable) {
+        bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
+    } else {
+        if ((allocator == ImageDecoder::kDefault_Allocator ||
+             allocator == ImageDecoder::kHardware_Allocator)
+            && bm.colorType() != kAlpha_8_SkColorType)
+        {
+            sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
+            if (hwBitmap) {
+                hwBitmap->setImmutable();
+                return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
+                                            ninePatchChunk, ninePatchInsets);
+            }
+            if (allocator == ImageDecoder::kHardware_Allocator) {
+                return nullObjectReturn("failed to allocate hardware Bitmap!");
+            }
+            // If we failed to create a hardware bitmap, go ahead and create a
+            // software one.
+        }
+
+        nativeBitmap->setImmutable();
+    }
+    return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
+                                ninePatchInsets);
+}
+
+static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                            jint sampleSize) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+    return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height());
+}
+
+static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                     jobject outPadding) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    decoder->mPeeker.getPadding(env, outPadding);
+}
+
+static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
+    delete reinterpret_cast<ImageDecoder*>(nativePtr);
+}
+
+static const JNINativeMethod gImageDecoderMethods[] = {
+    { "nCreate",        "(J)Landroid/graphics/ImageDecoder;",    (void*) ImageDecoder_nCreate },
+    { "nCreate",        "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+    { "nCreate",        "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+    { "nDecodeBitmap",  "(JLandroid/graphics/ImageDecoder$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+                                                                 (void*) ImageDecoder_nDecodeBitmap },
+    { "nGetSampledSize","(JI)Landroid/graphics/Point;",          (void*) ImageDecoder_nGetSampledSize },
+    { "nGetPadding",    "(JLandroid/graphics/Rect;)V",           (void*) ImageDecoder_nGetPadding },
+    { "nRecycle",       "(J)V",                                  (void*) ImageDecoder_nRecycle},
+};
+
+int register_android_graphics_ImageDecoder(JNIEnv* env) {
+    gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
+    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+
+    gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
+    gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
+
+    gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException"));
+    gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V");
+
+    gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException"));
+    gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");
+
+    jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener");
+    gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z");
+
+    jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
+    gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
+
+    gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
+    gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
+    gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
+
+    return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods,
+                                         NELEM(gImageDecoderMethods));
+}
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 564afeb..2619107 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -29,11 +29,15 @@
 #include "SkLatticeIter.h"
 #include "SkRegion.h"
 #include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
 #include "NinePatchUtils.h"
 
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
+jclass      gInsetStruct_class;
+jmethodID   gInsetStruct_constructorMethodID;
+
 using namespace android;
 
 /**
@@ -128,6 +132,30 @@
 
 };
 
+jobject NinePatchPeeker::createNinePatchInsets(JNIEnv* env, float scale) const {
+    if (!mHasInsets) {
+        return nullptr;
+    }
+
+    return env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+            mOpticalInsets[0], mOpticalInsets[1],
+            mOpticalInsets[2], mOpticalInsets[3],
+            mOutlineInsets[0], mOutlineInsets[1],
+            mOutlineInsets[2], mOutlineInsets[3],
+            mOutlineRadius, mOutlineAlpha, scale);
+}
+
+void NinePatchPeeker::getPadding(JNIEnv* env, jobject outPadding) const {
+    if (mPatch) {
+        GraphicsJNI::set_jrect(env, outPadding,
+                mPatch->paddingLeft, mPatch->paddingTop,
+                mPatch->paddingRight, mPatch->paddingBottom);
+
+    } else {
+        GraphicsJNI::set_jrect(env, outPadding, -1, -1, -1, -1);
+    }
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gNinePatchMethods[] = {
@@ -140,6 +168,10 @@
 };
 
 int register_android_graphics_NinePatch(JNIEnv* env) {
+    gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
+            "android/graphics/NinePatch$InsetStruct"));
+    gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
+            "(IIIIIIIIFIF)V");
     return android::RegisterMethodsOrDie(env,
             "android/graphics/NinePatch", gNinePatchMethods, NELEM(gNinePatchMethods));
 }
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 1ea5650..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -16,7 +16,8 @@
 
 #include "NinePatchPeeker.h"
 
-#include "SkBitmap.h"
+#include <SkBitmap.h>
+#include <cutils/compiler.h>
 
 using namespace android;
 
@@ -46,3 +47,47 @@
     }
     return true;    // keep on decoding
 }
+
+static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
+    for (int i = 0; i < count; i++) {
+        divs[i] = int32_t(divs[i] * scale + 0.5f);
+        if (i > 0 && divs[i] == divs[i - 1]) {
+            divs[i]++; // avoid collisions
+        }
+    }
+
+    if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
+        // if the collision avoidance above put some divs outside the bounds of the bitmap,
+        // slide outer stretchable divs inward to stay within bounds
+        int highestAvailable = maxValue;
+        for (int i = count - 1; i >= 0; i--) {
+            divs[i] = highestAvailable;
+            if (i > 0 && divs[i] <= divs[i-1]) {
+                // keep shifting
+                highestAvailable = divs[i] - 1;
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+void NinePatchPeeker::scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight) {
+    if (!mPatch) {
+        return;
+    }
+
+    // The max value for the divRange is one pixel less than the actual max to ensure that the size
+    // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
+    if (!SkScalarNearlyEqual(scaleX, 1.0f)) {
+        mPatch->paddingLeft   = int(mPatch->paddingLeft   * scaleX + 0.5f);
+        mPatch->paddingRight  = int(mPatch->paddingRight  * scaleX + 0.5f);
+        scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
+    }
+
+    if (!SkScalarNearlyEqual(scaleY, 1.0f)) {
+        mPatch->paddingTop    = int(mPatch->paddingTop    * scaleY + 0.5f);
+        mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f);
+        scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+    }
+}
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 126eab2..e4e58dd 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -20,7 +20,7 @@
 #include "SkPngChunkReader.h"
 #include <androidfw/ResourceTypes.h>
 
-class SkImageDecoder;
+#include <jni.h>
 
 using namespace android;
 
@@ -42,9 +42,14 @@
 
     bool readChunk(const char tag[], const void* data, size_t length) override;
 
+    jobject createNinePatchInsets(JNIEnv*, float scale) const;
+    void getPadding(JNIEnv*, jobject outPadding) const;
+    void scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight);
+
     Res_png_9patch* mPatch;
     size_t mPatchSize;
     bool mHasInsets;
+private:
     int32_t mOpticalInsets[4];
     int32_t mOutlineInsets[4];
     float mOutlineRadius;
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 1522c20..1676d4b 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -25,7 +25,8 @@
 #include <assert.h>
 #include <dlfcn.h>
 
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
 #include <ETC1/etc1.h>
 
 #include <SkBitmap.h>
@@ -639,6 +640,10 @@
                         return 0;
             }
             break;
+        case kRGBA_F16_SkColorType:
+            if (type == GL_HALF_FLOAT_OES && format == PIXEL_FORMAT_RGBA_FP16)
+                return 0;
+            break;
         default:
             break;
     }
@@ -656,6 +661,8 @@
             return GL_RGBA;
         case kRGB_565_SkColorType:
             return GL_RGB;
+        case kRGBA_F16_SkColorType:
+            return PIXEL_FORMAT_RGBA_FP16;
         default:
             return -1;
     }
@@ -672,6 +679,8 @@
             return GL_UNSIGNED_BYTE;
         case kRGB_565_SkColorType:
             return GL_UNSIGNED_SHORT_5_6_5;
+        case kRGBA_F16_SkColorType:
+            return GL_HALF_FLOAT_OES;
         default:
             return -1;
     }
diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp
deleted file mode 100644
index d0c237d..0000000
--- a/core/jni/android_net_TrafficStats.cpp
+++ /dev/null
@@ -1,198 +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.
- */
-
-#define LOG_TAG "TrafficStats"
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "core_jni_helpers.h"
-#include <jni.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include <utils/misc.h>
-#include <utils/Log.h>
-
-namespace android {
-
-static const char* QTAGUID_IFACE_STATS = "/proc/net/xt_qtaguid/iface_stat_fmt";
-static const char* QTAGUID_UID_STATS = "/proc/net/xt_qtaguid/stats";
-
-// NOTE: keep these in sync with TrafficStats.java
-static const uint64_t UNKNOWN = -1;
-
-enum StatsType {
-    RX_BYTES = 0,
-    RX_PACKETS = 1,
-    TX_BYTES = 2,
-    TX_PACKETS = 3,
-    TCP_RX_PACKETS = 4,
-    TCP_TX_PACKETS = 5
-};
-
-struct Stats {
-    uint64_t rxBytes;
-    uint64_t rxPackets;
-    uint64_t txBytes;
-    uint64_t txPackets;
-    uint64_t tcpRxPackets;
-    uint64_t tcpTxPackets;
-};
-
-static uint64_t getStatsType(struct Stats* stats, StatsType type) {
-    switch (type) {
-        case RX_BYTES:
-            return stats->rxBytes;
-        case RX_PACKETS:
-            return stats->rxPackets;
-        case TX_BYTES:
-            return stats->txBytes;
-        case TX_PACKETS:
-            return stats->txPackets;
-        case TCP_RX_PACKETS:
-            return stats->tcpRxPackets;
-        case TCP_TX_PACKETS:
-            return stats->tcpTxPackets;
-        default:
-            return UNKNOWN;
-    }
-}
-
-static int parseIfaceStats(const char* iface, struct Stats* stats) {
-    FILE *fp = fopen(QTAGUID_IFACE_STATS, "r");
-    if (fp == NULL) {
-        return -1;
-    }
-
-    char buffer[384];
-    char cur_iface[32];
-    bool foundTcp = false;
-    uint64_t rxBytes, rxPackets, txBytes, txPackets, tcpRxPackets, tcpTxPackets;
-
-    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
-        int matched = sscanf(buffer, "%31s %" SCNu64 " %" SCNu64 " %" SCNu64
-                " %" SCNu64 " " "%*u %" SCNu64 " %*u %*u %*u %*u "
-                "%*u %" SCNu64 " %*u %*u %*u %*u", cur_iface, &rxBytes,
-                &rxPackets, &txBytes, &txPackets, &tcpRxPackets, &tcpTxPackets);
-        if (matched >= 5) {
-            if (matched == 7) {
-                foundTcp = true;
-            }
-            if (!iface || !strcmp(iface, cur_iface)) {
-                stats->rxBytes += rxBytes;
-                stats->rxPackets += rxPackets;
-                stats->txBytes += txBytes;
-                stats->txPackets += txPackets;
-                if (matched == 7) {
-                    stats->tcpRxPackets += tcpRxPackets;
-                    stats->tcpTxPackets += tcpTxPackets;
-                }
-            }
-        }
-    }
-
-    if (!foundTcp) {
-        stats->tcpRxPackets = UNKNOWN;
-        stats->tcpTxPackets = UNKNOWN;
-    }
-
-    if (fclose(fp) != 0) {
-        return -1;
-    }
-    return 0;
-}
-
-static int parseUidStats(const uint32_t uid, struct Stats* stats) {
-    FILE *fp = fopen(QTAGUID_UID_STATS, "r");
-    if (fp == NULL) {
-        return -1;
-    }
-
-    char buffer[384];
-    char iface[32];
-    uint32_t idx, cur_uid, set;
-    uint64_t tag, rxBytes, rxPackets, txBytes, txPackets;
-
-    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
-        if (sscanf(buffer,
-                "%" SCNu32 " %31s 0x%" SCNx64 " %u %u %" SCNu64 " %" SCNu64
-                " %" SCNu64 " %" SCNu64 "",
-                &idx, iface, &tag, &cur_uid, &set, &rxBytes, &rxPackets,
-                &txBytes, &txPackets) == 9) {
-            if (uid == cur_uid && tag == 0L) {
-                stats->rxBytes += rxBytes;
-                stats->rxPackets += rxPackets;
-                stats->txBytes += txBytes;
-                stats->txPackets += txPackets;
-            }
-        }
-    }
-
-    if (fclose(fp) != 0) {
-        return -1;
-    }
-    return 0;
-}
-
-static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
-    struct Stats stats;
-    memset(&stats, 0, sizeof(Stats));
-    if (parseIfaceStats(NULL, &stats) == 0) {
-        return getStatsType(&stats, (StatsType) type);
-    } else {
-        return UNKNOWN;
-    }
-}
-
-static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
-    ScopedUtfChars iface8(env, iface);
-    if (iface8.c_str() == NULL) {
-        return UNKNOWN;
-    }
-
-    struct Stats stats;
-    memset(&stats, 0, sizeof(Stats));
-    if (parseIfaceStats(iface8.c_str(), &stats) == 0) {
-        return getStatsType(&stats, (StatsType) type);
-    } else {
-        return UNKNOWN;
-    }
-}
-
-static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
-    struct Stats stats;
-    memset(&stats, 0, sizeof(Stats));
-    if (parseUidStats(uid, &stats) == 0) {
-        return getStatsType(&stats, (StatsType) type);
-    } else {
-        return UNKNOWN;
-    }
-}
-
-static const JNINativeMethod gMethods[] = {
-    {"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
-    {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
-    {"nativeGetUidStat", "(II)J", (void*) getUidStat},
-};
-
-int register_android_net_TrafficStats(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
-}
-
-}
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 17b98da..cc2646c 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -236,17 +236,15 @@
         return INSTALL_SUCCEEDED;
     }
 
-    char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 2];
+    char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 1];
     if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName))
             != nativeLibPath.size()) {
         ALOGD("Couldn't allocate local file name for library");
         return INSTALL_FAILED_INTERNAL_ERROR;
     }
 
-    *(localTmpFileName + nativeLibPath.size()) = '/';
-
     if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN,
-                    TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) {
+                    TMP_FILE_PATTERN_LEN + 1) != TMP_FILE_PATTERN_LEN) {
         ALOGI("Couldn't allocate temporary file name for library");
         return INSTALL_FAILED_INTERNAL_ERROR;
     }
diff --git a/core/proto/android/internal/processstats.proto b/core/proto/android/internal/processstats.proto
new file mode 100644
index 0000000..5629c2d
--- /dev/null
+++ b/core/proto/android/internal/processstats.proto
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.app.procstats;
+
+message ProcessStatsProto {
+  enum MemoryFactor {
+    MEM_FACTOR_NORMAL = 0;
+    MEM_FACTOR_MODERATE = 1;
+    MEM_FACTOR_LOW = 2;
+    MEM_FACTOR_CRITICAL = 3;
+  }
+}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index a95fa57..522ff24 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "CpuInfoProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 /**
@@ -31,8 +29,6 @@
 message CpuInfo {
 
     message TaskStats {
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 total = 1;    // total number of cpu tasks
         optional int32 running = 2;  // number of running tasks
         optional int32 sleeping = 3; // number of sleeping tasks
@@ -42,8 +38,6 @@
     optional TaskStats task_stats = 1;
 
     message MemStats { // unit in kB
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 total = 1;
         optional int32 used = 2;
         optional int32 free = 3;
@@ -54,8 +48,6 @@
     optional MemStats swap = 3;
 
     message CpuUsage { // unit is percentage %
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 cpu = 1;   // 400% cpu indicates 4 cores
         optional int32 user = 2;
         optional int32 nice = 3;
@@ -70,8 +62,6 @@
 
     // Next Tag: 13
     message Task {
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 pid = 1;
         optional int32 tid = 2;
         optional string user = 3;
@@ -80,8 +70,6 @@
         optional float cpu = 6;     // precentage of cpu usage of the task
 
         enum Status {
-            option (stream_proto.stream_enum).enable_enums_mapping = true;
-
             STATUS_UNKNOWN = 0;
             STATUS_D = 1;  // uninterruptible sleep
             STATUS_R = 2;  // running
@@ -95,8 +83,6 @@
 
         // How Android memory manager will treat the task
         enum Policy {
-            option (stream_proto.stream_enum).enable_enums_mapping = true;
-
             POLICY_UNKNOWN = 0;
             POLICY_fg = 1;  // foreground, the name is lower case for parsing the value
             POLICY_bg = 2;  // background, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 4015a7e..ac8f26d 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -28,13 +28,14 @@
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
 import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
-import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/server/fingerprint.proto";
 import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/service/appwidget.proto";
 import "frameworks/base/core/proto/android/service/battery.proto";
 import "frameworks/base/core/proto/android/service/batterystats.proto";
 import "frameworks/base/core/proto/android/service/diskstats.proto";
+import "frameworks/base/core/proto/android/service/graphicsstats.proto";
 import "frameworks/base/core/proto/android/service/netstats.proto";
 import "frameworks/base/core/proto/android/service/notification.proto";
 import "frameworks/base/core/proto/android/service/package.proto";
@@ -176,6 +177,11 @@
 
     optional com.android.server.am.proto.MemInfoProto meminfo = 3018 [
         (section).type = SECTION_DUMPSYS,
-        (section).args = "meminfo --proto"
+        (section).args = "meminfo -a --proto"
+    ];
+
+    optional android.service.GraphicsStatsServiceDumpProto graphicsstats = 3019 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "graphicsstats --proto"
     ];
 }
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index eaad37a..7e5be9d 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "WakeupSourcesProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 message KernelWakeSources {
@@ -29,8 +27,6 @@
 
 // Next Tag: 11
 message WakeupSourceProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
-
     // Name of the event which triggers application processor
     optional string name = 1;
 
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index b86ee01..f82ea76 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "PageTypeInfoProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 /*
@@ -63,7 +61,6 @@
 
 // Next tag: 9
 message BlockProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
 
     optional int32 node = 1;
 
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index f684c84..204a5af 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -19,7 +19,6 @@
 option java_outer_classname = "ProcrankProto";
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
 
 package android.os;
 
@@ -36,7 +35,6 @@
 
 // Next Tag: 11
 message ProcessProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // ID of the process
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 76a108b..59582ec 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -19,14 +19,12 @@
 option java_multiple_files = true;
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
 
 package android.os;
 
 // Android Platform Exported System Properties
 // TODO: This is not the completed list, new properties need to be whitelisted.
 message SystemPropertiesProto {
-    option (stream_proto.stream_msg).enable_fields_mapping_recursively = true;
 
     // Properties that are not specified below would be appended here.
     // These values stay on device only.
@@ -329,7 +327,7 @@
         optional string crypto_state = 21;
         optional string crypto_type = 22;
         optional string dalvik_vm_native_bridge = 23;
-        optional int32  debuggable = 24;
+        optional bool  debuggable = 24;
         optional string frp_pst = 25;
         optional string gfx_driver_0 = 26;
 
@@ -415,19 +413,29 @@
         optional string revision = 35;
         optional int32  sf_lcd_density = 36;
         optional bool   storage_manager_enabled = 37;
-        optional bool   telephony_call_ring_multiple = 38;
-        optional int32  telephony_default_cdma_sub = 39;
-        optional int32  telephony_default_network = 40;
-        optional string url_legal = 41;
-        optional string url_legal_android_privacy = 42;
-        optional string vendor_build_date = 43;
-        optional int32  vendor_build_date_utc = 44;
-        optional string vendor_build_fingerprint = 45;
-        optional string vndk_version = 46;
-        optional int32  vts_coverage = 47;
-        optional string zygote = 48;
 
-        // Next Tag: 49
+        message Telephony {
+            optional bool  call_ring_multiple = 1;
+            optional int32 default_cdma_sub = 2;
+            optional int32 default_network = 3;
+        }
+        optional Telephony telephony = 38;
+
+        optional string url_legal = 39;
+        optional string url_legal_android_privacy = 40;
+
+        message Vendor {
+            optional string build_date = 1;
+            optional int32  build_date_utc = 2;
+            optional string build_fingerprint = 3;
+        }
+        optional Vendor vendor = 41;
+
+        optional string vndk_version = 42;
+        optional int32  vts_coverage = 43;
+        optional string zygote = 44;
+
+        // Next Tag: 45
     }
     optional Ro ro = 21;
 
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 3af6f51..d3ca496 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -21,6 +21,7 @@
 import "frameworks/base/core/proto/android/app/notification.proto";
 import "frameworks/base/core/proto/android/content/intent.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/internal/processstats.proto";
 import "frameworks/base/core/proto/android/os/looper.proto";
 import "frameworks/base/core/proto/android/server/intentresolver.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
@@ -163,7 +164,7 @@
   optional int64 uptime_duration_ms = 1;
   optional int64 elapsed_realtime_ms = 2;
 
-  message NativeProcess {
+  message ProcessMemory {
     optional int32 pid = 1;
     optional string process_name = 2;
 
@@ -197,7 +198,7 @@
     optional HeapInfo native_heap = 3;
     optional HeapInfo dalvik_heap = 4;
     repeated MemoryInfo other_heaps = 5;
-    optional HeapInfo unknown_heap = 6;
+    optional MemoryInfo unknown_heap = 6;
     // Summation of native_heap, dalvik_heap, and other_heaps.
     optional HeapInfo total_heap = 7;
 
@@ -219,7 +220,113 @@
     }
     optional AppSummary app_summary = 9;
   }
-  repeated NativeProcess native_processes = 3;
+  repeated ProcessMemory native_processes = 3;
+
+  message AppData {
+    optional ProcessMemory process_memory = 1;
+
+    message ObjectStats {
+      optional int32 view_instance_count = 1;
+      optional int32 view_root_instance_count = 2;
+      optional int32 app_context_instance_count = 3;
+      optional int32 activity_instance_count = 4;
+      optional int32 global_asset_count = 5;
+      optional int32 global_asset_manager_count = 6;
+      optional int32 local_binder_object_count = 7;
+      optional int32 proxy_binder_object_count = 8;
+      optional int64 parcel_memory_kb = 9;
+      optional int32 parcel_count = 10;
+      optional int32 binder_object_death_count = 11;
+      optional int32 open_ssl_socket_count = 12;
+      optional int32 webview_instance_count = 13;
+    }
+    optional ObjectStats objects = 2;
+
+    message SqlStats {
+      optional int32 memory_used_kb = 1;
+      optional int32 pagecache_overflow_kb = 2;
+      optional int32 malloc_size_kb = 3;
+
+      message Database {
+        optional string name = 1;
+        optional int32 page_size = 2;
+        optional int32 db_size = 3;
+        // Number of lookaside slots:
+        // http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
+        optional int32 lookaside_b = 4;
+        // Statement cache stats: hits/misses/cachesize
+        optional string cache = 5;
+      }
+      repeated Database databases = 4;
+    }
+    optional SqlStats sql = 3;
+
+    optional string asset_allocations = 4;
+    optional string unreachable_memory = 5;
+  }
+  repeated AppData app_processes = 4;
+
+  message MemItem {
+    optional string tag = 1;
+    optional string label = 2;
+    optional int32 id = 3;
+    optional bool is_proc = 4;
+    optional bool has_activities = 5;
+    optional int64 pss_kb = 6;
+    optional int64 swap_pss_kb = 7;
+    repeated MemItem sub_items = 8;
+  }
+  repeated MemItem total_pss_by_process = 5;
+  repeated MemItem total_pss_by_oom_adjustment = 6;
+  repeated MemItem total_pss_by_category = 7;
+
+  optional int64 total_ram_kb = 8;
+  optional .com.android.internal.app.procstats.ProcessStatsProto.MemoryFactor status = 9;
+  // Total free RAM = cached_pss_kb + cached_kernel_kb + free_kb.
+  optional int64 cached_pss_kb = 10;
+  optional int64 cached_kernel_kb = 11;
+  optional int64 free_kb = 12;
+  // Total used RAM = used_pss_kb + used_kernel_kb.
+  optional int64 used_pss_kb = 13;
+  optional int64 used_kernel_kb = 14;
+
+  optional int64 lost_ram_kb = 15;
+
+  optional int64 total_zram_kb = 16;
+  optional int64 zram_physical_used_in_swap_kb = 17;
+  optional int64 total_zram_swap_kb = 18;
+
+  optional int64 ksm_sharing_kb = 19;
+  optional int64 ksm_shared_kb = 20;
+  optional int64 ksm_unshared_kb = 21;
+  optional int64 ksm_volatile_kb = 22;
+
+  // The approximate per-application memory class of the current device. This
+  // gives developers an idea of how hard a memory limit you should impose on
+  // their application to let the overall system work best. The value is in
+  // megabytes; the baseline Android memory class is 16 (which happens to be the
+  // Java heap limit of those devices); some devices with more memory may have
+  // 24 or even higher numbers.
+  optional int32 tuning_mb = 23;
+  // The approximate per-application memory class of the current device when an
+  // application is running with a large heap. This is the space available for
+  // memory-intensive applications; most applications should not need this
+  // amount of memory, and should instead stay with the tuning_mb limit. The
+  // value is in megabytes. This may be the same size as tuning_mb on memory
+  // constrained devices, or it may be significantly larger on devices with a
+  // large amount of available RAM.
+  // This is the size of the application's Dalvik heap if it has specified
+  // 'android:largeHeap="true"' in its manifest.
+  optional int32 tuning_large_mb = 24;
+
+  optional int64 oom_kb = 25;
+
+  // The maximum pss size in kb that we consider a process acceptable to restore
+  // from its cached state for running in the background when RAM is low.
+  optional int64 restore_limit_kb = 26;
+
+  optional bool is_low_ram_device = 27;
+  optional bool is_high_end_gfx = 28;
 }
 
 message StickyBroadcastProto {
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 801411d..f422065 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -19,7 +19,6 @@
 
 option java_multiple_files = true;
 option java_outer_classname = "GraphicsStatsServiceProto";
-option optimize_for = LITE_RUNTIME;
 
 message GraphicsStatsServiceDumpProto {
     repeated GraphicsStatsProto stats = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 15e439e..272e3c7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1620,6 +1620,11 @@
     <permission android:name="android.permission.ACCESS_PDB_STATE"
         android:protectionLevel="signature" />
 
+    <!-- Allows testing if a passwords is forbidden by the admins.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows system update service to notify device owner about pending updates.
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
@@ -3500,6 +3505,7 @@
         @hide -->
     <permission android:name="android.permission.LOCAL_MAC_ADDRESS"
                 android:protectionLevel="signature|privileged" />
+    <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>
 
     <!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
         @hide -->
@@ -3528,11 +3534,19 @@
     @hide -->
     <permission android:name="android.permission.ACCESS_INSTANT_APPS"
             android:protectionLevel="signature|installer|verifier" />
+    <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
 
     <!-- Allows the holder to view the instant applications on the device.
     @hide -->
     <permission android:name="android.permission.VIEW_INSTANT_APPS"
-            android:protectionLevel="signature|preinstalled" />
+                android:protectionLevel="signature|preinstalled" />
+
+    <!-- Allows the holder to manage whether the system can bind to services
+         provided by instant apps. This permission is intended to protect
+         test/development fucntionality and should be used only in such cases.
+    @hide -->
+    <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+                android:protectionLevel="signature" />
 
     <!-- Allows receiving the usage of media resource e.g. video/audio codec and
          graphic memory.
@@ -3627,6 +3641,11 @@
     <permission android:name="android.permission.READ_RUNTIME_PROFILES"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to turn on / off quiet mode.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MODIFY_QUIET_MODE"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/drawable/ic_wifi_settings.xml b/core/res/res/drawable/ic_wifi_settings.xml
new file mode 100644
index 0000000..c678ad4
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_settings.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12.584,15.93c0.026-0.194,0.044-0.397,0.044-0.608c0-0.211-0.018-0.405-0.044-0.608l1.304-1.022
+c0.115-0.088,0.15-0.256,0.071-0.397l-1.234-2.133c-0.071-0.132-0.238-0.185-0.379-0.132l-1.533,0.617
+c-0.317-0.247-0.67-0.449-1.04-0.608L9.535,9.4c-0.018-0.132-0.141-0.247-0.3-0.247H6.768c-0.15,0-0.282,0.115-0.3,0.256
+L6.23,11.048c-0.379,0.159-0.723,0.361-1.04,0.608l-1.533-0.617c-0.141-0.053-0.3,0-0.379,0.132l-1.234,2.133
+c-0.079,0.132-0.044,0.3,0.07,0.397l1.304,1.022c-0.026,0.194-0.044,0.405-0.044,0.608s0.018,0.405,0.044,0.608l-1.304,1.022
+c-0.115,0.088-0.15,0.256-0.07,0.397l1.234,2.133c0.07,0.132,0.238,0.185,0.379,0.132l1.533-0.617
+c0.317,0.247,0.67,0.449,1.04,0.608l0.238,1.639c0.018,0.15,0.15,0.256,0.3,0.256h2.467c0.159,0,0.282-0.115,0.3-0.256
+l0.238-1.639c0.379-0.15,0.723-0.361,1.04-0.608l1.533,0.617c0.141,0.053,0.3,0,0.379-0.132l1.234-2.133
+c0.071-0.132,0.044-0.3-0.07-0.397L12.584,15.93z
+M8.002,17.481c-1.19,0-2.159-0.969-2.159-2.159s0.969-2.159,2.159-2.159
+s2.159,0.969,2.159,2.159C10.161,16.512,9.191,17.481,8.002,17.481z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M16.003,12.026l5.995-7.474c-0.229-0.172-2.537-2.06-6-2.06s-5.771,1.889-6,2.06l5.995,7.469l0.005,0.01L16.003,12.026z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/interpolator/aggressive_ease.xml b/core/res/res/interpolator/aggressive_ease.xml
new file mode 100644
index 0000000..620424f
--- /dev/null
+++ b/core/res/res/interpolator/aggressive_ease.xml
@@ -0,0 +1,22 @@
+<?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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.2"
+    android:controlY1="0"
+    android:controlX2="0"
+    android:controlY2="1"/>
\ No newline at end of file
diff --git a/core/res/res/interpolator/emphasized_deceleration.xml b/core/res/res/interpolator/emphasized_deceleration.xml
new file mode 100644
index 0000000..60c315c
--- /dev/null
+++ b/core/res/res/interpolator/emphasized_deceleration.xml
@@ -0,0 +1,22 @@
+<?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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.1"
+    android:controlY1="0.8"
+    android:controlX2="0.2"
+    android:controlY2="1"/>
\ No newline at end of file
diff --git a/core/res/res/interpolator/exaggerated_ease.xml b/core/res/res/interpolator/exaggerated_ease.xml
new file mode 100644
index 0000000..4961c1c
--- /dev/null
+++ b/core/res/res/interpolator/exaggerated_ease.xml
@@ -0,0 +1,19 @@
+<?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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.08, 0.166666, 0.4 C 0.225, 0.94, 0.25, 1, 1, 1"/>
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 865685f..435289d 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -72,7 +72,7 @@
                 android:textColor="#eeffffff"
                 android:layout_marginTop="4dp"
                 android:ellipsize="end"
-                android:maxLines="7"
+                android:maxLines="3"
             />
         </LinearLayout>
     </LinearLayout>
diff --git a/core/res/res/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml
index 3eecb36..3b89f0d 100644
--- a/core/res/res/layout/popup_menu_item_layout.xml
+++ b/core/res/res/layout/popup_menu_item_layout.xml
@@ -33,6 +33,7 @@
     <!-- The title and summary have some gap between them,
     and this 'group' should be centered vertically. -->
     <LinearLayout
+        android:id="@+id/content"
         android:layout_width="wrap_content"
         android:layout_height="?attr/dropdownListPreferredItemHeight"
         android:paddingEnd="16dip"
diff --git a/core/res/res/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
index d17437f..b501b06 100644
--- a/core/res/res/raw/color_fade_vert.vert
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -1,6 +1,5 @@
 uniform mat4 proj_matrix;
 uniform mat4 tex_matrix;
-uniform float scale;
 attribute vec2 position;
 attribute vec2 uv;
 varying vec2 UV;
@@ -9,5 +8,5 @@
 {
     vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
     UV = transformed_uv.st / transformed_uv.q;
-    gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+    gl_Position = proj_matrix * vec4(position.x, position.y, 0.0, 1.0);
 }
diff --git a/core/res/res/values-af/required_apps_managed_device.xml b/core/res/res/values-af/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-af/required_apps_managed_profile.xml b/core/res/res/values-af/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-af/required_apps_managed_user.xml b/core/res/res/values-af/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_device.xml b/core/res/res/values-am/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_profile.xml b/core/res/res/values-am/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_user.xml b/core/res/res/values-am/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_device.xml b/core/res/res/values-ar/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_profile.xml b/core/res/res/values-ar/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_user.xml b/core/res/res/values-ar/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c9611e4..2f69bd5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -66,15 +66,15 @@
     <string name="PwdMmi" msgid="7043715687905254199">"تغيير كلمة المرور"</string>
     <string name="PinMmi" msgid="3113117780361190304">"‏تغيير رمز PIN"</string>
     <string name="CnipMmi" msgid="3110534680557857162">"رقم الاتصال موجود"</string>
-    <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال مقيّد"</string>
+    <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال محظور"</string>
     <string name="ThreeWCMmi" msgid="9051047170321190368">"اتصال ثلاثي"</string>
     <string name="RuacMmi" msgid="7827887459138308886">"رفض المكالمات المزعجة غير المرغوب فيها"</string>
     <string name="CndMmi" msgid="3116446237081575808">"تسليم رقم الاتصال"</string>
     <string name="DndMmi" msgid="1265478932418334331">"عدم الإزعاج"</string>
-    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: مقيّد"</string>
-    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: غير مقيّد"</string>
-    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: مقيّد"</string>
-    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: غير مقيّد"</string>
+    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو محظور  . الاتصال التالي: محظور"</string>
+    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو محظور  . الاتصال التالي: غير محظور"</string>
+    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير محظور  . الاتصال التالي: محظور"</string>
+    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير محظور  . الاتصال التالي: غير محظور"</string>
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"الخدمة غير متوفرة."</string>
     <string name="CLIRPermanent" msgid="3377371145926835671">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
     <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"ليست هناك خدمة بيانات"</string>
diff --git a/core/res/res/values-az/required_apps_managed_device.xml b/core/res/res/values-az/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-az/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-az/required_apps_managed_profile.xml b/core/res/res/values-az/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-az/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-az/required_apps_managed_user.xml b/core/res/res/values-az/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-az/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_device.xml b/core/res/res/values-be/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_profile.xml b/core/res/res/values-be/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_user.xml b/core/res/res/values-be/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_device.xml b/core/res/res/values-bg/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_profile.xml b/core/res/res/values-bg/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_user.xml b/core/res/res/values-bg/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_device.xml b/core/res/res/values-bn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_profile.xml b/core/res/res/values-bn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_user.xml b/core/res/res/values-bn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 073fa12..fa3c9b1d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD অনুরোধটিকে SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS অনুরোধটিকে নতুন SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"কর্মস্থলের প্রোফাইল"</string>
diff --git a/core/res/res/values-bs/required_apps_managed_device.xml b/core/res/res/values-bs/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bs/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bs/required_apps_managed_profile.xml b/core/res/res/values-bs/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bs/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-bs/required_apps_managed_user.xml b/core/res/res/values-bs/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bs/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_device.xml b/core/res/res/values-ca/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_profile.xml b/core/res/res/values-ca/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_user.xml b/core/res/res/values-ca/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_device.xml b/core/res/res/values-cs/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_profile.xml b/core/res/res/values-cs/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_user.xml b/core/res/res/values-cs/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_device.xml b/core/res/res/values-da/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_profile.xml b/core/res/res/values-da/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_user.xml b/core/res/res/values-da/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_device.xml b/core/res/res/values-de/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_profile.xml b/core/res/res/values-de/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_user.xml b/core/res/res/values-de/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_device.xml b/core/res/res/values-el/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_profile.xml b/core/res/res/values-el/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_user.xml b/core/res/res/values-el/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rAU/required_apps_managed_device.xml b/core/res/res/values-en-rAU/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rAU/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rAU/required_apps_managed_profile.xml b/core/res/res/values-en-rAU/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rAU/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rAU/required_apps_managed_user.xml b/core/res/res/values-en-rAU/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rAU/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -212,6 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rCA/required_apps_managed_device.xml b/core/res/res/values-en-rCA/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rCA/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rCA/required_apps_managed_profile.xml b/core/res/res/values-en-rCA/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rCA/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rCA/required_apps_managed_user.xml b/core/res/res/values-en-rCA/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rCA/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -212,6 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rGB/required_apps_managed_device.xml b/core/res/res/values-en-rGB/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rGB/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rGB/required_apps_managed_profile.xml b/core/res/res/values-en-rGB/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rGB/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rGB/required_apps_managed_user.xml b/core/res/res/values-en-rGB/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rGB/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -212,6 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rIN/required_apps_managed_device.xml b/core/res/res/values-en-rIN/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rIN/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rIN/required_apps_managed_profile.xml b/core/res/res/values-en-rIN/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rIN/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rIN/required_apps_managed_user.xml b/core/res/res/values-en-rIN/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rIN/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -212,6 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rXC/required_apps_managed_device.xml b/core/res/res/values-en-rXC/required_apps_managed_device.xml
new file mode 100644
index 0000000..70d510f
--- /dev/null
+++ b/core/res/res/values-en-rXC/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎com.android.settings‎‏‎‎‏‎"</item>
+    <item msgid="7004798084799227194">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎com.android.contacts‎‏‎‎‏‎"</item>
+    <item msgid="5782220690863647256">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‎com.android.dialer‎‏‎‎‏‎"</item>
+    <item msgid="5746338511138092673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎com.android.stk‎‏‎‎‏‎"</item>
+    <item msgid="1712599182168590664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎com.android.providers.downloads‎‏‎‎‏‎"</item>
+    <item msgid="2858239953396384085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎com.android.providers.downloads.ui‎‏‎‎‏‎"</item>
+    <item msgid="3892021562839042708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‎com.android.documentsui‎‏‎‎‏‎"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rXC/required_apps_managed_profile.xml b/core/res/res/values-en-rXC/required_apps_managed_profile.xml
new file mode 100644
index 0000000..9ca9960
--- /dev/null
+++ b/core/res/res/values-en-rXC/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎com.android.contacts‎‏‎‎‏‎"</item>
+    <item msgid="4633145750237794002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎com.android.settings‎‏‎‎‏‎"</item>
+    <item msgid="6518205098643077579">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎com.android.providers.downloads‎‏‎‎‏‎"</item>
+    <item msgid="9003577256117829525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎com.android.providers.downloads.ui‎‏‎‎‏‎"</item>
+    <item msgid="6106837921940099371">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎com.android.documentsui‎‏‎‎‏‎"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rXC/required_apps_managed_user.xml b/core/res/res/values-en-rXC/required_apps_managed_user.xml
new file mode 100644
index 0000000..b2ef802
--- /dev/null
+++ b/core/res/res/values-en-rXC/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‏‎com.android.settings‎‏‎‎‏‎"</item>
+    <item msgid="2250259015310893915">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎com.android.contacts‎‏‎‎‏‎"</item>
+    <item msgid="7166574999426592423">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎com.android.dialer‎‏‎‎‏‎"</item>
+    <item msgid="7306937186458176744">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‎com.android.stk‎‏‎‎‏‎"</item>
+    <item msgid="7415441588151512455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎com.android.providers.downloads‎‏‎‎‏‎"</item>
+    <item msgid="2277950048461066377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎com.android.providers.downloads.ui‎‏‎‎‏‎"</item>
+    <item msgid="8640522622655589373">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎com.android.documentsui‎‏‎‎‏‎"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 5635292..22da8ef 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -212,6 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎Power off‎‏‎‎‏‎"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎Emergency‎‏‎‎‏‎"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎Bug report‎‏‎‎‏‎"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎End session‎‏‎‎‏‎"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎Take bug report‎‏‎‎‏‎"</string>
     <string name="bugreport_message" msgid="398447048750350456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient.‎‏‎‎‏‎"</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎Interactive report‎‏‎‎‏‎"</string>
@@ -1498,6 +1499,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ Current accessibility feature:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ You can change the feature in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎Turn off Shortcut‎‏‎‎‏‎"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎Use Shortcut‎‏‎‎‏‎"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎Color Inversion‎‏‎‎‏‎"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎Color Correction‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎Accessibility Shortcut turned ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ on‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎Accessibility Shortcut turned ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ off‎‏‎‎‏‎"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎Choose a feature to use when you tap the Accessibility button:‎‏‎‎‏‎"</string>
@@ -1684,13 +1687,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎Weeknight‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎Weekend‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎Event‎‏‎‎‏‎"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎Sleeping‎‏‎‎‏‎"</string>
     <string name="muted_by" msgid="6147073845094180001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎Muted by ‎‏‎‎‏‏‎<xliff:g id="THIRD_PARTY">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎There\'s an internal problem with your device, and it may be unstable until you factory data reset.‎‏‎‎‏‎"</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎There\'s an internal problem with your device. Contact your manufacturer for details.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎USSD request is modified to DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎USSD request is modified to SS request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‎USSD request is modified to new USSD request.‎‏‎‎‏‎"</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎USSD request is modified to Video DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎SS request is modified to DIAL request.‎‏‎‎‏‎"</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎SS request is modified to Video DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎SS request is modified to USSD request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎SS request is modified to new SS request.‎‏‎‎‏‎"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎Work profile‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_device.xml b/core/res/res/values-es-rUS/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_profile.xml b/core/res/res/values-es-rUS/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_user.xml b/core/res/res/values-es-rUS/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-es/required_apps_managed_device.xml b/core/res/res/values-es/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-es/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-es/required_apps_managed_profile.xml b/core/res/res/values-es/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-es/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-es/required_apps_managed_user.xml b/core/res/res/values-es/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-es/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_device.xml b/core/res/res/values-et/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_profile.xml b/core/res/res/values-et/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_user.xml b/core/res/res/values-et/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_device.xml b/core/res/res/values-eu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_profile.xml b/core/res/res/values-eu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_user.xml b/core/res/res/values-eu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_device.xml b/core/res/res/values-fa/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_profile.xml b/core/res/res/values-fa/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_user.xml b/core/res/res/values-fa/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_device.xml b/core/res/res/values-fi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_profile.xml b/core/res/res/values-fi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_user.xml b/core/res/res/values-fi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_device.xml b/core/res/res/values-fr-rCA/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_profile.xml b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_user.xml b/core/res/res/values-fr-rCA/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr/required_apps_managed_device.xml b/core/res/res/values-fr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr/required_apps_managed_profile.xml b/core/res/res/values-fr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-fr/required_apps_managed_user.xml b/core/res/res/values-fr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_device.xml b/core/res/res/values-gl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_profile.xml b/core/res/res/values-gl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_user.xml b/core/res/res/values-gl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index b5aab39..b4e3904 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -977,7 +977,7 @@
     <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto"</string>
     <string name="addToDictionary" msgid="4352161534510057874">"Engadir ao dicionario"</string>
     <string name="deleteText" msgid="6979668428458199034">"Eliminar"</string>
-    <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
+    <string name="inputMethod" msgid="1653630062304567879">"Método de introdución de texto"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
     <string name="dial" msgid="1253998302767701559">"Chamar"</string>
@@ -1288,7 +1288,7 @@
     <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"Permiso solicitado\npara a conta <xliff:g id="ACCOUNT">%s</xliff:g>."</string>
     <string name="forward_intent_to_owner" msgid="1207197447013960896">"Estás usando esta aplicación fóra do teu perfil de traballo"</string>
     <string name="forward_intent_to_work" msgid="621480743856004612">"Estás usando esta aplicación no teu perfil de traballo"</string>
-    <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string>
+    <string name="input_method_binding_label" msgid="1283557179944992649">"Método de introdución de texto"</string>
     <string name="sync_binding_label" msgid="3687969138375092423">"Sincronizar"</string>
     <string name="accessibility_binding_label" msgid="4148120742096474641">"Accesibilidade"</string>
     <string name="wallpaper_binding_label" msgid="1240087844304687662">"Fondo de pantalla"</string>
diff --git a/core/res/res/values-gu/required_apps_managed_device.xml b/core/res/res/values-gu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_profile.xml b/core/res/res/values-gu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_user.xml b/core/res/res/values-gu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index cff1a85..eada633 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD વિનંતીને SS વિનંતી પર સંશોધિત કરી."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD વિનંતીને નવી USSD વિનંતી પર સંશોધિત કરી."</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS વિનંતીને USSD વિનંતી પર સંશોધિત કરી."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS વિનંતીને નવી SS વિનંતી પર સંશોધિત કરી."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"કાર્યાલયની પ્રોફાઇલ"</string>
diff --git a/core/res/res/values-hi/required_apps_managed_device.xml b/core/res/res/values-hi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_profile.xml b/core/res/res/values-hi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_user.xml b/core/res/res/values-hi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_device.xml b/core/res/res/values-hr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_profile.xml b/core/res/res/values-hr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_user.xml b/core/res/res/values-hr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_device.xml b/core/res/res/values-hu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_profile.xml b/core/res/res/values-hu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_user.xml b/core/res/res/values-hu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_device.xml b/core/res/res/values-hy/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_profile.xml b/core/res/res/values-hy/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_user.xml b/core/res/res/values-hy/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-in/required_apps_managed_device.xml b/core/res/res/values-in/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-in/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-in/required_apps_managed_profile.xml b/core/res/res/values-in/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-in/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-in/required_apps_managed_user.xml b/core/res/res/values-in/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-in/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_device.xml b/core/res/res/values-is/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_profile.xml b/core/res/res/values-is/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_user.xml b/core/res/res/values-is/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-it/required_apps_managed_device.xml b/core/res/res/values-it/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-it/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-it/required_apps_managed_profile.xml b/core/res/res/values-it/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-it/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-it/required_apps_managed_user.xml b/core/res/res/values-it/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-it/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 2116542..7e96aff 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1694,7 +1694,7 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"La richiesta USSD è stata modificata in richiesta SS."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"La richiesta USSD è stata modificata in nuova richiesta USSD."</string>
-    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta Video DIAL."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"La richiesta SS è stata modificata in richiesta DIAL."</string>
     <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"La richiesta SS è stata modificata in richiesta Video DIAL."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"La richiesta SS è stata modificata in richiesta USSD."</string>
diff --git a/core/res/res/values-iw/required_apps_managed_device.xml b/core/res/res/values-iw/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_profile.xml b/core/res/res/values-iw/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_user.xml b/core/res/res/values-iw/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_device.xml b/core/res/res/values-ja/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_profile.xml b/core/res/res/values-ja/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_user.xml b/core/res/res/values-ja/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ka/required_apps_managed_device.xml b/core/res/res/values-ka/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ka/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ka/required_apps_managed_profile.xml b/core/res/res/values-ka/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ka/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ka/required_apps_managed_user.xml b/core/res/res/values-ka/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ka/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_device.xml b/core/res/res/values-kk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_profile.xml b/core/res/res/values-kk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_user.xml b/core/res/res/values-kk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_device.xml b/core/res/res/values-km/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_profile.xml b/core/res/res/values-km/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_user.xml b/core/res/res/values-km/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index b2778a1..7be15f1 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1696,9 +1696,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS។"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD ថ្មី។្"</string>
-    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string>
-    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD។"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS ថ្មី។"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ប្រវត្តិរូបការងារ"</string>
diff --git a/core/res/res/values-kn/required_apps_managed_device.xml b/core/res/res/values-kn/required_apps_managed_device.xml
new file mode 100644
index 0000000..e15b0b0
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
+    <item msgid="7004798084799227194">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+    <item msgid="5782220690863647256">"com.android.ಡಯಲರ್"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_profile.xml b/core/res/res/values-kn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..92ab682
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+    <item msgid="4633145750237794002">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
+    <item msgid="6518205098643077579">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_user.xml b/core/res/res/values-kn/required_apps_managed_user.xml
new file mode 100644
index 0000000..0c59edd
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
+    <item msgid="2250259015310893915">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+    <item msgid="7166574999426592423">"com.android.ಡಯಲರ್"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 4485561..99db9e6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ವಿನಂತಿಯನ್ನು SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ವಿನಂತಿಯನ್ನು ಹೊಸ USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ವಿನಂತಿಯನ್ನು USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ವಿನಂತಿಯನ್ನು ಹೊಸ SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string>
diff --git a/core/res/res/values-ko/required_apps_managed_device.xml b/core/res/res/values-ko/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_profile.xml b/core/res/res/values-ko/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_user.xml b/core/res/res/values-ko/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_device.xml b/core/res/res/values-ky/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_profile.xml b/core/res/res/values-ky/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_user.xml b/core/res/res/values-ky/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index e580a59..cfa05de 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -212,7 +212,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Кубатын өчүрүү"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Тез жардам"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Ката тууралуу билдирүү"</string>
-    <string name="global_action_logout" msgid="935179188218826050">"Сессияны аяктоо"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"Сеансты бүтүрүү"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Ката тууралуу билдирүү түзүү"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Ушуну менен түзмөгүңүздүн учурдагы абалы тууралуу маалымат топтолуп, электрондук почта аркылуу жөнөтүлөт. Отчет даяр болгуча бир аз күтө туруңуз."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Интерактивдүү кабар"</string>
@@ -1502,7 +1502,7 @@
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Кыска жолду өчүрүү"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Кыска жолду колдонуу"</string>
     <string name="color_inversion_feature_name" msgid="4231186527799958644">"Түстү инверсиялоо"</string>
-    <string name="color_correction_feature_name" msgid="6779391426096954933">"Түстү түзөтүү"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Түсүн тууралоо"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын күйгүздү"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын өчүрдү"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Атайын мүмкүнчүлүктөр баскычын таптаганыңызда иштетиле турган функцияны тандаңыз:"</string>
diff --git a/core/res/res/values-lo/required_apps_managed_device.xml b/core/res/res/values-lo/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_profile.xml b/core/res/res/values-lo/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_user.xml b/core/res/res/values-lo/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_device.xml b/core/res/res/values-lt/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_profile.xml b/core/res/res/values-lt/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_user.xml b/core/res/res/values-lt/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_device.xml b/core/res/res/values-lv/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_profile.xml b/core/res/res/values-lv/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_user.xml b/core/res/res/values-lv/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_device.xml b/core/res/res/values-mk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_profile.xml b/core/res/res/values-mk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_user.xml b/core/res/res/values-mk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_device.xml b/core/res/res/values-ml/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_profile.xml b/core/res/res/values-ml/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_user.xml b/core/res/res/values-ml/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 2595f94..fc958fa 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD അഭ്യർത്ഥന, SS അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD അഭ്യർത്ഥന, പുതിയ USSD അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS അഭ്യർത്ഥന, USSD അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS അഭ്യർത്ഥന, പുതിയ SS അഭ്യർത്ഥനയായി പരിഷ്‌ക്കരിച്ചു."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
diff --git a/core/res/res/values-mn/required_apps_managed_device.xml b/core/res/res/values-mn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_profile.xml b/core/res/res/values-mn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_user.xml b/core/res/res/values-mn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_device.xml b/core/res/res/values-mr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_profile.xml b/core/res/res/values-mr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_user.xml b/core/res/res/values-mr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_device.xml b/core/res/res/values-ms/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_profile.xml b/core/res/res/values-ms/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_user.xml b/core/res/res/values-ms/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-my/required_apps_managed_device.xml b/core/res/res/values-my/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-my/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-my/required_apps_managed_profile.xml b/core/res/res/values-my/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-my/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-my/required_apps_managed_user.xml b/core/res/res/values-my/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-my/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_device.xml b/core/res/res/values-nb/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_profile.xml b/core/res/res/values-nb/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_user.xml b/core/res/res/values-nb/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_device.xml b/core/res/res/values-ne/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_profile.xml b/core/res/res/values-ne/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_user.xml b/core/res/res/values-ne/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 9223c76..f0a45a3 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1232,8 +1232,8 @@
     <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> त्रुटिपूर्ण छ। समाधान गर्न ट्याप गर्नुहोस्।"</string>
     <string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> बिग्रेको छ। समाधान गर्न चयन गर्नुहोस्।"</string>
     <string name="ext_media_unsupported_notification_title" msgid="3797642322958803257">"असमर्थित <xliff:g id="NAME">%s</xliff:g>"</string>
-    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न ट्याप गर्नुहोस्।"</string>
-    <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न चयन गर्नुहोस्।"</string>
+    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न ट्याप गर्नुहोस्।"</string>
+    <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न चयन गर्नुहोस्।"</string>
     <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> अप्रत्याशित रूपमा निकालियो"</string>
     <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"डेटा हराउनबाट जोगाउन निकाल्नु अघि <xliff:g id="NAME">%s</xliff:g> अनमाउन्ट गर्नुहोस्"</string>
     <string name="ext_media_nomedia_notification_title" msgid="1704840188641749091">"निकालियो <xliff:g id="NAME">%s</xliff:g>"</string>
@@ -1318,7 +1318,7 @@
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"कार मोड सक्षम पारियो।"</string>
     <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"कार मोडबाट बाहिर निस्कन ट्याप गर्नुहोस्।"</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"टेथर गर्ने वा हटस्पट सक्रिय"</string>
-    <string name="tethered_notification_message" msgid="2113628520792055377">"सेट अप गर्न ट्याप गर्नुहोस्।"</string>
+    <string name="tethered_notification_message" msgid="2113628520792055377">"सेटअप गर्न ट्याप गर्नुहोस्।"</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"टेदरिङलाई असक्षम पारिएको छ"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्"</string>
     <string name="back_button_label" msgid="2300470004503343439">"पछाडि"</string>
diff --git a/core/res/res/values-nl/required_apps_managed_device.xml b/core/res/res/values-nl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-nl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-nl/required_apps_managed_profile.xml b/core/res/res/values-nl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-nl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-nl/required_apps_managed_user.xml b/core/res/res/values-nl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-nl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_device.xml b/core/res/res/values-pa/required_apps_managed_device.xml
new file mode 100644
index 0000000..faadc50
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.download"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_profile.xml b/core/res/res/values-pa/required_apps_managed_profile.xml
new file mode 100644
index 0000000..537a80c
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.download"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_user.xml b/core/res/res/values-pa/required_apps_managed_user.xml
new file mode 100644
index 0000000..e69bbbc
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.download"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 283701e..b5b6a00 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ਬੇਨਤੀ SS ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ਬੇਨਤੀ ਨਵੀਂ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ਬੇਨਤੀ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ਬੇਨਤੀ ਨਵੀਂ SS ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
diff --git a/core/res/res/values-pl/required_apps_managed_device.xml b/core/res/res/values-pl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_profile.xml b/core/res/res/values-pl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_user.xml b/core/res/res/values-pl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rBR/required_apps_managed_device.xml b/core/res/res/values-pt-rBR/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt-rBR/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rBR/required_apps_managed_profile.xml b/core/res/res/values-pt-rBR/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt-rBR/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rBR/required_apps_managed_user.xml b/core/res/res/values-pt-rBR/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt-rBR/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_device.xml b/core/res/res/values-pt-rPT/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_profile.xml b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_user.xml b/core/res/res/values-pt-rPT/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 434b50b..dc2d6e4 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -381,7 +381,7 @@
     <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu tablet e partilhar ou guardar os dados do calendário."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"Esta aplicação pode ler todos os eventos do calendário armazenados na sua TV e partilhar ou guardar os dados do calendário."</string>
     <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu telemóvel e partilhar ou guardar os dados do calendário."</string>
-    <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar e-mail a convidados sem o conhecimento dos proprietários"</string>
+    <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar email a convidados sem o conhecimento dos proprietários"</string>
     <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu tablet. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
     <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário na sua TV. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
     <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu telemóvel. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
@@ -756,7 +756,7 @@
     <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Desbloqueio da conta"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Demasiadas tentativas para desenhar sequência"</string>
     <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"Para desbloquear, inicie sessão com a Conta Google."</string>
-    <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (e-mail)"</string>
+    <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (email)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Palavra-passe"</string>
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Iniciar sessão"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de utilizador ou palavra-passe inválidos."</string>
diff --git a/core/res/res/values-pt/required_apps_managed_device.xml b/core/res/res/values-pt/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt/required_apps_managed_profile.xml b/core/res/res/values-pt/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-pt/required_apps_managed_user.xml b/core/res/res/values-pt/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_device.xml b/core/res/res/values-ro/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_profile.xml b/core/res/res/values-ro/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_user.xml b/core/res/res/values-ro/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ru/required_apps_managed_device.xml b/core/res/res/values-ru/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ru/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ru/required_apps_managed_profile.xml b/core/res/res/values-ru/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ru/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ru/required_apps_managed_user.xml b/core/res/res/values-ru/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ru/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-si/required_apps_managed_device.xml b/core/res/res/values-si/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-si/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-si/required_apps_managed_profile.xml b/core/res/res/values-si/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-si/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-si/required_apps_managed_user.xml b/core/res/res/values-si/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-si/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_device.xml b/core/res/res/values-sk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_profile.xml b/core/res/res/values-sk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_user.xml b/core/res/res/values-sk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_device.xml b/core/res/res/values-sl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_profile.xml b/core/res/res/values-sl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_user.xml b/core/res/res/values-sl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_device.xml b/core/res/res/values-sq/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_profile.xml b/core/res/res/values-sq/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_user.xml b/core/res/res/values-sq/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_device.xml b/core/res/res/values-sr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_profile.xml b/core/res/res/values-sr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_user.xml b/core/res/res/values-sr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_device.xml b/core/res/res/values-sv/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_profile.xml b/core/res/res/values-sv/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_user.xml b/core/res/res/values-sv/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_device.xml b/core/res/res/values-sw/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_profile.xml b/core/res/res/values-sw/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_user.xml b/core/res/res/values-sw/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_device.xml b/core/res/res/values-ta/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_profile.xml b/core/res/res/values-ta/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_user.xml b/core/res/res/values-ta/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index c1526f3..c517223 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD கோரிக்கையானது SS கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD கோரிக்கையானது புதிய USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS கோரிக்கையானது USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS கோரிக்கையானது புதிய SS கோரிக்கைக்கு மாற்றப்பட்டது."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"பணி சுயவிவரம்"</string>
diff --git a/core/res/res/values-te/required_apps_managed_device.xml b/core/res/res/values-te/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-te/required_apps_managed_profile.xml b/core/res/res/values-te/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-te/required_apps_managed_user.xml b/core/res/res/values-te/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index a96bcaf..fd103bc 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD అభ్యర్థన SS అభ్యర్థనగా సవరించబడింది."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD అభ్యర్థన కొత్త USSD అభ్యర్థనగా సవరించబడింది."</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS అభ్యర్థన USSD అభ్యర్థనగా సవరించబడింది."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS అభ్యర్థన కొత్త SS అభ్యర్థనగా సవరించబడింది."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"కార్యాలయ ప్రొఫైల్‌"</string>
diff --git a/core/res/res/values-th/required_apps_managed_device.xml b/core/res/res/values-th/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-th/required_apps_managed_profile.xml b/core/res/res/values-th/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-th/required_apps_managed_user.xml b/core/res/res/values-th/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_device.xml b/core/res/res/values-tl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_profile.xml b/core/res/res/values-tl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_user.xml b/core/res/res/values-tl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_device.xml b/core/res/res/values-tr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_profile.xml b/core/res/res/values-tr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_user.xml b/core/res/res/values-tr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-uk/required_apps_managed_device.xml b/core/res/res/values-uk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-uk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-uk/required_apps_managed_profile.xml b/core/res/res/values-uk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-uk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-uk/required_apps_managed_user.xml b/core/res/res/values-uk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-uk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_device.xml b/core/res/res/values-ur/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_profile.xml b/core/res/res/values-ur/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_user.xml b/core/res/res/values-ur/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 2155b34..2771432 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1695,11 +1695,9 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"‏USSD درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"‏USSD درخواست میں ترمیم کر کے SS درخواست بنا دی گئی ہے۔"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"‏USSD درخواست میں ترمیم کر کے نئی USSD درخواست بنا دی گئی ہے۔"</string>
-    <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
-    <skip />
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"‏USSD درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"‏SS درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string>
-    <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
-    <skip />
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"‏SS درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"‏SS درخواست میں ترمیم کر کے USSD درخواست بنا دی گئی ہے۔"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"‏SS درخواست میں ترمیم کر کے نئی SS درخواست بنا دی گئی ہے۔"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"دفتری پروفائل"</string>
diff --git a/core/res/res/values-uz/required_apps_managed_device.xml b/core/res/res/values-uz/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_profile.xml b/core/res/res/values-uz/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_user.xml b/core/res/res/values-uz/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_device.xml b/core/res/res/values-vi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_profile.xml b/core/res/res/values-vi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_user.xml b/core/res/res/values-vi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_device.xml b/core/res/res/values-zh-rCN/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_profile.xml b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_user.xml b/core/res/res/values-zh-rCN/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_device.xml b/core/res/res/values-zh-rHK/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_profile.xml b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_user.xml b/core/res/res/values-zh-rHK/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_device.xml b/core/res/res/values-zh-rTW/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_profile.xml b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_user.xml b/core/res/res/values-zh-rTW/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zu/required_apps_managed_device.xml b/core/res/res/values-zu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_device">
+    <item msgid="1104492179978792509">"com.android.settings"</item>
+    <item msgid="7004798084799227194">"com.android.contacts"</item>
+    <item msgid="5782220690863647256">"com.android.dialer"</item>
+    <item msgid="5746338511138092673">"com.android.stk"</item>
+    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+    <item msgid="3892021562839042708">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zu/required_apps_managed_profile.xml b/core/res/res/values-zu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_profile">
+    <item msgid="1457364287544474838">"com.android.contacts"</item>
+    <item msgid="4633145750237794002">"com.android.settings"</item>
+    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+    <item msgid="6106837921940099371">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values-zu/required_apps_managed_user.xml b/core/res/res/values-zu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="required_apps_managed_user">
+    <item msgid="4823915868435007499">"com.android.settings"</item>
+    <item msgid="2250259015310893915">"com.android.contacts"</item>
+    <item msgid="7166574999426592423">"com.android.dialer"</item>
+    <item msgid="7306937186458176744">"com.android.stk"</item>
+    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+    <item msgid="8640522622655589373">"com.android.documentsui"</item>
+  </string-array>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 51ffa60..64febf1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4442,7 +4442,7 @@
         <attr name="textColor" />
         <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
         <attr name="textSize" />
-        <!-- Style (bold, italic, bolditalic) for the text. -->
+        <!-- Style (normal, bold, italic, bold|italic) for the text. -->
         <attr name="textStyle" />
         <!-- Typeface (normal, sans, serif, monospace) for the text. -->
         <attr name="typeface" />
@@ -4527,7 +4527,7 @@
         <attr name="textScaleX" format="float" />
         <!-- Typeface (normal, sans, serif, monospace) for the text. -->
         <attr name="typeface" />
-        <!-- Style (bold, italic, bolditalic) for the text. -->
+        <!-- Style (normal, bold, italic, bold|italic) for the text. -->
         <attr name="textStyle" />
         <!-- Font family (named by string or as a font resource reference) for the text. -->
         <attr name="fontFamily" />
@@ -7729,6 +7729,10 @@
              wallpaper. -->
         <attr name="showMetadataInPreview" format="boolean" />
 
+        <!-- Wallpapers optimized and capable of drawing in ambient mode will return true.
+            @hide -->
+        <attr name="supportsAmbientMode" format="boolean" />
+
     </declare-styleable>
 
     <!-- Use <code>dream</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5ccaf5c..5783435 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3014,6 +3014,13 @@
     <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
     <string name="wifi_available_action_all_networks">All Networks</string>
 
+    <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. -->
+    <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string>
+    <!--Notification subtext for Wi-Fi Wake onboarding.-->
+    <string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string>
+    <!--Notification action to disable Wi-Fi Wake during onboarding.-->
+    <string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string>
+
     <!-- A notification is shown when a wifi captive portal network is detected.  This is the notification's title. -->
     <string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b2424f..8e391d3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1308,6 +1308,7 @@
   <java-symbol type="drawable" name="platlogo" />
   <java-symbol type="drawable" name="stat_notify_sync_error" />
   <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+  <java-symbol type="drawable" name="ic_wifi_settings" />
   <java-symbol type="drawable" name="ic_wifi_signal_0" />
   <java-symbol type="drawable" name="ic_wifi_signal_1" />
   <java-symbol type="drawable" name="ic_wifi_signal_2" />
@@ -1573,6 +1574,8 @@
   <java-symbol type="anim" name="voice_activity_close_enter" />
   <java-symbol type="anim" name="voice_activity_open_exit" />
   <java-symbol type="anim" name="voice_activity_open_enter" />
+  <java-symbol type="anim" name="activity_open_exit" />
+  <java-symbol type="anim" name="activity_open_enter" />
 
   <java-symbol type="array" name="config_autoRotationTiltTolerance" />
   <java-symbol type="array" name="config_keyboardTapVibePattern" />
@@ -1905,6 +1908,9 @@
   <java-symbol type="string" name="wifi_available_content_failed_to_connect" />
   <java-symbol type="string" name="wifi_available_action_connect" />
   <java-symbol type="string" name="wifi_available_action_all_networks" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_title" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
   <java-symbol type="string" name="accessibility_binding_label" />
   <java-symbol type="string" name="adb_active_notification_message" />
   <java-symbol type="string" name="adb_active_notification_title" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3f2a46a..e094772 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1051,7 +1051,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".menus.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
+        <activity android:name="android.view.menu.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
                 android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
index 0cb5498..1e6bdc6 100644
--- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -17,9 +17,15 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.coretests.apps.bstatstestapp">
 
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
+
     <application>
         <activity android:name=".TestActivity"
                   android:exported="true" />
+        <service android:name=".TestService"
+                  android:exported="true" />
         <service android:name=".IsolatedTestService"
                  android:exported="true"
                  android:isolatedProcess="true" />
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
new file mode 100644
index 0000000..2601f35
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+public class BaseCmdReceiver extends ICmdReceiver.Stub {
+    @Override
+    public void doSomeWork(int durationMs) {}
+    @Override
+    public void showApplicationOverlay() throws RemoteException {}
+    @Override
+    public void finishHost() {}
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
new file mode 100644
index 0000000..d192fbd
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.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.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class Common {
+    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+    public static void doSomeWork(int durationMs) {
+        final long endTime = SystemClock.currentThreadTimeMillis() + durationMs;
+        double x;
+        double y;
+        double z;
+        while (SystemClock.currentThreadTimeMillis() <= endTime) {
+            x = 0.02;
+            x *= 1000;
+            y = x % 5;
+            z = Math.sqrt(y / 100);
+        }
+    }
+
+    public static void notifyLaunched(Intent intent, IBinder binder, String tag) {
+        if (intent == null) {
+            return;
+        }
+
+        final Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return;
+        }
+        final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+                extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+        try {
+            callback.onLaunched(binder);
+        } catch (RemoteException e) {
+            Log.e(tag, "Error occured while notifying the test: " + e);
+        }
+    }
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
index 1f5f397..892f60e 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -15,17 +15,14 @@
  */
 package com.android.coretests.apps.bstatstestapp;
 
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.Process;
-import android.os.SystemClock;
 import android.util.Log;
 
 public class IsolatedTestService extends Service {
-    private static final String TAG = IsolatedTestService.class.getName();
+    private static final String TAG = IsolatedTestService.class.getSimpleName();
 
     @Override
     public void onCreate() {
@@ -34,23 +31,20 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind called. myUid=" + Process.myUid());
         return mReceiver.asBinder();
     }
 
-    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+    }
+
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
         @Override
         public void doSomeWork(int durationMs) {
-            final long endTime = SystemClock.uptimeMillis() + durationMs;
-            double x;
-            double y;
-            double z;
-            while (SystemClock.uptimeMillis() <= endTime) {
-                x = 0.02;
-                x *= 1000;
-                y = x % 5;
-                z = Math.sqrt(y / 100);
-            }
-        };
+            Common.doSomeWork(durationMs);
+        }
 
         @Override
         public void finishHost() {
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
index 87b14d9..5c551d5 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -15,19 +15,12 @@
  */
 package com.android.coretests.apps.bstatstestapp;
 
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
 import android.app.Activity;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 
 public class TestActivity extends Activity {
-    private static final String TAG = TestActivity.class.getName();
-
-    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+    private static final String TAG = TestActivity.class.getSimpleName();
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -37,21 +30,7 @@
     }
 
     private void notifyActivityLaunched() {
-        if (getIntent() == null) {
-            return;
-        }
-
-        final Bundle extras = getIntent().getExtras();
-        if (extras == null) {
-            return;
-        }
-        final ICmdCallback callback = ICmdCallback.Stub.asInterface(
-                extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
-        try {
-            callback.onActivityLaunched(mReceiver.asBinder());
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error occured while notifying the test: " + e);
-        }
+        Common.notifyLaunched(getIntent(), mReceiver.asBinder(), TAG);
     }
 
     @Override
@@ -60,24 +39,17 @@
         Log.d(TAG, "finish called");
     }
 
-    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
         @Override
         public void doSomeWork(int durationMs) {
-            final long endTime = SystemClock.uptimeMillis() + durationMs;
-            double x;
-            double y;
-            double z;
-            while (SystemClock.uptimeMillis() <= endTime) {
-                x = 0.02;
-                x *= 1000;
-                y = x % 5;
-                z = Math.sqrt(y / 100);
-            }
-        };
+            Common.doSomeWork(durationMs);
+        }
 
         @Override
         public void finishHost() {
-            finish();
+            if (!isFinishing()) {
+                finish();
+            }
         }
     };
 }
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
new file mode 100644
index 0000000..8a22aca
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.R;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestService extends Service {
+    private static final String TAG = TestService.class.getSimpleName();
+
+    private static final int FLAG_START_FOREGROUND = 1;
+
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
+    private static final int NOTIFICATION_ID = 42;
+
+    private static final int TIMEOUT_OVERLAY_SEC = 2;
+
+    private View mOverlay;
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(TAG, "onStartCommand called. myUid=" + Process.myUid());
+        if (intent != null && (intent.getFlags() & FLAG_START_FOREGROUND) != 0) {
+            startForeground();
+        }
+        notifyServiceLaunched(intent);
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind called. myUid=" + Process.myUid());
+        return null;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+        removeOverlays();
+    }
+
+    private void notifyServiceLaunched(Intent intent) {
+        Common.notifyLaunched(intent, mReceiver.asBinder(), TAG);
+    }
+
+    private void startForeground() {
+        final NotificationManager noMan = getSystemService(NotificationManager.class);
+        noMan.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        Log.d(TAG, "Starting foreground. myUid=" + Process.myUid());
+        startForeground(NOTIFICATION_ID,
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.ic_dialog_alert)
+                        .build());
+    }
+
+    private void removeOverlays() {
+        if (mOverlay != null) {
+            final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+            wm.removeView(mOverlay);
+            mOverlay = null;
+        }
+    }
+
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
+        @Override
+        public void doSomeWork(int durationMs) {
+            Common.doSomeWork(durationMs);
+        }
+
+        @Override
+        public void showApplicationOverlay() throws RemoteException {
+            final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+            final Point size = new Point();
+            wm.getDefaultDisplay().getSize(size);
+
+            final WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+            wmlp.width = size.x / 2;
+            wmlp.height = size.y / 2;
+            wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+            wmlp.setTitle(TAG);
+
+            final ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+
+            mOverlay = new View(TestService.this);
+            mOverlay.setBackgroundColor(Color.GREEN);
+            mOverlay.setLayoutParams(vglp);
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final Handler handler = new Handler(TestService.this.getMainLooper());
+            handler.post(() -> {
+                wm.addView(mOverlay, wmlp);
+                latch.countDown();
+            });
+            try {
+                if (!latch.await(TIMEOUT_OVERLAY_SEC, TimeUnit.SECONDS)) {
+                    throw new RemoteException("Timed out waiting for the overlay");
+                }
+            } catch (InterruptedException e) {
+                throw new RemoteException("Error while adding overlay: " + e.toString());
+            }
+            Log.d(TAG, "Overlay displayed, myUid=" + Process.myUid());
+        }
+
+        @Override
+        public void finishHost() {
+            removeOverlays();
+            stopSelf();
+        }
+    };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
index 53a181a..6d0239b 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -17,5 +17,5 @@
 package com.android.frameworks.coretests.aidl;
 
 interface ICmdCallback {
-    void onActivityLaunched(IBinder receiver);
+    void onLaunched(IBinder receiver);
 }
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
index c406570..cce8e28 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -18,5 +18,6 @@
 
 interface ICmdReceiver {
     void doSomeWork(int durationMs);
+    void showApplicationOverlay();
     void finishHost();
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
index 3c81853..e26bdf5 100644
--- a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
@@ -15,6 +15,7 @@
 */
 package android.animation;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 
 import java.util.HashSet;
@@ -24,6 +25,7 @@
 
 import com.android.frameworks.coretests.R;
 
+@LargeTest
 public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity>  {
     Set<Integer> identityHashes = new HashSet<Integer>();
 
diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
index 38df78d..a9961e1 100644
--- a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
@@ -17,6 +17,7 @@
 
 package android.animation;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.util.StateSet;
@@ -27,7 +28,7 @@
 
 import java.util.concurrent.atomic.AtomicInteger;
 
-
+@LargeTest
 public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
 
     public StateListAnimatorTest() {
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index f60bf94..063bef7 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageInfo;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.support.test.filters.LargeTest;
 
 import junit.framework.TestCase;
 
@@ -34,6 +35,7 @@
 import static android.os.storage.VolumeInfo.STATE_MOUNTED;
 import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
 
+@LargeTest
 public class ApplicationPackageManagerTest extends TestCase {
     private static final String sInternalVolPath = "/data";
     private static final String sAdoptedVolPath = "/mnt/expand/123";
diff --git a/core/tests/coretests/src/android/app/InstrumentationTest.java b/core/tests/coretests/src/android/app/InstrumentationTest.java
index ee3834c..9b59da4 100644
--- a/core/tests/coretests/src/android/app/InstrumentationTest.java
+++ b/core/tests/coretests/src/android/app/InstrumentationTest.java
@@ -17,8 +17,10 @@
 package android.app;
 
 import android.os.Bundle;
+import android.support.test.filters.LargeTest;
 import android.test.InstrumentationTestCase;
 
+@LargeTest
 public class InstrumentationTest extends InstrumentationTestCase {
 
     /**
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index c14dc90..7183934 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -214,6 +214,20 @@
         assertTrue(n.allPendingIntents.contains(intent));
     }
 
+    @Test
+    public void testMessagingStyle_isGroupConversation() {
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
     private Notification.Builder getMediaNotification() {
         MediaSession session = new MediaSession(mContext, "test");
         return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index e9e8bfc..13e70eb 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -27,12 +27,13 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
 import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
 import java.util.Arrays;
 
+@LargeTest
 public class BroadcastTest extends ActivityTestsBase {
     public static final int BROADCAST_TIMEOUT = 5 * 1000;
 
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 3c30915..8c1d79b 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -20,10 +20,10 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.test.suitebuilder.annotation.Suppress;
 import android.os.Bundle;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
 
+@LargeTest
 public class IntentSenderTest extends BroadcastTest {
 
     public void testRegisteredReceivePermissionGranted() throws Exception {
diff --git a/core/tests/coretests/src/android/app/backup/BackupDataTest.java b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
index 0c204e0..5b8e481 100644
--- a/core/tests/coretests/src/android/app/backup/BackupDataTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.test.InstrumentationTestCase;
 import android.util.Base64;
@@ -41,6 +42,7 @@
 import java.lang.Exception;
 import java.nio.ByteBuffer;
 
+@LargeTest
 public class BackupDataTest extends AndroidTestCase {
     private static final String KEY1 = "key1";
     private static final String KEY2 = "key2a";
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index 3869cd2..bc6fc15 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -36,6 +37,7 @@
 import java.util.Map;
 import java.util.Set;
 
+@LargeTest
 public class FullBackupTest extends AndroidTestCase {
     private XmlPullParserFactory mFactory;
     private XmlPullParser mXpp;
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index c19a343..aefc47e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -42,8 +42,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-// TODO: b/70616950
-//@Presubmit
+@Presubmit
 public class ObjectPoolTests {
 
     // 1. Check if two obtained objects from pool are not the same.
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 4b1f2da..e4de526 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -513,6 +513,12 @@
         }
 
         @Override
+        public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor,
+                Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2,
+                boolean b3, String[] strings) throws RemoteException {
+        }
+
+        @Override
         public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
                 throws RemoteException {
         }
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
index 70a0877..e20645c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link DistroFormatVersion}.
  */
+@LargeTest
 public class DistroFormatVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
index eecae46..b69054c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link DistroRulesVersion}.
  */
+@LargeTest
 public class DistroRulesVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index 99abe24..dd46240 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -23,12 +23,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link RulesState}.
  */
+@LargeTest
 public class RulesStateTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
index 91f8ebc..e4aac50 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.support.test.filters.LargeTest;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -33,6 +34,7 @@
 /**
  * Tests for {@link RulesUpdaterContract}.
  */
+@LargeTest
 public class RulesUpdaterContractTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
index d92eece..10d74f7 100644
--- a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
+++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
@@ -17,6 +17,7 @@
 
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 import java.util.Arrays;
@@ -24,6 +25,7 @@
 import java.util.List;
 import java.util.Set;
 
+@LargeTest
 public class RestrictionsManagerTest extends AndroidTestCase {
     private RestrictionsManager mRm;
 
diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
index 948e722..659f9ea 100644
--- a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
+++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 import java.io.ByteArrayInputStream;
@@ -27,6 +28,7 @@
 
 import libcore.io.Streams;
 
+@LargeTest
 public class MacAuthenticatedInputStreamTest extends AndroidTestCase {
 
     private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC");
diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
index a9d19b4..952bb55 100644
--- a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
+++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
@@ -2,6 +2,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
 
 import junit.framework.TestCase;
 
@@ -9,6 +10,7 @@
 import java.util.Collections;
 import java.util.List;
 
+@LargeTest
 public class ParceledListSliceTest extends TestCase {
 
     public void testSmallList() throws Exception {
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 271c639..d3d1f22a 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.SparseArray;
@@ -43,6 +44,7 @@
 /**
  * Tests for {@link android.content.pm.RegisteredServicesCache}
  */
+@LargeTest
 public class RegisteredServicesCacheTest extends AndroidTestCase {
     private static final int U0 = 0;
     private static final int U1 = 1;
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index 89d5997..a3fa1a9 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -16,8 +16,11 @@
 
 package android.content.pm;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
+@LargeTest
 public class SignatureTest extends TestCase {
 
     /** Cert A with valid syntax */
diff --git a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
index d963812..68942cb 100644
--- a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
@@ -19,6 +19,7 @@
 import android.content.pm.VerificationParams;
 import android.net.Uri;
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 /**
@@ -27,6 +28,7 @@
  * To test run:
  * ./development/testrunner/runtest.py frameworks-core -c android.content.pm.VerificationParamsTest
  */
+@LargeTest
 public class VerificationParamsTest extends AndroidTestCase {
 
     private final static String VERIFICATION_URI_STRING = "http://verification.uri/path";
diff --git a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
index cb13eb7..88d7a59 100644
--- a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
@@ -17,9 +17,11 @@
 package android.content.pm;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import java.util.Random;
 
+@LargeTest
 public class VerifierDeviceIdentityTest extends android.test.AndroidTestCase {
     private static final long TEST_1 = 0x7A5F00FF5A55AAA5L;
 
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7550cb5..b28a4b5 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -15,6 +15,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.PathParser;
@@ -23,6 +24,7 @@
 import java.util.Arrays;
 import org.junit.Test;
 
+@LargeTest
 public class AdaptiveIconDrawableTest extends AndroidTestCase {
 
     public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index a2e9ae8..f30b1a2 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -30,6 +30,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -49,6 +50,7 @@
  * Contains additional tests that cannot be included in CTS because they require
  * system permissions.  See also the CTS version of VirtualDisplayTest.
  */
+@LargeTest
 public class VirtualDisplayTest extends AndroidTestCase {
     private static final String TAG = "VirtualDisplayTest";
 
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index ada59cd..3be776d 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -15,9 +15,13 @@
  */
 package android.metrics;
 
+import android.support.test.filters.LargeTest;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import junit.framework.TestCase;
 
+@LargeTest
 public class LogMakerTest extends TestCase {
 
     public void testSerialize() {
diff --git a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
index d10b351..784a12f 100644
--- a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
+++ b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
@@ -16,6 +16,7 @@
 package android.metrics;
 
 import android.metrics.MetricsReader.Event;
+import android.support.test.filters.LargeTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -23,6 +24,7 @@
 
 import java.util.Collection;
 
+@LargeTest
 public class MetricsReaderTest extends TestCase {
     private static final int FULL_N = 10;
     private static final int CHECKPOINTED_N = 4;
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
new file mode 100644
index 0000000..e2f800f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2008 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.os;
+
+import android.os.WorkSource.WorkChain;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides unit tests for hidden / unstable WorkSource APIs that are not CTS testable.
+ *
+ * These tests will be moved to CTS when finalized.
+ */
+public class WorkSourceTest extends TestCase {
+    public void testWorkChain_add() {
+        WorkChain wc1 = new WorkChain();
+        wc1.addNode(56, null);
+
+        assertEquals(56, wc1.getUids()[0]);
+        assertEquals(null, wc1.getTags()[0]);
+        assertEquals(1, wc1.getSize());
+
+        wc1.addNode(57, "foo");
+        assertEquals(56, wc1.getUids()[0]);
+        assertEquals(null, wc1.getTags()[0]);
+        assertEquals(57, wc1.getUids()[1]);
+        assertEquals("foo", wc1.getTags()[1]);
+
+        assertEquals(2, wc1.getSize());
+    }
+
+    public void testWorkChain_equalsHashCode() {
+        WorkChain wc1 = new WorkChain();
+        WorkChain wc2 = new WorkChain();
+
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1.addNode(1, null);
+        wc2.addNode(1, null);
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1.addNode(2, "tag");
+        wc2.addNode(2, "tag");
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1 = new WorkChain();
+        wc2 = new WorkChain();
+        wc1.addNode(5, null);
+        wc2.addNode(6, null);
+        assertFalse(wc1.equals(wc2));
+        assertFalse(wc1.hashCode() == wc2.hashCode());
+
+        wc1 = new WorkChain();
+        wc2 = new WorkChain();
+        wc1.addNode(5, "tag1");
+        wc2.addNode(5, "tag2");
+        assertFalse(wc1.equals(wc2));
+        assertFalse(wc1.hashCode() == wc2.hashCode());
+    }
+
+    public void testWorkChain_constructor() {
+        WorkChain wc1 = new WorkChain();
+        wc1.addNode(1, "foo")
+            .addNode(2, null)
+            .addNode(3, "baz");
+
+        WorkChain wc2 = new WorkChain(wc1);
+        assertEquals(wc1, wc2);
+
+        wc1.addNode(4, "baz");
+        assertFalse(wc1.equals(wc2));
+    }
+
+    public void testDiff_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+        ws1.createWorkChain().addNode(52, "foo");
+        WorkSource ws2 = new WorkSource();
+        ws2.add(50);
+        ws2.createWorkChain().addNode(60, "bar");
+
+        // Diffs don't take WorkChains into account for the sake of backward compatibility.
+        assertFalse(ws1.diff(ws2));
+        assertFalse(ws2.diff(ws1));
+    }
+
+    public void testEquals_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+        ws1.createWorkChain().addNode(52, "foo");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(50);
+        ws2.createWorkChain().addNode(52, "foo");
+
+        assertEquals(ws1, ws2);
+
+        // Unequal number of WorkChains.
+        ws2.createWorkChain().addNode(53, "baz");
+        assertFalse(ws1.equals(ws2));
+
+        // Different WorkChain contents.
+        WorkSource ws3 = new WorkSource();
+        ws3.add(50);
+        ws3.createWorkChain().addNode(60, "bar");
+
+        assertFalse(ws1.equals(ws3));
+        assertFalse(ws3.equals(ws1));
+    }
+
+    public void testWorkSourceParcelling() {
+        WorkSource ws = new WorkSource();
+
+        WorkChain wc = ws.createWorkChain();
+        wc.addNode(56, "foo");
+        wc.addNode(75, "baz");
+        WorkChain wc2 = ws.createWorkChain();
+        wc2.addNode(20, "foo2");
+        wc2.addNode(30, "baz2");
+
+        Parcel p = Parcel.obtain();
+        ws.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        WorkSource unparcelled = WorkSource.CREATOR.createFromParcel(p);
+
+        assertEquals(unparcelled, ws);
+    }
+
+    public void testSet_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(60);
+        WorkChain wc = ws2.createWorkChain();
+        wc.addNode(75, "tag");
+
+        ws1.set(ws2);
+
+        // Assert that the WorkChains are copied across correctly to the new WorkSource object.
+        List<WorkChain> workChains = ws1.getWorkChains();
+        assertEquals(1, workChains.size());
+
+        assertEquals(1, workChains.get(0).getSize());
+        assertEquals(75, workChains.get(0).getUids()[0]);
+        assertEquals("tag", workChains.get(0).getTags()[0]);
+
+        // Also assert that a deep copy of workchains is made, so the addition of a new WorkChain
+        // or the modification of an existing WorkChain has no effect.
+        ws2.createWorkChain();
+        assertEquals(1, ws1.getWorkChains().size());
+
+        wc.addNode(50, "tag2");
+        assertEquals(1, ws1.getWorkChains().size());
+        assertEquals(1, ws1.getWorkChains().get(0).getSize());
+    }
+
+    public void testSet_nullWorkChain() {
+        WorkSource ws = new WorkSource();
+        ws.add(60);
+        WorkChain wc = ws.createWorkChain();
+        wc.addNode(75, "tag");
+
+        ws.set(null);
+        assertEquals(0, ws.getWorkChains().size());
+    }
+
+    public void testAdd_workChains() {
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(70, "foo");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(60, "tag");
+
+        ws.add(ws2);
+
+        // Check that the new WorkChain is added to the end of the list.
+        List<WorkChain> workChains = ws.getWorkChains();
+        assertEquals(2, workChains.size());
+        assertEquals(1, workChains.get(1).getSize());
+        assertEquals(60, ws.getWorkChains().get(1).getUids()[0]);
+        assertEquals("tag", ws.getWorkChains().get(1).getTags()[0]);
+
+        // Adding the same WorkChain twice should be a no-op.
+        ws.add(ws2);
+        assertEquals(2, workChains.size());
+    }
+
+    public void testSet_noWorkChains() {
+        WorkSource ws = new WorkSource();
+        ws.set(10);
+        assertEquals(1, ws.size());
+        assertEquals(10, ws.get(0));
+
+        WorkSource ws2 = new WorkSource();
+        ws2.set(20, "foo");
+        assertEquals(1, ws2.size());
+        assertEquals(20, ws2.get(0));
+        assertEquals("foo", ws2.getName(0));
+    }
+
+    public void testDiffChains_noChanges() {
+        // WorkSources with no chains.
+        assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource()));
+
+        // WorkSources with the same chains.
+        WorkSource ws1 = new WorkSource();
+        ws1.createWorkChain().addNode(50, "tag");
+        ws1.createWorkChain().addNode(60, "tag2");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(50, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        assertEquals(null, WorkSource.diffChains(ws1, ws1));
+        assertEquals(null, WorkSource.diffChains(ws2, ws1));
+    }
+
+    public void testDiffChains_noChains() {
+        // Diffs against a worksource with no chains.
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(70, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        // The "old" work source has no chains, so "newChains" should be non-null.
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNull(diffs[1]);
+        assertEquals(2, diffs[0].size());
+        assertEquals(ws2.getWorkChains(), diffs[0]);
+
+        // The "new" work source has no chains, so "oldChains" should be non-null.
+        diffs = WorkSource.diffChains(ws2, ws1);
+        assertNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(2, diffs[1].size());
+        assertEquals(ws2.getWorkChains(), diffs[1]);
+    }
+
+    public void testDiffChains_onlyAdditionsOrRemovals() {
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(70, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        // Both work sources have WorkChains : test the case where changes were only added
+        // or were only removed.
+        ws1.createWorkChain().addNode(70, "tag");
+
+        // The "new" work source only contains additions (60, "tag2") in this case.
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNull(diffs[1]);
+        assertEquals(1, diffs[0].size());
+        assertEquals(new WorkChain().addNode(60, "tag2"), diffs[0].get(0));
+
+        // The "new" work source only contains removals (60, "tag2") in this case.
+        diffs = WorkSource.diffChains(ws2, ws1);
+        assertNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(1, diffs[1].size());
+        assertEquals(new WorkChain().addNode(60, "tag2"), diffs[1].get(0));
+    }
+
+
+    public void testDiffChains_generalCase() {
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+
+        // Both work sources have WorkChains, test the case where chains were added AND removed.
+        ws1.createWorkChain().addNode(0, "tag0");
+        ws2.createWorkChain().addNode(0, "tag0_changed");
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(ws2.getWorkChains(), diffs[0]);
+        assertEquals(ws1.getWorkChains(), diffs[1]);
+
+        // Give both WorkSources a chain in common; it should not be a part of any diffs.
+        ws1.createWorkChain().addNode(1, "tag1");
+        ws2.createWorkChain().addNode(1, "tag1");
+        diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(1, diffs[0].size());
+        assertEquals(1, diffs[1].size());
+        assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+        assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+
+        // Finally, test the case where more than one chain was added / removed.
+        ws1.createWorkChain().addNode(2, "tag2");
+        ws2.createWorkChain().addNode(2, "tag2_changed");
+        diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(2, diffs[0].size());
+        assertEquals(2, diffs[1].size());
+        assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+        assertEquals(new WorkChain().addNode(2, "tag2_changed"), diffs[0].get(1));
+        assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+        assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1));
+    }
+}
diff --git a/core/tests/coretests/src/android/preference/ListPreferenceTest.java b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
index 41f8e03..72f62f1 100644
--- a/core/tests/coretests/src/android/preference/ListPreferenceTest.java
+++ b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
@@ -17,8 +17,10 @@
 package android.preference;
 
 import android.preference.ListPreference;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
+@LargeTest
 public class ListPreferenceTest extends AndroidTestCase {
     public void testListPreferenceSummaryFromEntries() {
         String[] entries = { "one", "two", "three" };
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 907a1b8..eef9866 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -106,6 +106,7 @@
                     Settings.Global.APN_DB_UPDATE_CONTENT_URL,
                     Settings.Global.APN_DB_UPDATE_METADATA_URL,
                     Settings.Global.APP_IDLE_CONSTANTS,
+                    Settings.Global.APP_STANDBY_ENABLED,
                     Settings.Global.ASSISTED_GPS_ENABLED,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
@@ -172,6 +173,7 @@
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
+                    Settings.Global.BATTERY_TIP_CONSTANTS,
                     Settings.Global.DEFAULT_SM_DP_PLUS,
                     Settings.Global.DEVICE_NAME,
                     Settings.Global.DEVICE_POLICY_CONSTANTS,
@@ -421,13 +423,6 @@
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                 // TODO(b/67867469): Move autofill settings below to
-                 // BACKUP_BLACKLISTED_SYSTEM_SETTINGS once feature is moved out of experimental
-                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
-                 Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
                  Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                  Settings.Secure.ALWAYS_ON_VPN_APP,
                  Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
@@ -437,6 +432,11 @@
                  Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
                  Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
                  Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
+                 Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
                  Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index aa9aed8..ea954f6 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -30,6 +30,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.style.ReplacementSpan;
+import android.util.ArraySet;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -190,6 +191,27 @@
     }
 
     @Test
+    public void testReflow_afterSpannableEdit() {
+        final String text = "a\nb:\uD83C\uDF1A c \n\uD83C\uDF1A";
+        final int length = text.length();
+        final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        spannable.setSpan(new MockReplacementSpan(), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        spannable.setSpan(new MockReplacementSpan(), 10, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        final DynamicLayout layout = new DynamicLayout(spannable, new TextPaint(), WIDTH,
+                ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/);
+
+        spannable.delete(8, 9);
+        spannable.replace(7, 8, "ch");
+
+        layout.reflow(spannable, 0, length, length);
+        final ArraySet<Integer> blocks = layout.getBlocksAlwaysNeedToBeRedrawn();
+        for (Integer value : blocks) {
+            assertTrue("Block index should not be negative", value >= 0);
+        }
+    }
+
+    @Test
     public void testFallbackLineSpacing() {
         // All glyphs in the fonts are 1em wide.
         final String[] testFontFiles = {
diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index f1a730a..d0f2d46 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.style.BulletSpan;
@@ -30,6 +31,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SpannableStringBuilderTest extends SpannableTest {
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index f4514b5..d817278 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -697,9 +697,13 @@
     public void testGetOffset_UNICODE_Hebrew() {
         String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters
         for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) {
-            StaticLayout layout = new StaticLayout(seq, mDefaultPaint,
-                    DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
-                    TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true);
+            StaticLayout.Builder b = StaticLayout.Builder.obtain(
+                    seq, 0, seq.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH)
+                    .setAlignment(DEFAULT_ALIGN)
+                    .setTextDirection(TextDirectionHeuristics.RTL)
+                    .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+                    .setIncludePad(true);
+            StaticLayout layout = b.build();
 
             String testLabel = buildTestMessage(seq);
 
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 2d94016..9c544f4 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -23,6 +23,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.icu.util.MeasureUnit;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -36,7 +37,7 @@
 import java.util.Locale;
 import java.util.Set;
 
-
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class FormatterTest {
diff --git a/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
index ab4320c..7e629f9 100644
--- a/core/tests/coretests/src/android/transition/TransitionTest.java
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -19,6 +19,7 @@
 import android.animation.AnimatorSetActivity;
 import android.app.Activity;
 import android.graphics.Rect;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.transition.Transition.EpicenterCallback;
 import android.util.ArrayMap;
@@ -30,6 +31,7 @@
 
 import java.lang.reflect.Field;
 
+@LargeTest
 public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
     Activity mActivity;
     public TransitionTest() {
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 32aa29f..f0cc7f7 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -14,6 +14,7 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
 import android.util.ArrayMap;
 
 import junit.framework.TestCase;
@@ -24,6 +25,7 @@
 /**
  * Unit tests for ArrayMap that don't belong in CTS.
  */
+@LargeTest
 public class ArrayMapTest extends TestCase {
     private static final String TAG = "ArrayMapTest";
     ArrayMap<String, String> map = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index 53368d4..3aee583 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -16,15 +16,17 @@
 
 package android.util;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import junit.framework.TestCase;
+import android.support.test.filters.LargeTest;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
 import java.util.Random;
+import junit.framework.TestCase;
 
+@LargeTest
 public class Base64Test extends TestCase {
     private static final String TAG = "Base64Test";
 
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
index a63c8a0..5144c85e 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
 import java.io.PrintWriter;
@@ -24,7 +26,7 @@
 import java.util.Collections;
 import java.util.List;
 
-
+@LargeTest
 public class LocalLogTest extends TestCase {
 
     public void testA() {
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index cb468bc..3f03db9 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
 import java.util.HashMap;
@@ -26,6 +28,7 @@
 /**
  * Tests for {@link LongSparseLongArray}.
  */
+@LargeTest
 public class LongSparseLongArrayTest extends TestCase {
     private static final String TAG = "LongSparseLongArrayTest";
 
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6dd787d..0d8c679 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -25,6 +25,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
@@ -34,7 +35,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
@@ -45,19 +45,14 @@
     /** This is not a consistent cutout. Useful for verifying insets in one go though. */
     final DisplayCutout mCutoutNumbers = new DisplayCutout(
             new Rect(1, 2, 3, 4),
-            new Rect(5, 6, 7, 8),
-            Arrays.asList(
-                    new Point(9, 10),
-                    new Point(11, 12),
-                    new Point(13, 14),
-                    new Point(15, 16)));
+            new Region(5, 6, 7, 8));
 
     final DisplayCutout mCutoutTop = createCutoutTop();
 
     @Test
     public void hasCutout() throws Exception {
-        assertFalse(NO_CUTOUT.hasCutout());
-        assertTrue(mCutoutTop.hasCutout());
+        assertTrue(NO_CUTOUT.isEmpty());
+        assertFalse(mCutoutTop.isEmpty());
     }
 
     @Test
@@ -67,30 +62,12 @@
         assertEquals(3, mCutoutNumbers.getSafeInsetRight());
         assertEquals(4, mCutoutNumbers.getSafeInsetBottom());
 
-        Rect safeInsets = new Rect();
-        mCutoutNumbers.getSafeInsets(safeInsets);
-
-        assertEquals(new Rect(1, 2, 3, 4), safeInsets);
+        assertEquals(new Rect(1, 2, 3, 4), mCutoutNumbers.getSafeInsets());
     }
 
     @Test
     public void getBoundingRect() throws Exception {
-        Rect boundingRect = new Rect();
-        mCutoutTop.getBoundingRect(boundingRect);
-
-        assertEquals(new Rect(50, 0, 75, 100), boundingRect);
-    }
-
-    @Test
-    public void getBoundingPolygon() throws Exception {
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        mCutoutTop.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(75, 0),
-                new Point(50, 0),
-                new Point(75, 100),
-                new Point(50, 100)), boundingPolygon);
+        assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect());
     }
 
     @Test
@@ -167,104 +144,61 @@
         assertEquals(cutout.getSafeInsetRight(), 0);
         assertEquals(cutout.getSafeInsetBottom(), 0);
 
-        assertFalse(cutout.hasCutout());
+        assertTrue(cutout.isEmpty());
     }
 
     @Test
     public void inset_bounds() throws Exception {
         DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
 
-        Rect boundingRect = new Rect();
-        cutout.getBoundingRect(boundingRect);
-
-        assertEquals(new Rect(49, -2, 74, 98), boundingRect);
-
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        cutout.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(74, -2),
-                new Point(49, -2),
-                new Point(74, 98),
-                new Point(49, 98)), boundingPolygon);
+        assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
     }
 
     @Test
     public void calculateRelativeTo_top() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 100, 0, 0), insets);
+        assertEquals(new Rect(0, 100, 0, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_left() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(75, 0, 0, 0), insets);
+        assertEquals(new Rect(75, 0, 0, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_bottom() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 0, 0, 100), insets);
+        assertEquals(new Rect(0, 0, 0, 100), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_right() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 0, 50, 0), insets);
+        assertEquals(new Rect(0, 0, 50, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_bounds() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
 
-
-        Rect boundingRect = new Rect();
-        cutout.getBoundingRect(boundingRect);
-        assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect);
-
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        cutout.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(1075, 2000),
-                new Point(1050, 2000),
-                new Point(1075, 2100),
-                new Point(1050, 2100)), boundingPolygon);
+        assertEquals(new Rect(1050, 2000, 1075, 2100), cutout.getBoundingRect());
     }
 
     @Test
     public void fromBoundingPolygon() throws Exception {
         assertEquals(
-                new DisplayCutout(
-                        new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets.
-                        new Rect(50, 0, 75, 100),
-                        Arrays.asList(
-                                new Point(75, 0),
-                                new Point(50, 0),
-                                new Point(75, 100),
-                                new Point(50, 100))),
+                new Rect(50, 0, 75, 100),
                 DisplayCutout.fromBoundingPolygon(
                         Arrays.asList(
                                 new Point(75, 0),
                                 new Point(50, 0),
                                 new Point(75, 100),
-                                new Point(50, 100))));
+                                new Point(50, 100))).getBounds().getBounds());
     }
 
     @Test
@@ -324,24 +258,12 @@
     }
 
     private static DisplayCutout createCutoutTop() {
-        return new DisplayCutout(
-                new Rect(0, 100, 0, 0),
-                new Rect(50, 0, 75, 100),
-                Arrays.asList(
-                        new Point(75, 0),
-                        new Point(50, 0),
-                        new Point(75, 100),
-                        new Point(50, 100)));
+        return createCutoutWithInsets(0, 100, 0, 0);
     }
 
     private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
         return new DisplayCutout(
                 new Rect(left, top, right, bottom),
-                new Rect(50, 0, 75, 100),
-                Arrays.asList(
-                        new Point(75, 0),
-                        new Point(50, 0),
-                        new Point(75, 100),
-                        new Point(50, 100)));
+                new Region(50, 0, 75, 100));
     }
 }
diff --git a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
index 23d0251..fba8eae 100644
--- a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
+++ b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.Context;
+import android.support.test.filters.LargeTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.ActivityInstrumentationTestCase2;
@@ -36,6 +37,7 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.Espresso.onView;
 
+@LargeTest
 public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
     private ScaleGesture mScaleGestureActivity;
 
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 44fcd13..aa8f8d8 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -20,6 +20,7 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.PixelFormat;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.view.View;
@@ -28,6 +29,7 @@
 
 import com.android.frameworks.coretests.R;
 
+@LargeTest
 public class ViewAttachTest extends
         ActivityInstrumentationTestCase2<ViewAttachTestActivity> {
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 2f32d13..4de8155 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
@@ -42,7 +43,7 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
-
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityCacheTest {
     private static final int WINDOW_ID_1 = 0xBEEF;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index c1b2309..318d122 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -22,6 +22,7 @@
 
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import libcore.util.EmptyArray;
@@ -36,6 +37,7 @@
 /**
  * Tests for AccessibilityInteractionClient
  */
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityInteractionClientTest {
     private static final int MOCK_CONNECTION_ID = 0xabcd;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 79e6316..b135025 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.fail;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -29,6 +30,7 @@
 
 import java.util.ArrayList;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityNodeInfoTest {
 
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index e3c91e6..9edbf3e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,6 +26,7 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -34,6 +35,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodInfoTest {
 
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index 9347b27..8ed0d86 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -21,8 +21,10 @@
 import com.android.internal.view.menu.MenuBuilder;
 
 import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 
+@LargeTest
 public class MenuLayoutLandscapeTest extends ActivityInstrumentationTestCase<MenuLayoutLandscape> {
     private static final String LONG_TITLE = "Really really really really really really really really really really long title";
     private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index b053699..ccf1264 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -16,13 +16,12 @@
 
 package android.view.menu;
 
-import android.util.KeyUtils;
-import com.android.internal.view.menu.IconMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
 import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
+import android.util.KeyUtils;
 
+@LargeTest
 public class MenuLayoutPortraitTest extends ActivityInstrumentationTestCase<MenuLayoutPortrait> {
     private static final String LONG_TITLE = "Really really really really really really really really really really long title";
     private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 9092c85..a8de374 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -16,10 +16,9 @@
 
 package android.view.textclassifier;
 
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
 import android.os.LocaleList;
@@ -34,8 +33,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Collection;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
@@ -166,20 +163,50 @@
     }
 
     @Test
-    public void testGenerateLinks() {
+    public void testGenerateLinks_phone() {
         if (isTextClassifierDisabled()) return;
+        String text = "The number is +12122537077. See you tonight!";
+        assertThat(mClassifier.generateLinks(text, null),
+                isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
+    }
 
-        checkGenerateLinksFindsLink(
-                "The number is +12122537077. See you tonight!",
-                "+12122537077",
-                TextClassifier.TYPE_PHONE);
+    @Test
+    public void testGenerateLinks_exclude() {
+        if (isTextClassifierDisabled()) return;
+        String text = "The number is +12122537077. See you tonight!";
+        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_ALL)
+                        .excludeEntities(TextClassifier.TYPE_PHONE))),
+                not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
+    }
 
-        checkGenerateLinksFindsLink(
-                "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you tonight!",
-                "1600 Amphitheater Parkway, Mountain View, CA",
-                TextClassifier.TYPE_ADDRESS);
+    @Test
+    public void testGenerateLinks_none_config() {
+        if (isTextClassifierDisabled()) return;
+        String text = "The number is +12122537077. See you tonight!";
+        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE))),
+                not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
+    }
 
-        // TODO: Add more entity types when the model supports them.
+    @Test
+    public void testGenerateLinks_address() {
+        if (isTextClassifierDisabled()) return;
+        String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
+        assertThat(mClassifier.generateLinks(text, null),
+                isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
+                        TextClassifier.TYPE_ADDRESS));
+    }
+
+    @Test
+    public void testGenerateLinks_include() {
+        if (isTextClassifierDisabled()) return;
+        String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
+        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE)
+                        .includeEntities(TextClassifier.TYPE_ADDRESS))),
+                isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
+                        TextClassifier.TYPE_ADDRESS));
     }
 
     @Test
@@ -193,25 +220,6 @@
         return mClassifier == TextClassifier.NO_OP;
     }
 
-    private void checkGenerateLinksFindsLink(String text, String classifiedText, String type) {
-        assertTrue(text.contains(classifiedText));
-        int startIndex = text.indexOf(classifiedText);
-        int endIndex = startIndex + classifiedText.length();
-
-        Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, mLinksOptions)
-                .getLinks();
-        for (TextLinks.TextLink link : links) {
-            if (text.subSequence(link.getStart(), link.getEnd()).equals(classifiedText)) {
-                assertEquals(type, link.getEntity(0));
-                assertEquals(startIndex, link.getStart());
-                assertEquals(endIndex, link.getEnd());
-                assertTrue(link.getConfidenceScore(type) > 0);
-                return;
-            }
-        }
-        fail(); // Subsequence was not identified.
-    }
-
     private static Matcher<TextSelection> isTextSelection(
             final int startIndex, final int endIndex, final String type) {
         return new BaseMatcher<TextSelection>() {
@@ -240,6 +248,31 @@
         };
     }
 
+    private static Matcher<TextLinks> isTextLinksContaining(
+            final String text, final String substring, final String type) {
+        return new BaseMatcher<TextLinks>() {
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("text=").appendValue(text)
+                        .appendText(", substring=").appendValue(substring)
+                        .appendText(", type=").appendValue(type);
+            }
+
+            @Override
+            public boolean matches(Object o) {
+                if (o instanceof TextLinks) {
+                    for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) {
+                        if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) {
+                            return type.equals(link.getEntity(0));
+                        }
+                    }
+                }
+                return false;
+            }
+        };
+    }
+
     private static Matcher<TextClassification> isTextClassification(
             final String text, final String type, final String intentUri) {
         return new BaseMatcher<TextClassification>() {
diff --git a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
index 513e40f..be85450 100644
--- a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
+++ b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.KeyEvent;
 import android.view.View;
@@ -28,6 +29,7 @@
 /**
  * Test {@link DatePicker} focus changes.
  */
+@LargeTest
 public class DatePickerFocusTest extends ActivityInstrumentationTestCase2<DatePickerActivity> {
 
     private Activity mActivity;
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index 28a3b67..2add221 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -22,6 +22,7 @@
 
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
 import java.util.Arrays;
 import java.util.List;
 
+@LargeTest
 @RunWith(JUnit4.class)
 public final class SelectionActionModeHelperTest {
 
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 0e460b9..1a654f4 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -27,8 +27,10 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
 import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
 import static android.widget.espresso.DragHandleUtils.onHandleView;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils
+        .assertFloatingToolbarContainsItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils
+        .assertFloatingToolbarDoesNotContainItem;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
@@ -68,12 +70,15 @@
 import android.text.InputType;
 import android.text.Selection;
 import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.method.LinkMovementMethod;
 import android.view.ActionMode;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
 
 import com.android.frameworks.coretests.R;
@@ -305,6 +310,33 @@
     }
 
     @Test
+    public void testToolbarAppearsAfterLinkClicked() throws Throwable {
+        useSystemDefaultTextClassifier();
+        TextClassificationManager textClassificationManager =
+                mActivity.getSystemService(TextClassificationManager.class);
+        TextClassifier textClassifier = textClassificationManager.getTextClassifier();
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        SpannableString content = new SpannableString("Call me at +19148277737");
+        TextLinks links = textClassifier.generateLinks(content);
+        links.apply(content, null);
+
+        mActivityRule.runOnUiThread(() -> {
+            textView.setText(content);
+            textView.setMovementMethod(LinkMovementMethod.getInstance());
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Wait for the UI thread to refresh
+        Thread.sleep(1000);
+
+        TextLinks.TextLink textLink = links.getLinks().iterator().next();
+        int position = (textLink.getStart() + textLink.getEnd()) / 2;
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+    }
+
+    @Test
     public void testToolbarAndInsertionHandle() {
         final String text = "text";
         onView(withId(R.id.textview)).perform(replaceText(text));
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 7875c17..5dec42e 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -35,6 +35,7 @@
 import android.text.Layout;
 import android.text.Selection;
 import android.text.Spannable;
+import android.util.TypedValue;
 import android.view.View;
 
 import org.junit.Before;
@@ -274,7 +275,7 @@
                 new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) {
             mTextView = new TextView(mActivity);
             mTextView.setTypeface(setup.getTypefaceFor("sans-serif"));
-            mTextView.setTextSize(100);
+            mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);
             mTextView.setText("aaaaa aabaa aaaaa"); // This should result in three lines.
             mTextView.setPadding(0, 0, 0, 0);
             mTextView.setIncludeFontPadding(false);
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
index b591e5f..43986ee 100644
--- a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
@@ -18,6 +18,7 @@
 
 import android.widget.focus.HorizontalFocusSearch;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.Suppress;
 import android.widget.LinearLayout;
@@ -32,6 +33,7 @@
  * various widths and vertical placements.
  */
 // Suppress until bug http://b/issue?id=1416545 is fixed.
+@LargeTest
 @Suppress
 public class HorizontalFocusSearchTest extends ActivityInstrumentationTestCase<HorizontalFocusSearch> {
 
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
index f05d83a..f01422e 100644
--- a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
@@ -18,6 +18,7 @@
 
 import android.widget.focus.VerticalFocusSearch;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.Suppress;
 import android.view.FocusFinder;
@@ -31,6 +32,7 @@
  * various widths and horizontal placements.
  */
 // Suppress until bug http://b/issue?id=1416545 is fixed
+@LargeTest
 @Suppress 
 public class VerticalFocusSearchTest extends ActivityInstrumentationTestCase<VerticalFocusSearch> {
 
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
index 400fd7d..9a8e634 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
@@ -16,12 +16,14 @@
 
 package android.widget.listview.arrowscroll;
 
-import android.widget.listview.ListWithFirstScreenUnSelectable;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.KeyEvent;
 import android.widget.ListView;
+import android.widget.listview.ListWithFirstScreenUnSelectable;
 import android.widget.AdapterView;
 
+@LargeTest
 public class ListWithFirstScreenUnSelectableTest
         extends ActivityInstrumentationTestCase2<ListWithFirstScreenUnSelectable> {
     private ListView mListView;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 6ff0ab2..b5a7bec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -960,7 +960,7 @@
     }
 
     @Test
-    public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+    public void testReadKernelUidCpuFreqTimesLocked_invalidUid() {
         // PRECONDITIONS
         updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
new file mode 100644
index 0000000..3794b5f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsImplTest {
+    @Mock
+    private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+    @Mock
+    private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+
+    private MockBatteryStatsImpl mBatteryStatsImpl;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
+        when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+        mBatteryStatsImpl = new MockBatteryStatsImpl()
+                .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+    }
+
+    @Test
+    public void testUpdateProcStateCpuTimes() {
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+        final int[] testUids = {10032, 10048, 10145, 10139};
+        final int[] testProcStates = {
+                PROCESS_STATE_BACKGROUND,
+                PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_TOP,
+                PROCESS_STATE_CACHED
+        };
+        addPendingUids(testUids, testProcStates);
+        final long[][] cpuTimes = {
+                {349734983, 394982394832l, 909834, 348934, 9838},
+                {7498, 1239890, 988, 13298, 98980},
+                {989834, 384098, 98483, 23809, 4984},
+                {4859048, 348903, 4578967, 5973894, 298549}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(cpuTimes[i]);
+
+            // Verify there are no cpu times initially.
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    assertArrayEquals("Uid=" + testUids[i], cpuTimes[i],
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        final long[][] delta1 = {
+                {9589, 148934, 309894, 3098493, 98754},
+                {21983, 94983, 4983, 9878493, 84854},
+                {945894, 9089432, 19478, 3834, 7845},
+                {843895, 43948, 949582, 99, 384}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta1[i]);
+        }
+        addPendingUids(testUids, testProcStates);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j];
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        final long[][] delta2 = {
+                {95932, 2943, 49834, 89034, 139},
+                {349, 89605, 5896, 845, 98444},
+                {678, 7498, 9843, 889, 4894},
+                {488, 998, 8498, 394, 574}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta2[i]);
+        }
+        addPendingUids(testUids, testProcStates);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j] + delta2[i][j];
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertArrayEquals("Uid=" + testUids[i], delta2[i],
+                            u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+            }
+        }
+
+        final long[][] delta3 = {
+                {98545, 95768795, 76586, 548945, 57846},
+                {788876, 586, 578459, 8776984, 9578923},
+                {3049509483598l, 4597834, 377654, 94589035, 7854},
+                {9493, 784, 99895, 8974893, 9879843}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(
+                    delta3[i].clone());
+        }
+        addPendingUids(testUids, testProcStates);
+        final int parentUid = testUids[1];
+        final int childUid = 99099;
+        addIsolatedUid(parentUid, childUid);
+        final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
+        when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j] + delta2[i][j] + delta3[i][j]
+                                + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    long[] expectedScreenOffTimes = delta2[i].clone();
+                    for (int j = 0; j < expectedScreenOffTimes.length; ++j) {
+                        expectedScreenOffTimes[j] += delta3[i][j]
+                                + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedScreenOffTimes,
+                            u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testCopyFromAllUidsCpuTimes() {
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+        final int[] testUids = {10032, 10048, 10145, 10139};
+        final int[] testProcStates = {
+                PROCESS_STATE_BACKGROUND,
+                PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_TOP,
+                PROCESS_STATE_CACHED
+        };
+        final int[] pendingUidIdx = {1, 2};
+        updateProcessStates(testUids, testProcStates, pendingUidIdx);
+
+        final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
+        long[][] allCpuTimes = {
+                {938483, 4985984, 439893},
+                {499, 94904, 27694},
+                {302949085, 39789473, 34792839},
+                {9809485, 9083475, 347889834},
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            allUidCpuTimes.put(testUids[i], allCpuTimes[i]);
+        }
+        when(mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs()).thenReturn(allUidCpuTimes);
+        long[][] expectedCpuTimes = {
+                {843598745, 397843, 32749, 99854},
+                {9834, 5885, 487589, 394},
+                {203984, 439, 9859, 30948},
+                {9389, 858, 239, 349}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            final int idx = i;
+            final ArgumentMatcher<long[]> matcher = times -> Arrays.equals(times, allCpuTimes[idx]);
+            when(mKernelSingleUidTimeReader.computeDelta(eq(testUids[i]), argThat(matcher)))
+                    .thenReturn(expectedCpuTimes[i]);
+        }
+
+        mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes[i],
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+    }
+
+    @Test
+    public void testAddCpuTimes() {
+        long[] timesA = null;
+        long[] timesB = null;
+        assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        timesA = new long[] {34, 23, 45, 24};
+        assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        timesB = timesA;
+        timesA = null;
+        assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        final long[] expected = {434, 6784, 34987, 9984};
+        timesA = new long[timesB.length];
+        for (int i = 0; i < timesA.length; ++i) {
+            timesA[i] = expected[i] - timesB[i];
+        }
+        assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+    }
+
+    private void addIsolatedUid(int parentUid, int childUid) {
+        final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
+        u.addIsolatedUid(childUid);
+    }
+
+    private void addPendingUids(int[] uids, int[] procStates) {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        for (int i = 0; i < uids.length; ++i) {
+            pendingUids.put(uids[i], procStates[i]);
+        }
+    }
+
+    private void updateProcessStates(int[] uids, int[] procStates,
+            int[] pendingUidsIdx) {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        for (int i = 0; i < uids.length; ++i) {
+            final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uids[i]);
+            if (ArrayUtils.contains(pendingUidsIdx, i)) {
+                u.setProcessStateForTest(PROCESS_STATE_TOP);
+                pendingUids.put(uids[i], procStates[i]);
+            } else {
+                u.setProcessStateForTest(procStates[i]);
+            }
+        }
+    }
+
+    private void verifyNoPendingUids() {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        assertEquals("There shouldn't be any pending uids left: " + pendingUids,
+                0, pendingUids.size());
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 4e83221..0afec34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -170,7 +170,6 @@
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HEAVY_WEIGHT)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 12f5b70..e8f2456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -26,6 +26,7 @@
         BatteryStatsDualTimerTest.class,
         BatteryStatsDurationTimerTest.class,
         BatteryStatsHelperTest.class,
+        BatteryStatsImplTest.class,
         BatteryStatsNoteTest.class,
         BatteryStatsSamplingTimerTest.class,
         BatteryStatsSensorTest.class,
@@ -36,6 +37,7 @@
         BatteryStatsUidTest.class,
         BatteryStatsUserLifecycleTests.class,
         KernelMemoryBandwidthStatsTest.class,
+        KernelSingleUidTimeReaderTest.class,
         KernelUidCpuFreqTimeReaderTest.class,
         KernelWakelockReaderTest.class,
         LongSamplingCounterArrayTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 4b197e4..e54fe7d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -15,19 +15,25 @@
  */
 package com.android.internal.os;
 
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.BatteryStats.UID_TIMES_TYPE_ALL;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
+import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
-import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
-import static org.junit.Assume.assumeTrue;
-
 import com.android.frameworks.coretests.aidl.ICmdCallback;
 import com.android.frameworks.coretests.aidl.ICmdReceiver;
 
+import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,6 +42,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
+import android.os.BatteryStats;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -45,9 +52,9 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiDevice;
+import android.util.DebugUtils;
 import android.util.Log;
 
-import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,22 +68,26 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class BstatsCpuTimesValidationTest {
-    private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+    private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
 
     private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
     private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+    private static final String TEST_SERVICE = TEST_PKG + ".TestService";
     private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
 
     private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+    private static final int FLAG_START_FOREGROUND = 1;
 
     private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
 
     private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+    private static final int START_FG_SERVICE_TIMEOUT_MS = 2000;
+    private static final int START_SERVICE_TIMEOUT_MS = 2000;
     private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
 
-    private static final int GENERAL_TIMEOUT_MS = 1000;
-    private static final int GENERAL_INTERVAL_MS = 100;
+    private static final int GENERAL_TIMEOUT_MS = 4000;
+    private static final int GENERAL_INTERVAL_MS = 200;
 
     private static final int WORK_DURATION_MS = 2000;
 
@@ -84,6 +95,7 @@
     private static UiDevice sUiDevice;
     private static int sTestPkgUid;
     private static boolean sCpuFreqTimesAvailable;
+    private static boolean sPerProcStateTimesAvailable;
 
     @BeforeClass
     public static void setupOnce() throws Exception {
@@ -92,14 +104,20 @@
         sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
-        sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+        checkCpuTimesAvailability();
     }
 
     // Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
-    // kernel node is available.
-    private static boolean cpuFreqTimesAvailable() throws Exception {
-        final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
-        return cpuTimes != null;
+    // and /proc/uid/<uid>/time_in_state kernel nodes are available.
+    private static void checkCpuTimesAvailability() throws Exception {
+        batteryOn();
+        SystemClock.sleep(GENERAL_TIMEOUT_MS);
+        batteryOff();
+        final long[] totalCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+        sCpuFreqTimesAvailable = totalCpuTimes != null;
+        final long[] fgSvcCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE);
+        sPerProcStateTimesAvailable = fgSvcCpuTimes != null;
     }
 
     @Test
@@ -112,7 +130,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWork();
         forceStop();
 
@@ -136,7 +155,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWork();
         forceStop();
 
@@ -166,7 +186,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWorkInIsolatedProcess();
         forceStop();
 
@@ -180,6 +201,224 @@
         batteryOffScreenOn();
     }
 
+    @Test
+    public void testCpuFreqTimes_stateTop() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+        doSomeWork(PROCESS_STATE_TOP);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testIsolatedCpuFreqTimes_stateService() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+        final ICmdReceiver activityReceiver = ICmdReceiver.Stub.asInterface(startActivity());
+        final ICmdReceiver isolatedReceiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+        try {
+            assertProcState(PROCESS_STATE_TOP);
+            isolatedReceiver.doSomeWork(WORK_DURATION_MS);
+        } finally {
+            activityReceiver.finishHost();
+            isolatedReceiver.finishHost();
+        }
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateTopSleeping() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING));
+
+        doSomeWork(PROCESS_STATE_TOP_SLEEPING);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateFgService() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE));
+
+        doSomeWork(PROCESS_STATE_FOREGROUND_SERVICE);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateFg() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND));
+
+        doSomeWork(PROCESS_STATE_FOREGROUND);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOff();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateBg() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND));
+
+        doSomeWork(PROCESS_STATE_BACKGROUND);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateCached() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED));
+
+        doSomeWork(PROCESS_STATE_CACHED);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
@@ -219,6 +458,66 @@
         receiver.finishHost();
     }
 
+    private void doSomeWork(int procState) throws Exception {
+        final ICmdReceiver receiver;
+        switch (procState) {
+            case PROCESS_STATE_TOP:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                break;
+            case PROCESS_STATE_TOP_SLEEPING:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                break;
+            case PROCESS_STATE_FOREGROUND_SERVICE:
+                receiver = ICmdReceiver.Stub.asInterface(startForegroundService());
+                break;
+            case PROCESS_STATE_FOREGROUND:
+                receiver = ICmdReceiver.Stub.asInterface(startService());
+                receiver.showApplicationOverlay();
+                break;
+            case PROCESS_STATE_BACKGROUND:
+                receiver = ICmdReceiver.Stub.asInterface(startService());
+                break;
+            case PROCESS_STATE_CACHED:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                receiver.finishHost();
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown state: " + procState);
+        }
+        try {
+            assertProcState(procState);
+            receiver.doSomeWork(WORK_DURATION_MS);
+        } finally {
+            receiver.finishHost();
+        }
+    }
+
+    private void assertProcState(String state) throws Exception {
+        final String expectedState = "(" + state + ")";
+        assertDelayedCondition("", () -> {
+            final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+            final String actualState = uidStateStr.split(" ")[1];
+            return expectedState.equals(actualState) ? null
+                    : "expected=" + expectedState + ", actual" + actualState;
+        });
+    }
+
+    private void assertProcState(int expectedState) throws Exception {
+        assertDelayedCondition("Unexpected proc state", () -> {
+            final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+            final int amProcState = Integer.parseInt(uidStateStr.split(" ")[0]);
+            final int actualState = BatteryStats.mapToInternalProcessState(amProcState);
+            return (actualState == expectedState) ? null
+                    : "expected=" + getStateName(BatteryStats.Uid.class, expectedState)
+                            + ", actual=" + getStateName(BatteryStats.Uid.class, actualState)
+                            + ", amState=" + getStateName(ActivityManager.class, amProcState);
+        });
+    }
+
+    private String getStateName(Class clazz, int procState) {
+        return DebugUtils.valueToString(clazz, "PROCESS_STATE_", procState);
+    }
+
     private IBinder startIsolatedService() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final IBinder[] binders = new IBinder[1];
@@ -248,6 +547,59 @@
         return null;
     }
 
+    private IBinder startForegroundService() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE))
+                .setFlags(FLAG_START_FOREGROUND);
+        final Bundle extras = new Bundle();
+        final IBinder[] binders = new IBinder[1];
+        extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+            @Override
+            public void onLaunched(IBinder receiver) {
+                binders[0] = receiver;
+                latch.countDown();
+            }
+        });
+        launchIntent.putExtras(extras);
+        sContext.startForegroundService(launchIntent);
+        if (latch.await(START_FG_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the test fg service to start; testUid=" + sTestPkgUid);
+        }
+        return null;
+    }
+
+    private IBinder startService() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+        final Bundle extras = new Bundle();
+        final IBinder[] binders = new IBinder[1];
+        extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+            @Override
+            public void onLaunched(IBinder receiver) {
+                binders[0] = receiver;
+                latch.countDown();
+            }
+        });
+        launchIntent.putExtras(extras);
+        sContext.startService(launchIntent);
+        if (latch.await(START_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the test service to start; testUid=" + sTestPkgUid);
+        }
+        return null;
+    }
+
     private IBinder startActivity() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final Intent launchIntent = new Intent()
@@ -256,7 +608,7 @@
         final IBinder[] binders = new IBinder[1];
         extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
             @Override
-            public void onActivityLaunched(IBinder receiver) {
+            public void onLaunched(IBinder receiver) {
                 binders[0] = receiver;
                 latch.countDown();
             }
@@ -274,21 +626,63 @@
         return null;
     }
 
+    private static String getAllCpuTimesMsg() throws Exception {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("uid=" + sTestPkgUid + ";");
+        sb.append(UID_TIMES_TYPE_ALL + "=" + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid)));
+        for (int i = 0; i < NUM_PROCESS_STATE; ++i) {
+            sb.append("|");
+            sb.append(UID_PROCESS_TYPES[i] + "="
+                    + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid, i)));
+        }
+        return sb.toString();
+    }
+
+    private static String getMsgCpuTimesSum(long[] cpuTimes) throws Exception {
+        if (cpuTimes == null) {
+            return "(0,0)";
+        }
+        long totalTime = 0;
+        for (int i = 0; i < cpuTimes.length / 2; ++i) {
+            totalTime += cpuTimes[i];
+        }
+        long screenOffTime = 0;
+        for (int i = cpuTimes.length / 2; i < cpuTimes.length; ++i) {
+            screenOffTime += cpuTimes[i];
+        }
+        return "(" + totalTime + "," + screenOffTime + ")";
+    }
+
     private static long[] getAllCpuFreqTimes(int uid) throws Exception {
         final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
-        final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+        final Pattern pattern = Pattern.compile(uid + ",l,ctf," + UID_TIMES_TYPE_ALL + ",(.*?)\n");
         final Matcher matcher = pattern.matcher(checkinDump);
         if (!matcher.find()) {
             return null;
         }
-        final String[] uidTimesStr = matcher.group(1).split(",");
-        final int freqCount = Integer.parseInt(uidTimesStr[0]);
-        if (uidTimesStr.length != (2 * freqCount + 1)) {
-            fail("Malformed data: " + Arrays.toString(uidTimesStr));
+        return parseCpuTimesStr(matcher.group(1));
+    }
+
+    private static long[] getAllCpuFreqTimes(int uid, int procState) throws Exception {
+        final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+        final Pattern pattern = Pattern.compile(
+                uid + ",l,ctf," + UID_PROCESS_TYPES[procState] + ",(.*?)\n");
+        final Matcher matcher = pattern.matcher(checkinDump);
+        if (!matcher.find()) {
+            return null;
+        }
+        return parseCpuTimesStr(matcher.group(1));
+    }
+
+    private static long[] parseCpuTimesStr(String str) {
+        final String[] cpuTimesStr = str.split(",");
+        final int freqCount = Integer.parseInt(cpuTimesStr[0]);
+        if (cpuTimesStr.length != (2 * freqCount + 1)) {
+            fail("Malformed data: " + Arrays.toString(cpuTimesStr));
         }
         final long[] cpuTimes = new long[freqCount * 2];
         for (int i = 0; i < cpuTimes.length; ++i) {
-            cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+            cpuTimes[i] = Long.parseLong(cpuTimesStr[i + 1]);
         }
         return cpuTimes;
     }
@@ -312,12 +706,12 @@
         screenOn();
     }
 
-    private void batteryOn() throws Exception {
+    private static void batteryOn() throws Exception {
         executeCmd("dumpsys battery unplug");
         assertBatteryState(false);
     }
 
-    private void batteryOff() throws Exception {
+    private static void batteryOff() throws Exception {
         executeCmd("dumpsys battery reset");
         assertBatteryState(true);
     }
@@ -336,43 +730,41 @@
 
     private void forceStop() throws Exception {
         executeCmd("cmd activity force-stop " + TEST_PKG);
-        assertUidState(PROCESS_STATE_NONEXISTENT);
+        assertProcState("NONEXISTENT");
     }
 
-    private void assertUidState(int state) throws Exception {
-        final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
-        final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
-        assertEquals(state, uidState);
-    }
-
-    private void assertKeyguardUnLocked() {
+    private void assertKeyguardUnLocked() throws Exception {
         final KeyguardManager keyguardManager =
                 (KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
-        assertDelayedCondition("Keyguard should be unlocked",
-                () -> !keyguardManager.isKeyguardLocked());
+        assertDelayedCondition("Unexpected Keyguard state", () ->
+                keyguardManager.isKeyguardLocked() ? "expected=unlocked" : null
+        );
     }
 
-    private void assertScreenInteractive(boolean interactive) {
+    private void assertScreenInteractive(boolean interactive) throws Exception {
         final PowerManager powerManager =
                 (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
-        assertDelayedCondition("Unexpected screen interactive state",
-                () -> interactive == powerManager.isInteractive());
+        assertDelayedCondition("Unexpected screen interactive state", () ->
+                interactive == powerManager.isInteractive() ? null : "expected=" + interactive
+        );
     }
 
-    private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
+            throws Exception {
         final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
         while (SystemClock.uptimeMillis() <= endTime) {
-            if (condition.isTrue()) {
+            if (condition.getErrIfNotTrue() == null) {
                 return;
             }
             SystemClock.sleep(GENERAL_INTERVAL_MS);
         }
-        if (!condition.isTrue()) {
-            fail(errorMsg);
+        final String errMsg = condition.getErrIfNotTrue();
+        if (errMsg != null) {
+            fail(errMsgPrefix + ": " + errMsg);
         }
     }
 
-    private void assertBatteryState(boolean pluggedIn) throws Exception {
+    private static void assertBatteryState(boolean pluggedIn) throws Exception {
         final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
         while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
             Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
@@ -383,13 +775,13 @@
         }
     }
 
-    private boolean isDevicePluggedIn() {
+    private static boolean isDevicePluggedIn() {
         final Intent batteryIntent = sContext.registerReceiver(null,
                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
     }
 
-    private String executeCmd(String cmd) throws Exception {
+    private static String executeCmd(String cmd) throws Exception {
         final String result = sUiDevice.executeShellCommand(cmd).trim();
         Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
         return result;
@@ -400,6 +792,6 @@
     }
 
     private interface ExpectedCondition {
-        boolean isTrue();
+        String getErrIfNotTrue() throws Exception;
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
new file mode 100644
index 0000000..5d72942
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelSingleUidTimeReader.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleUidTimeReaderTest {
+    private final static int TEST_UID = 2222;
+    private final static int TEST_FREQ_COUNT = 5;
+
+    private KernelSingleUidTimeReader mReader;
+    private TestInjector mInjector;
+
+    @Before
+    public void setUp() {
+        mInjector = new TestInjector();
+        mReader = new KernelSingleUidTimeReader(TEST_FREQ_COUNT, mInjector);
+    }
+
+    @Test
+    public void readDelta() {
+        final SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+        long[] latestCpuTimes = new long[] {120, 130, 140, 150, 160};
+        mInjector.setData(latestCpuTimes);
+        long[] deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        long[] expectedDeltaTimes = new long[] {200, 340, 1230, 490, 4890};
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            latestCpuTimes[i] += expectedDeltaTimes[i];
+        }
+        mInjector.setData(latestCpuTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // delta should be null if cpu times haven't changed
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed data (-ve)
+        long[] malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = -4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        mInjector.setData(malformedLatestTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed data (decreased)
+        malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        mInjector.setData(malformedLatestTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+    }
+
+    @Test
+    public void readDelta_fileNotAvailable() {
+        mInjector.letReadDataThrowException(true);
+
+        for (int i = 0; i < KernelSingleUidTimeReader.TOTAL_READ_ERROR_COUNT; ++i) {
+            assertTrue(mReader.singleUidCpuTimesAvailable());
+            mReader.readDeltaMs(TEST_UID);
+        }
+        assertFalse(mReader.singleUidCpuTimesAvailable());
+    }
+
+    @Test
+    public void testComputeDelta() {
+        // proc file not available
+        mReader.setSingleUidCpuTimesAvailable(false);
+        long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+        long[] deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        // cpu times have changed
+        mReader.setSingleUidCpuTimesAvailable(true);
+        SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+        long[] lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        allLastCpuTimes.put(TEST_UID, lastCpuTimes);
+        long[] expectedDeltaTimes = new long[] {123, 324, 43, 989, 80};
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            latestCpuTimes[i] = lastCpuTimes[i] + expectedDeltaTimes[i];
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // no change in cpu times
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed cpu times (-ve)
+        long[] malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = -4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed cpu times (decreased)
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+    }
+
+    @Test
+    public void testGetDelta() {
+        // No last cpu times
+        long[] lastCpuTimes = null;
+        long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+        long[] deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+
+        // Latest cpu times are -ve
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {15, -10, 19, 21, 23};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        // Latest cpu times are less than last cpu times
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {15, 11, 21, 34, 171};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {112, 213, 314, 415, 516};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(new long[] {100, 200, 300, 400, 500}, deltaCpuTimes);
+    }
+
+    @Test
+    public void testRemoveUid() {
+        final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+        lastUidCpuTimes.put(12, new long[] {});
+        lastUidCpuTimes.put(16, new long[] {});
+
+        mReader.removeUid(12);
+        assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+                lastUidCpuTimes.indexOfKey(12) >= 0);
+        mReader.removeUid(16);
+        assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+                lastUidCpuTimes.indexOfKey(16) >= 0);
+    }
+
+    @Test
+    public void testRemoveUidsRange() {
+        final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+        final int startUid = 12;
+        final int endUid = 24;
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid, endUid);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid - 1, endUid);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid, endUid + 1);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid - 1, endUid + 1);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+    }
+
+    private void assertCpuTimesEqual(long[] expected, long[] actual) {
+        assertArrayEquals("Expected=" + Arrays.toString(expected)
+                + ", Actual=" + Arrays.toString(actual), expected, actual);
+    }
+
+    class TestInjector extends Injector {
+        private byte[] mData;
+        private boolean mThrowExcpetion;
+
+        @Override
+        public byte[] readData(String procFile) throws IOException {
+            if (mThrowExcpetion) {
+                throw new IOException("In the test");
+            } else {
+                return mData;
+            }
+        }
+
+        public void setData(long[] cpuTimes) {
+            final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
+            buffer.order(ByteOrder.nativeOrder());
+            for (long time : cpuTimes) {
+                buffer.putLong(time / 10);
+            }
+            mData = buffer.array();
+        }
+
+        public void letReadDataThrowException(boolean throwException) {
+            mThrowExcpetion = throwException;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 63d1e5a..de2fd12 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.os;
 
+import android.util.SparseIntArray;
+
 import java.util.ArrayList;
+import java.util.concurrent.Future;
 
 /**
  * Mocks a BatteryStatsImpl object.
@@ -33,6 +36,7 @@
         mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
                 mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+        setExternalStatsSyncLocked(new DummyExternalStatsSync());
     }
 
     MockBatteryStatsImpl() {
@@ -78,6 +82,11 @@
         return this;
     }
 
+    public MockBatteryStatsImpl setKernelSingleUidTimeReader(KernelSingleUidTimeReader reader) {
+        mKernelSingleUidTimeReader = reader;
+        return this;
+    }
+
     public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
         mKernelCpuSpeedReaders = readers;
         return this;
@@ -102,5 +111,32 @@
         mOnBatteryInternal = onBatteryInternal;
         return this;
     }
+
+    public SparseIntArray getPendingUids() {
+        return mPendingUids;
+    }
+
+    private class DummyExternalStatsSync implements ExternalStatsSync {
+        @Override
+        public Future<?> scheduleSync(String reason, int flags) {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleReadProcStateCpuTimes() {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+            return null;
+        }
+
+    }
 }
 
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
index 193e601..b255c7e 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
index 9f44767..428b1f8 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsBTTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
index b4d354c..93ece86 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsDiffKeyTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
index 969e588..b348966 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsFLTestApp
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
index c1e8c98..5bfde78 100644
--- a/core/tests/packagemanagertests/Android.mk
+++ b/core/tests/packagemanagertests/Android.mk
@@ -10,7 +10,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    frameworks-base-testutils
+    frameworks-base-testutils \
+    mockito-target-minus-junit4
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk
new file mode 100644
index 0000000..7b11225
--- /dev/null
+++ b/core/tests/privacytests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/privacytests/AndroidManifest.xml b/core/tests/privacytests/AndroidManifest.xml
new file mode 100644
index 0000000..a0e5281
--- /dev/null
+++ b/core/tests/privacytests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.coretests.privacy">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.coretests.privacy"
+            android:label="Frameworks Privacy Library Tests" />
+
+</manifest>
diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java
new file mode 100644
index 0000000..9166438
--- /dev/null
+++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java
@@ -0,0 +1,392 @@
+/*
+ * 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.privacy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig;
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * Unit test for the {@link LongitudinalReportingEncoder}.
+ *
+ * As {@link LongitudinalReportingEncoder} is based on Rappor,
+ * most cases are covered by Rappor tests already.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LongitudinalReportingEncoderTest {
+
+    @Test
+    public void testLongitudinalReportingEncoder_config() throws Exception {
+        final LongitudinalReportingConfig config = new LongitudinalReportingConfig(
+                "Foo",  // encoderId
+                0.4,  // probabilityF
+                0.25,  // probabilityP
+                1);  // probabilityQ
+        final LongitudinalReportingEncoder encoder =
+                LongitudinalReportingEncoder.createInsecureEncoderForTest(
+                        config);
+        assertEquals("LongitudinalReporting", encoder.getConfig().getAlgorithm());
+        assertEquals(
+                "EncoderId: Foo, ProbabilityF: 0.400, ProbabilityP: 0.250, ProbabilityQ: 1.000",
+                encoder.getConfig().toString());
+    }
+
+    @Test
+    public void testLongitudinalReportingEncoder_basicIRRTest() throws Exception {
+        // Test if IRR can generate expected result when seed is fixed (insecure encoder)
+        final LongitudinalReportingConfig config = new LongitudinalReportingConfig(
+                "Foo",  // encoderId
+                0.4,  // probabilityF
+                0,  // probabilityP
+                0);  // probabilityQ
+        // Use insecure encoder here to make sure seed is set.
+        final LongitudinalReportingEncoder encoder =
+                LongitudinalReportingEncoder.createInsecureEncoderForTest(
+                        config);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(0, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(0, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+        assertEquals(1, encoder.encodeBoolean(true)[0]);
+
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(1, encoder.encodeBoolean(false)[0]);
+        assertEquals(1, encoder.encodeBoolean(false)[0]);
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(1, encoder.encodeBoolean(false)[0]);
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(0, encoder.encodeBoolean(false)[0]);
+        assertEquals(1, encoder.encodeBoolean(false)[0]);
+
+        // Test if IRR returns original result when f = 0
+        final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+                "Foo",  // encoderId
+                0,  // probabilityF
+                0,  // probabilityP
+                0);  // probabilityQ
+        final LongitudinalReportingEncoder encoder2
+                = LongitudinalReportingEncoder.createEncoder(
+                config2, makeTestingUserSecret("secret2"));
+        for (int i = 0; i < 10; i++) {
+            assertEquals(1, encoder2.encodeBoolean(true)[0]);
+        }
+        for (int i = 0; i < 10; i++) {
+            assertEquals(0, encoder2.encodeBoolean(false)[0]);
+        }
+
+        // Test if IRR returns opposite result when f = 1
+        final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig(
+                "Foo",  // encoderId
+                1,  // probabilityF
+                0,  // probabilityP
+                0);  // probabilityQ
+        final LongitudinalReportingEncoder encoder3
+                = LongitudinalReportingEncoder.createEncoder(
+                config3, makeTestingUserSecret("secret3"));
+        for (int i = 0; i < 10; i++) {
+            assertEquals(1, encoder3.encodeBoolean(false)[0]);
+        }
+        for (int i = 0; i < 10; i++) {
+            assertEquals(0, encoder3.encodeBoolean(true)[0]);
+        }
+    }
+
+    @Test
+    public void testLongitudinalReportingEncoder_basicPRRTest() throws Exception {
+        // Should always return original value when p = 0
+        for (int i = 0; i < 10; i++) {
+            for (int j = 0; j < 10; j++) {
+                final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig(
+                        "Foo" + i,  // encoderId
+                        0,  // probabilityF
+                        0,  // probabilityP
+                        0);  // probabilityQ
+                final LongitudinalReportingEncoder encoder1
+                        = LongitudinalReportingEncoder.createEncoder(
+                        config1, makeTestingUserSecret("encoder" + j));
+                assertEquals(0, encoder1.encodeBoolean(false)[0]);
+                assertEquals(0, encoder1.encodeBoolean(false)[0]);
+                assertEquals(0, encoder1.encodeBoolean(false)[0]);
+                assertEquals(0, encoder1.encodeBoolean(false)[0]);
+                assertEquals(0, encoder1.encodeBoolean(false)[0]);
+                assertEquals(1, encoder1.encodeBoolean(true)[0]);
+                assertEquals(1, encoder1.encodeBoolean(true)[0]);
+                assertEquals(1, encoder1.encodeBoolean(true)[0]);
+                assertEquals(1, encoder1.encodeBoolean(true)[0]);
+                assertEquals(1, encoder1.encodeBoolean(true)[0]);
+            }
+        }
+
+        // Should always return false when p = 1, q = 0
+        for (int i = 0; i < 10; i++) {
+            for (int j = 0; j < 10; j++) {
+                final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+                        "Foo" + i,  // encoderId
+                        0,  // probabilityF
+                        1,  // probabilityP
+                        0);  // probabilityQ
+                final LongitudinalReportingEncoder encoder2
+                        = LongitudinalReportingEncoder.createEncoder(
+                        config2, makeTestingUserSecret("encoder" + j));
+                assertEquals(0, encoder2.encodeBoolean(false)[0]);
+                assertEquals(0, encoder2.encodeBoolean(false)[0]);
+                assertEquals(0, encoder2.encodeBoolean(false)[0]);
+                assertEquals(0, encoder2.encodeBoolean(false)[0]);
+                assertEquals(0, encoder2.encodeBoolean(false)[0]);
+                assertEquals(0, encoder2.encodeBoolean(true)[0]);
+                assertEquals(0, encoder2.encodeBoolean(true)[0]);
+                assertEquals(0, encoder2.encodeBoolean(true)[0]);
+                assertEquals(0, encoder2.encodeBoolean(true)[0]);
+                assertEquals(0, encoder2.encodeBoolean(true)[0]);
+            }
+        }
+
+        // Should always return true when p = 1, q = 1
+        for (int i = 0; i < 10; i++) {
+            for (int j = 0; j < 10; j++) {
+                final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig(
+                        "Foo" + i,  // encoderId
+                        0,  // probabilityF
+                        1,  // probabilityP
+                        1);  // probabilityQ
+                final LongitudinalReportingEncoder encoder3
+                        = LongitudinalReportingEncoder.createEncoder(
+                        config3, makeTestingUserSecret("encoder" + j));
+                assertEquals(1, encoder3.encodeBoolean(false)[0]);
+                assertEquals(1, encoder3.encodeBoolean(false)[0]);
+                assertEquals(1, encoder3.encodeBoolean(false)[0]);
+                assertEquals(1, encoder3.encodeBoolean(false)[0]);
+                assertEquals(1, encoder3.encodeBoolean(false)[0]);
+                assertEquals(1, encoder3.encodeBoolean(true)[0]);
+                assertEquals(1, encoder3.encodeBoolean(true)[0]);
+                assertEquals(1, encoder3.encodeBoolean(true)[0]);
+                assertEquals(1, encoder3.encodeBoolean(true)[0]);
+                assertEquals(1, encoder3.encodeBoolean(true)[0]);
+            }
+        }
+
+        // PRR should return different value when encoder id is changed
+        boolean hasFalseResult1 = false;
+        boolean hasTrueResult1 = false;
+        for (int i = 0; i < 50; i++) {
+            boolean firstResult = false;
+            for (int j = 0; j < 10; j++) {
+                final LongitudinalReportingConfig config4 = new LongitudinalReportingConfig(
+                        "Foo" + i,  // encoderId
+                        0,  // probabilityF
+                        1,  // probabilityP
+                        0.5);  // probabilityQ
+                final LongitudinalReportingEncoder encoder4
+                        = LongitudinalReportingEncoder.createEncoder(
+                        config4, makeTestingUserSecret("encoder4"));
+                boolean encodedFalse = encoder4.encodeBoolean(false)[0] > 0;
+                boolean encodedTrue = encoder4.encodeBoolean(true)[0] > 0;
+                // PRR should always give the same value when all parameters are the same
+                assertEquals(encodedTrue, encodedFalse);
+                if (j == 0) {
+                    firstResult = encodedTrue;
+                } else {
+                    assertEquals(firstResult, encodedTrue);
+                }
+                if (encodedTrue) {
+                    hasTrueResult1 = true;
+                } else {
+                    hasFalseResult1 = true;
+                }
+            }
+        }
+        // Ensure it has both true and false results when encoder id is different
+        assertTrue(hasTrueResult1);
+        assertTrue(hasFalseResult1);
+
+        // PRR should give different value when secret is changed
+        boolean hasFalseResult2 = false;
+        boolean hasTrueResult2 = false;
+        for (int i = 0; i < 50; i++) {
+            boolean firstResult = false;
+            for (int j = 0; j < 10; j++) {
+                final LongitudinalReportingConfig config5 = new LongitudinalReportingConfig(
+                        "Foo",  // encoderId
+                        0,  // probabilityF
+                        1,  // probabilityP
+                        0.5);  // probabilityQ
+                final LongitudinalReportingEncoder encoder5
+                        = LongitudinalReportingEncoder.createEncoder(
+                        config5, makeTestingUserSecret("encoder" + i));
+                boolean encodedFalse = encoder5.encodeBoolean(false)[0] > 0;
+                boolean encodedTrue = encoder5.encodeBoolean(true)[0] > 0;
+                // PRR should always give the same value when parameters are the same
+                assertEquals(encodedTrue, encodedFalse);
+                if (j == 0) {
+                    firstResult = encodedTrue;
+                } else {
+                    assertEquals(firstResult, encodedTrue);
+                }
+                if (encodedTrue) {
+                    hasTrueResult2 = true;
+                } else {
+                    hasFalseResult2 = true;
+                }
+            }
+        }
+        // Ensure it has both true and false results when encoder id is different
+        assertTrue(hasTrueResult2);
+        assertTrue(hasFalseResult2);
+
+        // Confirm if PRR randomizer is working correctly
+        final int n1 = 1000;
+        final double p1 = 0.8;
+        final double expectedTrueSum1 = n1 * p1;
+        final double valueRange1 = 5 * Math.sqrt(n1 * p1 * (1 - p1));
+        int trueSum1 = 0;
+        for (int i = 0; i < n1; i++) {
+            final LongitudinalReportingConfig config6 = new LongitudinalReportingConfig(
+                    "Foo",  // encoderId
+                    0,  // probabilityF
+                    p1,  // probabilityP
+                    1);  // probabilityQ
+            final LongitudinalReportingEncoder encoder6
+                    = LongitudinalReportingEncoder.createEncoder(
+                    config6, makeTestingUserSecret("encoder" + i));
+            boolean encodedFalse = encoder6.encodeBoolean(false)[0] > 0;
+            if (encodedFalse) {
+                trueSum1 += 1;
+            }
+        }
+        // Total number of true(s) should be around the mean (1000 * 0.8)
+        assertTrue(trueSum1 < expectedTrueSum1 + valueRange1);
+        assertTrue(trueSum1 > expectedTrueSum1 - valueRange1);
+
+        // Confirm if PRR randomizer is working correctly
+        final int n2 = 1000;
+        final double p2 = 0.2;
+        final double expectedTrueSum2 = n2 * p2;
+        final double valueRange2 = 5 * Math.sqrt(n2 * p2 * (1 - p2));
+        int trueSum2 = 0;
+        for (int i = 0; i < n2; i++) {
+            final LongitudinalReportingConfig config7 = new LongitudinalReportingConfig(
+                    "Foo",  // encoderId
+                    0,  // probabilityF
+                    p2,  // probabilityP
+                    1);  // probabilityQ
+            final LongitudinalReportingEncoder encoder7
+                    = LongitudinalReportingEncoder.createEncoder(
+                    config7, makeTestingUserSecret("encoder" + i));
+            boolean encodedFalse = encoder7.encodeBoolean(false)[0] > 0;
+            if (encodedFalse) {
+                trueSum2 += 1;
+            }
+        }
+        // Total number of true(s) should be around the mean (1000 * 0.2)
+        assertTrue(trueSum2 < expectedTrueSum2 + valueRange2);
+        assertTrue(trueSum2 > expectedTrueSum2 - valueRange2);
+    }
+
+    @Test
+    public void testLongitudinalReportingEncoder_basicIRRwithPRRTest() throws Exception {
+        // Verify PRR result will run IRR
+        boolean hasFalseResult1 = false;
+        boolean hasTrueResult1 = false;
+        for (int i = 0; i < 50; i++) {
+            final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig(
+                    "Foo",  // encoderId
+                    0.5,  // probabilityF
+                    1,  // probabilityP
+                    1);  // probabilityQ
+            final LongitudinalReportingEncoder encoder1
+                    = LongitudinalReportingEncoder.createEncoder(
+                    config1, makeTestingUserSecret("encoder1"));
+            if (encoder1.encodeBoolean(false)[0] > 0) {
+                hasTrueResult1 = true;
+            } else {
+                hasFalseResult1 = true;
+            }
+        }
+        assertTrue(hasTrueResult1);
+        assertTrue(hasFalseResult1);
+
+        // When secret is different, some device should use PRR result, some should use IRR result
+        boolean hasFalseResult2 = false;
+        boolean hasTrueResult2 = false;
+        for (int i = 0; i < 50; i++) {
+            final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+                    "Foo",  // encoderId
+                    1,  // probabilityF
+                    0.5,  // probabilityP
+                    1);  // probabilityQ
+            final LongitudinalReportingEncoder encoder2
+                    = LongitudinalReportingEncoder.createEncoder(
+                    config2, makeTestingUserSecret("encoder" + i));
+            if (encoder2.encodeBoolean(false)[0] > 0) {
+                hasTrueResult2 = true;
+            } else {
+                hasFalseResult2 = true;
+            }
+        }
+        assertTrue(hasTrueResult2);
+        assertTrue(hasFalseResult2);
+    }
+
+    @Test
+    public void testLongTermRandomizedResult() throws Exception {
+        // Verify getLongTermRandomizedResult can return expected result when parameters are fixed.
+        final boolean[] expectedResult =
+                new boolean[]{true, false, true, true, true,
+                        false, false, false, true, false,
+                        false, false, false, true, true,
+                        true, true, false, true, true,
+                        true, true, false, true, true};
+        for (int i = 0; i < 5; i++) {
+            for (int j = 0; j < 5; j++) {
+                boolean result = LongitudinalReportingEncoder.getLongTermRandomizedResult(0.5,
+                        true, makeTestingUserSecret("secret" + i), "encoder" + j);
+                assertEquals(expectedResult[i * 5 + j], result);
+            }
+        }
+    }
+
+    private static byte[] makeTestingUserSecret(String testingSecret) throws Exception {
+        // We generate the fake user secret by concatenating three copies of the
+        // 16 byte MD5 hash of the testingSecret string encoded in UTF 8.
+        MessageDigest md5 = MessageDigest.getInstance("MD5");
+        byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8));
+        assertEquals(16, digest.length);
+        return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array();
+    }
+}
diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java
new file mode 100644
index 0000000..dad98b8
--- /dev/null
+++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.privacy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.privacy.internal.rappor.RapporConfig;
+import android.privacy.internal.rappor.RapporEncoder;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * Unit test for the {@link RapporEncoder}.
+ * Most of the tests are done in external/rappor/client/javatest/ already.
+ * Tests here are just make sure the {@link RapporEncoder} wrap Rappor correctly.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RapporEncoderTest {
+
+    @Test
+    public void testRapporEncoder_config() throws Exception {
+        final RapporConfig config = new RapporConfig(
+                "Foo",  // encoderId
+                8,  // numBits,
+                13.0 / 128.0,  // probabilityF
+                0.25,  // probabilityP
+                0.75,  // probabilityQ
+                1,  // numCohorts
+                2);  // numBloomHashes)
+        final RapporEncoder encoder = RapporEncoder.createEncoder(config,
+                makeTestingUserSecret("encoder1"));
+        assertEquals("Rappor", encoder.getConfig().getAlgorithm());
+        assertEquals("EncoderId: Foo, NumBits: 8, ProbabilityF: 0.102, "
+                + "ProbabilityP: 0.250, ProbabilityQ: 0.750, NumCohorts: 1, "
+                + "NumBloomHashes: 2", encoder.getConfig().toString());
+    }
+
+    @Test
+    public void testRapporEncoder_basicIRRTest() throws Exception {
+        final RapporConfig config = new RapporConfig(
+                "Foo", // encoderId
+                12, // numBits,
+                0, // probabilityF
+                0, // probabilityP
+                1, // probabilityQ
+                1, // numCohorts (so must be cohort 0)
+                2);  // numBloomHashes
+        // Use insecure encoder here as we want to get the exact output.
+        final RapporEncoder encoder = RapporEncoder.createInsecureEncoderForTest(config);
+        assertEquals(768, toLong(encoder.encodeString("Testing")));
+    }
+
+    @Test
+    public void testRapporEncoder_IRRWithPRR() throws Exception {
+        int numBits = 8;
+        final long inputValue = 254L;
+        final long prrValue = 250L;
+        final long prrAndIrrValue = 184L;
+
+        final RapporConfig config1 = new RapporConfig(
+                "Foo", // encoderId
+                numBits, // numBits,
+                0.25, // probabilityF
+                0, // probabilityP
+                1, // probabilityQ
+                1, // numCohorts
+                2); // numBloomHashes
+        // Use insecure encoder here as we want to get the exact output.
+        final RapporEncoder encoder1 = RapporEncoder.createInsecureEncoderForTest(config1);
+        // Verify that PRR is working as expected.
+        assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue))));
+        assertTrue(encoder1.isInsecureEncoderForTest());
+
+        // Verify that IRR is working as expected.
+        final RapporConfig config2 = new RapporConfig(
+                "Foo", // encoderId
+                numBits, // numBits,
+                0, // probabilityF
+                0.3, // probabilityP
+                0.7, // probabilityQ
+                1, // numCohorts
+                2); // numBloomHashes
+        // Use insecure encoder here as we want to get the exact output.
+        final RapporEncoder encoder2 = RapporEncoder.createInsecureEncoderForTest(config2);
+        assertEquals(prrAndIrrValue, toLong(encoder2.encodeBits(toBytes(prrValue))));
+
+        // Test that end-to-end is the result of PRR + IRR.
+        final RapporConfig config3 = new RapporConfig(
+                "Foo", // encoderId
+                numBits, // numBits,
+                0.25, // probabilityF
+                0.3, // probabilityP
+                0.7, // probabilityQ
+                1, // numCohorts
+                2); // numBloomHashes
+        final RapporEncoder encoder3 = RapporEncoder.createInsecureEncoderForTest(config3);
+        // Verify that PRR is working as expected.
+        assertEquals(prrAndIrrValue, toLong(encoder3.encodeBits(toBytes(inputValue))));
+    }
+
+    @Test
+    public void testRapporEncoder_ensureSecureEncoderIsSecure() throws Exception {
+        int numBits = 8;
+        final long inputValue = 254L;
+        final long prrValue = 250L;
+        final long prrAndIrrValue = 184L;
+
+        final RapporConfig config1 = new RapporConfig(
+                "Foo", // encoderId
+                numBits, // numBits,
+                0.25, // probabilityF
+                0, // probabilityP
+                1, // probabilityQ
+                1, // numCohorts
+                2); // numBloomHashes
+        final RapporEncoder encoder1 = RapporEncoder.createEncoder(config1,
+                makeTestingUserSecret("secret1"));
+        // Verify that PRR is working as expected, not affected by random seed.
+        assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue))));
+        assertFalse(encoder1.isInsecureEncoderForTest());
+
+        boolean hasDifferentResult2 = false;
+        for (int i = 0; i < 5; i++) {
+            final RapporConfig config2 = new RapporConfig(
+                    "Foo", // encoderId
+                    numBits, // numBits,
+                    0, // probabilityF
+                    0.3, // probabilityP
+                    0.7, // probabilityQ
+                    1, // numCohorts
+                    2); // numBloomHashes
+            final RapporEncoder encoder2 = RapporEncoder.createEncoder(config2,
+                    makeTestingUserSecret("secret1"));
+            hasDifferentResult2 |= (prrAndIrrValue != toLong(
+                    encoder2.encodeBits(toBytes(prrValue))));
+        }
+        // Ensure it's not getting same result as it has random seed while encoder id and secret
+        // is the same.
+        assertTrue(hasDifferentResult2);
+
+        boolean hasDifferentResults3 = false;
+        for (int i = 0; i < 5; i++) {
+            final RapporConfig config3 = new RapporConfig(
+                    "Foo", // encoderId
+                    numBits, // numBits,
+                    0.25, // probabilityF
+                    0.3, // probabilityP
+                    0.7, // probabilityQ
+                    1, // numCohorts
+                    2); // numBloomHashes
+            final RapporEncoder encoder3 = RapporEncoder.createEncoder(config3,
+                    makeTestingUserSecret("secret1"));
+            hasDifferentResults3 |= (prrAndIrrValue != toLong(
+                    encoder3.encodeBits(toBytes(inputValue))));
+        }
+        // Ensure it's not getting same result as it has random seed while encoder id and secret
+        // is the same.
+        assertTrue(hasDifferentResults3);
+    }
+
+    private static byte[] toBytes(long value) {
+        return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array();
+    }
+
+    private static long toLong(byte[] bytes) {
+        ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes);
+        buffer.rewind();
+        return buffer.getLong();
+    }
+
+    private static byte[] makeTestingUserSecret(String testingSecret) throws Exception {
+        // We generate the fake user secret by concatenating three copies of the
+        // 16 byte MD5 hash of the testingSecret string encoded in UTF 8.
+        MessageDigest md5 = MessageDigest.getInstance("MD5");
+        byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8));
+        assertEquals(16, digest.length);
+        return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array();
+    }
+}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2333fec..1affba0 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -150,6 +150,7 @@
     <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8e7147c..7bb2859 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,7 @@
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
         <permission name="android.permission.MANAGE_USB"/>
diff --git a/data/sounds/AudioPackageGo.mk b/data/sounds/AudioPackageGo.mk
index ae742df..0296219 100644
--- a/data/sounds/AudioPackageGo.mk
+++ b/data/sounds/AudioPackageGo.mk
@@ -35,6 +35,7 @@
     $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
     $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
     $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
+    $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
     $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
     $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
     $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
new file mode 100644
index 0000000..60416a7
--- /dev/null
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -0,0 +1,665 @@
+/*
+ * 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.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RawRes;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ *  Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ *  @hide
+ */
+public final class ImageDecoder {
+    /**
+     *  Source of the encoded image data.
+     */
+    public static abstract class Source {
+        /* @hide */
+        Resources getResources() { return null; }
+
+        /* @hide */
+        void close() {}
+
+        /* @hide */
+        abstract ImageDecoder createImageDecoder();
+    };
+
+    private static class ByteArraySource extends Source {
+        ByteArraySource(byte[] data, int offset, int length) {
+            mData = data;
+            mOffset = offset;
+            mLength = length;
+        };
+        private final byte[] mData;
+        private final int    mOffset;
+        private final int    mLength;
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            return nCreate(mData, mOffset, mLength);
+        }
+    }
+
+    private static class ByteBufferSource extends Source {
+        ByteBufferSource(ByteBuffer buffer) {
+            mBuffer = buffer;
+        }
+        private final ByteBuffer mBuffer;
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            if (!mBuffer.isDirect() && mBuffer.hasArray()) {
+                int offset = mBuffer.arrayOffset() + mBuffer.position();
+                int length = mBuffer.limit() - mBuffer.position();
+                return nCreate(mBuffer.array(), offset, length);
+            }
+            return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+        }
+    }
+
+    private static class ResourceSource extends Source {
+        ResourceSource(Resources res, int resId)
+                throws Resources.NotFoundException {
+            // Test that the resource can be found.
+            InputStream is = null;
+            try {
+                is = res.openRawResource(resId);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+
+            mResources = res;
+            mResId = resId;
+        }
+
+        final Resources mResources;
+        final int       mResId;
+        // This is just stored here in order to keep the underlying Asset
+        // alive. FIXME: Can I access the Asset (and keep it alive) without
+        // this object?
+        InputStream mInputStream;
+
+        @Override
+        public Resources getResources() { return mResources; }
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            // FIXME: Can I bypass creating the stream?
+            try {
+                mInputStream = mResources.openRawResource(mResId);
+            } catch (Resources.NotFoundException e) {
+                // This should never happen, since we already tested in the
+                // constructor.
+            }
+            if (!(mInputStream instanceof AssetManager.AssetInputStream)) {
+                // This should never happen.
+                throw new RuntimeException("Resource is not an asset?");
+            }
+            long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset();
+            return nCreate(asset);
+        }
+
+        @Override
+        public void close() {
+            try {
+                mInputStream.close();
+            } catch (IOException e) {
+            } finally {
+                mInputStream = null;
+            }
+        }
+    }
+
+    /**
+     *  Contains information about the encoded image.
+     */
+    public static class ImageInfo {
+        public final int width;
+        public final int height;
+        // TODO?: Add more info? mimetype, ninepatch etc?
+
+        ImageInfo(int width, int height) {
+            this.width = width;
+            this.height = height;
+        }
+    };
+
+    /**
+     *  Used if the provided data is incomplete.
+     *
+     *  There may be a partial image to display.
+     */
+    public class IncompleteException extends Exception {};
+
+    /**
+     *  Used if the provided data is corrupt.
+     *
+     *  There may be a partial image to display.
+     */
+    public class CorruptException extends Exception {};
+
+    /**
+     *  Optional listener supplied to {@link #decodeDrawable} or
+     *  {@link #decodeBitmap}.
+     */
+    public static interface OnHeaderDecodedListener {
+        /**
+         *  Called when the header is decoded and the size is known.
+         *
+         *  @param info Information about the encoded image.
+         *  @param decoder allows changing the default settings of the decode.
+         */
+        public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder);
+
+    };
+
+    /**
+     *  Optional listener supplied to the ImageDecoder.
+     */
+    public static interface OnExceptionListener {
+        /**
+         *  Called when there is a problem in the stream or in the data.
+         *  FIXME: Or do not allow streams?
+         *  FIXME: Report how much of the image has been decoded?
+         *
+         *  @param e Exception containing information about the error.
+         *  @return True to create and return a {@link Drawable}/
+         *      {@link Bitmap} with partial data. False to return
+         *      {@code null}. True is the default.
+         */
+        public boolean onException(Exception e);
+    };
+
+    // Fields
+    private long      mNativePtr;
+    private final int mWidth;
+    private final int mHeight;
+
+    private int     mDesiredWidth;
+    private int     mDesiredHeight;
+    private int     mAllocator = DEFAULT_ALLOCATOR;
+    private boolean mRequireUnpremultiplied = false;
+    private boolean mMutable = false;
+    private boolean mPreferRamOverQuality = false;
+    private boolean mAsAlphaMask = false;
+    private Rect    mCropRect;
+
+    private PostProcess         mPostProcess;
+    private OnExceptionListener mOnExceptionListener;
+
+
+    /**
+     * Private constructor called by JNI. {@link #recycle} must be
+     * called after decoding to delete native resources.
+     */
+    @SuppressWarnings("unused")
+    private ImageDecoder(long nativePtr, int width, int height) {
+        mNativePtr = nativePtr;
+        mWidth = width;
+        mHeight = height;
+        mDesiredWidth = width;
+        mDesiredHeight = height;
+    }
+
+    /**
+     * Create a new {@link Source} from an asset.
+     *
+     * @param res the {@link Resources} object containing the image data.
+     * @param resId resource ID of the image data.
+     *      // FIXME: Can be an @DrawableRes?
+     * @return a new Source object, which can be passed to
+     *      {@link #decodeDrawable} or {@link #decodeBitmap}.
+     * @throws Resources.NotFoundException if the asset does not exist.
+     */
+    public static Source createSource(@NonNull Resources res, @RawRes int resId)
+            throws Resources.NotFoundException {
+        return new ResourceSource(res, resId);
+    }
+
+    /**
+     * Create a new {@link Source} from a byte array.
+     * @param data byte array of compressed image data.
+     * @param offset offset into data for where the decoder should begin
+     *      parsing.
+     * @param length number of bytes, beginning at offset, to parse.
+     * @throws NullPointerException if data is null.
+     * @throws ArrayIndexOutOfBoundsException if offset and length are
+     *      not within data.
+     */
+    // TODO: Overloads that don't use offset, length
+    public static Source createSource(@NonNull byte[] data, int offset,
+            int length) throws ArrayIndexOutOfBoundsException {
+        if (data == null) {
+            throw new NullPointerException("null byte[] in createSource!");
+        }
+        if (offset < 0 || length < 0 || offset >= data.length ||
+                offset + length > data.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "invalid offset/length!");
+        }
+        return new ByteArraySource(data, offset, length);
+    }
+
+    /**
+     * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+     *
+     * The returned {@link Source} effectively takes ownership of the
+     * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+     * this call.
+     *
+     * Decoding will start from {@link java.nio.ByteBuffer#position()}.
+     */
+    public static Source createSource(ByteBuffer buffer) {
+        return new ByteBufferSource(buffer);
+    }
+
+    /**
+     *  Return the width and height of a given sample size.
+     *
+     *  This takes an input that functions like
+     *  {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+     *  height that can be acheived by sampling the encoded image. Other widths
+     *  and heights may be supported, but will require an additional (internal)
+     *  scaling step. Such internal scaling is *not* supported with
+     *  {@link #requireUnpremultiplied}.
+     *
+     *  @param sampleSize Sampling rate of the encoded image.
+     *  @return Point {@link Point#x} and {@link Point#y} correspond to the
+     *      width and height after sampling.
+     */
+    public Point getSampledSize(int sampleSize) {
+        if (sampleSize <= 0) {
+            throw new IllegalArgumentException("sampleSize must be positive! "
+                    + "provided " + sampleSize);
+        }
+        if (mNativePtr == 0) {
+            throw new IllegalStateException("ImageDecoder is recycled!");
+        }
+
+        return nGetSampledSize(mNativePtr, sampleSize);
+    }
+
+    // Modifiers
+    /**
+     *  Resize the output to have the following size.
+     *
+     *  @param width must be greater than 0.
+     *  @param height must be greater than 0.
+     */
+    public void resize(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Dimensions must be positive! "
+                    + "provided (" + width + ", " + height + ")");
+        }
+
+        mDesiredWidth = width;
+        mDesiredHeight = height;
+    }
+
+    /**
+     *  Resize based on a sample size.
+     *
+     *  This has the same effect as passing the result of
+     *  {@link #getSampledSize} to {@link #resize(int, int)}.
+     *
+     *  @param sampleSize Sampling rate of the encoded image.
+     */
+    public void resize(int sampleSize) {
+        Point dimensions = this.getSampledSize(sampleSize);
+        this.resize(dimensions.x, dimensions.y);
+    }
+
+    // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+    /**
+     *  Use the default allocation for the pixel memory.
+     *
+     *  Will typically result in a {@link Bitmap.Config#HARDWARE}
+     *  allocation, but may be software for small images. In addition, this will
+     *  switch to software when HARDWARE is incompatible, e.g.
+     *  {@link #setMutable}, {@link #setAsAlphaMask}.
+     */
+    public static final int DEFAULT_ALLOCATOR = 0;
+
+    /**
+     *  Use a software allocation for the pixel memory.
+     *
+     *  Useful for drawing to a software {@link Canvas} or for
+     *  accessing the pixels on the final output.
+     */
+    public static final int SOFTWARE_ALLOCATOR = 1;
+
+    /**
+     *  Use shared memory for the pixel memory.
+     *
+     *  Useful for sharing across processes.
+     */
+    public static final int SHARED_MEMORY_ALLOCATOR = 2;
+
+    /**
+     *  Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+     *
+     *  This will throw an {@link java.lang.IllegalStateException} when combined
+     *  with incompatible options, like {@link #setMutable} or
+     *  {@link #setAsAlphaMask}.
+     */
+    public static final int HARDWARE_ALLOCATOR = 3;
+
+    /** @hide **/
+    @Retention(SOURCE)
+    @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR,
+              HARDWARE_ALLOCATOR })
+    public @interface Allocator {};
+
+    /**
+     *  Choose the backing for the pixel memory.
+     *
+     *  This is ignored for animated drawables.
+     *
+     *  TODO: Allow accessing the backing from the Bitmap.
+     *
+     *  @param allocator Type of allocator to use.
+     */
+    public void setAllocator(@Allocator int allocator) {
+        if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) {
+            throw new IllegalArgumentException("invalid allocator " + allocator);
+        }
+        mAllocator = allocator;
+    }
+
+    /**
+     *  Create a {@link Bitmap} with unpremultiplied pixels.
+     *
+     *  By default, ImageDecoder will create a {@link Bitmap} with
+     *  premultiplied pixels, which is required for drawing with the
+     *  {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+     *  this method will result in {@link #decodeBitmap} returning a
+     *  {@link Bitmap} with unpremultiplied pixels. See
+     *  {@link Bitmap#isPremultiplied}. Incompatible with
+     *  {@link #decodeDrawable}; attempting to decode an unpremultiplied
+     *  {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+     */
+    public void requireUnpremultiplied() {
+        mRequireUnpremultiplied = true;
+    }
+
+    /**
+     *  Modify the image after decoding and scaling.
+     *
+     *  This allows adding effects prior to returning a {@link Drawable} or
+     *  {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+     *  this is the only way to process the image after decoding.
+     *
+     *  If set on a nine-patch image, the nine-patch data is ignored.
+     *
+     *  For an animated image, the drawing commands drawn on the {@link Canvas}
+     *  will be recorded immediately and then applied to each frame.
+     */
+    public void setPostProcess(PostProcess p) {
+        mPostProcess = p;
+    }
+
+    /**
+     *  Set (replace) the {@link OnExceptionListener} on this object.
+     *
+     *  Will be called if there is an error in the input. Without one, a
+     *  partial {@link Bitmap} will be created.
+     */
+    public void setOnExceptionListener(OnExceptionListener l) {
+        mOnExceptionListener = l;
+    }
+
+    /**
+     *  Crop the output to {@code subset} of the (possibly) scaled image.
+     *
+     *  {@code subset} must be contained within the size set by {@link #resize}
+     *  or the bounds of the image if resize was not called. Otherwise an
+     *  {@link IllegalStateException} will be thrown.
+     *
+     *  NOT intended as a replacement for
+     *  {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+     *  but merely crops the output.
+     */
+    public void crop(Rect subset) {
+        mCropRect = subset;
+    }
+
+    /**
+     *  Create a mutable {@link Bitmap}.
+     *
+     *  By default, a {@link Bitmap} created will be immutable, but that can be
+     *  changed with this call.
+     *
+     *  Incompatible with {@link #HARDWARE_ALLOCATOR}, because
+     *  {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to
+     *  combine them will throw an {@link java.lang.IllegalStateException}.
+     *
+     *  Incompatible with {@link #decodeDrawable}, which would require
+     *  retrieving the Bitmap from the returned Drawable in order to modify.
+     *  Attempting to decode a mutable {@link Drawable} will throw an
+     *  {@link java.lang.IllegalStateException}
+     */
+    public void setMutable() {
+        mMutable = true;
+    }
+
+    /**
+     *  Potentially save RAM at the expense of quality.
+     *
+     *  This may result in a {@link Bitmap} with a denser {@link Bitmap.Config},
+     *  depending on the image. For example, for an opaque {@link Bitmap}, this
+     *  may result in a {@link Bitmap.Config} with no alpha information.
+     */
+    public void setPreferRamOverQuality() {
+        mPreferRamOverQuality = true;
+    }
+
+    /**
+     *  Potentially treat the output as an alpha mask.
+     *
+     *  If the image is encoded in a format with only one channel, treat that
+     *  channel as alpha. Otherwise this call has no effect.
+     *
+     *  Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them
+     *  will throw an {@link java.lang.IllegalStateException}.
+     */
+    public void setAsAlphaMask() {
+        mAsAlphaMask = true;
+    }
+
+    /**
+     *  Clean up resources.
+     *
+     *  ImageDecoder has a private constructor, and will always be recycled
+     *  by decodeDrawable or decodeBitmap which creates it, so there is no
+     *  need for a finalizer.
+     */
+    private void recycle() {
+        if (mNativePtr == 0) {
+            return;
+        }
+        nRecycle(mNativePtr);
+        mNativePtr = 0;
+    }
+
+    private void checkState() {
+        if (mNativePtr == 0) {
+            throw new IllegalStateException("Cannot reuse ImageDecoder.Source!");
+        }
+
+        checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
+
+        if (mAllocator == HARDWARE_ALLOCATOR) {
+            if (mMutable) {
+                throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
+            }
+            if (mAsAlphaMask) {
+                throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
+            }
+        }
+
+        if (mPostProcess != null && mRequireUnpremultiplied) {
+            throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
+        }
+    }
+
+    private static void checkSubset(int width, int height, Rect r) {
+        if (r == null) {
+            return;
+        }
+        if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
+            throw new IllegalStateException("Subset " + r + " not contained by "
+                    + "scaled image bounds: (" + width + " x " + height + ")");
+        }
+    }
+
+    /**
+     *  Create a {@link Drawable}.
+     */
+    public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) {
+        ImageDecoder decoder = src.createImageDecoder();
+        if (decoder == null) {
+            return null;
+        }
+
+        if (listener != null) {
+            ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+            listener.onHeaderDecoded(info, decoder);
+        }
+
+        decoder.checkState();
+
+        if (decoder.mRequireUnpremultiplied) {
+            // Though this could be supported (ignored) for opaque images, it
+            // seems better to always report this error.
+            throw new IllegalStateException("Cannot decode a Drawable with" +
+                                            " unpremultiplied pixels!");
+        }
+
+        if (decoder.mMutable) {
+            throw new IllegalStateException("Cannot decode a mutable Drawable!");
+        }
+
+        try {
+            Bitmap bm = nDecodeBitmap(decoder.mNativePtr,
+                                      decoder.mOnExceptionListener,
+                                      decoder.mPostProcess,
+                                      decoder.mDesiredWidth, decoder.mDesiredHeight,
+                                      decoder.mCropRect,
+                                      false,    // decoder.mMutable
+                                      decoder.mAllocator,
+                                      false,    // decoder.mRequireUnpremultiplied
+                                      decoder.mPreferRamOverQuality,
+                                      decoder.mAsAlphaMask
+                                      );
+            if (bm == null) {
+                return null;
+            }
+
+            Resources res = src.getResources();
+            if (res == null) {
+                bm.setDensity(Bitmap.DENSITY_NONE);
+            }
+
+            byte[] np = bm.getNinePatchChunk();
+            if (np != null && NinePatch.isNinePatchChunk(np)) {
+                Rect opticalInsets = new Rect();
+                bm.getOpticalInsets(opticalInsets);
+                Rect padding = new Rect();
+                nGetPadding(decoder.mNativePtr, padding);
+                return new NinePatchDrawable(res, bm, np, padding,
+                        opticalInsets, null);
+            }
+
+            // TODO: Handle animation.
+            return new BitmapDrawable(res, bm);
+        } finally {
+            decoder.recycle();
+            src.close();
+        }
+    }
+
+    /**
+     * Create a {@link Bitmap}.
+     */
+    public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) {
+        ImageDecoder decoder = src.createImageDecoder();
+        if (decoder == null) {
+            return null;
+        }
+
+        if (listener != null) {
+            ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+            listener.onHeaderDecoded(info, decoder);
+        }
+
+        decoder.checkState();
+
+        try {
+            return nDecodeBitmap(decoder.mNativePtr,
+                                 decoder.mOnExceptionListener,
+                                 decoder.mPostProcess,
+                                 decoder.mDesiredWidth, decoder.mDesiredHeight,
+                                 decoder.mCropRect,
+                                 decoder.mMutable,
+                                 decoder.mAllocator,
+                                 decoder.mRequireUnpremultiplied,
+                                 decoder.mPreferRamOverQuality,
+                                 decoder.mAsAlphaMask);
+        } finally {
+            decoder.recycle();
+            src.close();
+        }
+    }
+
+    private static native ImageDecoder nCreate(long asset);
+    private static native ImageDecoder nCreate(ByteBuffer buffer,
+                                               int position,
+                                               int limit);
+    private static native ImageDecoder nCreate(byte[] data, int offset,
+                                               int length);
+    private static native Bitmap nDecodeBitmap(long nativePtr,
+            OnExceptionListener listener,
+            PostProcess postProcess,
+            int width, int height,
+            Rect cropRect, boolean mutable,
+            int allocator, boolean requireUnpremul,
+            boolean preferRamOverQuality, boolean asAlphaMask);
+    private static native Point nGetSampledSize(long nativePtr,
+                                                int sampleSize);
+    private static native void nGetPadding(long nativePtr, Rect outRect);
+    private static native void nRecycle(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/PostProcess.java b/graphics/java/android/graphics/PostProcess.java
new file mode 100644
index 0000000..c5a31e8
--- /dev/null
+++ b/graphics/java/android/graphics/PostProcess.java
@@ -0,0 +1,91 @@
+/*
+ * 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.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+
+/**
+ *  Helper interface for adding custom processing to an image.
+ *
+ *  The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ *  of an animated image produced by {@link ImageDecoder}. This is called before
+ *  the requested object is returned.
+ *
+ *  This custom processing also applies to image types that are otherwise
+ *  immutable, such as {@link Bitmap.Config#HARDWARE}.
+ *
+ *  On an animated image, the callback will only be called once, but the drawing
+ *  commands will be applied to each frame, as if the {@code Canvas} had been
+ *  returned by {@link Picture#beginRecording}.
+ *
+ *  Supplied to ImageDecoder via {@link ImageDecoder#setPostProcess}.
+ *  @hide
+ */
+public interface PostProcess {
+    /**
+     *  Do any processing after (for example) decoding.
+     *
+     *  Drawing to the {@link Canvas} will behave as if the initial processing
+     *  (e.g. decoding) already exists in the Canvas. An implementation can draw
+     *  effects on top of this, or it can even draw behind it using
+     *  {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
+     *  to the corners to achieve rounded corners. That can be done with the
+     *  following code:
+     *
+     *  <code>
+     *      Path path = new Path();
+     *      path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+     *      path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+     *      Paint paint = new Paint();
+     *      paint.setAntiAlias(true);
+     *      paint.setColor(Color.TRANSPARENT);
+     *      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+     *      canvas.drawPath(path, paint);
+     *      return PixelFormat.TRANSLUCENT;
+     *  </code>
+     *
+     *
+     *  @param canvas The {@link Canvas} to draw to.
+     *  @param width Width of {@code canvas}. Anything drawn outside of this
+     *      will be ignored.
+     *  @param height Height of {@code canvas}. Anything drawn outside of this
+     *      will be ignored.
+     *  @return Opacity of the result after drawing.
+     *      {@link PixelFormat#UNKNOWN} means that the implementation did not
+     *      change whether the image has alpha. Return this unless you added
+     *      transparency (e.g. with the code above, in which case you should
+     *      return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
+     *      be opaque (e.g. by drawing everywhere with an opaque color and
+     *      {@code PorterDuff.Mode.DST_OVER}, in which case you should return
+     *      {@code PixelFormat.OPAQUE}).
+     *      {@link PixelFormat#TRANSLUCENT} means that the implementation added
+     *      transparency. This is safe to return even if the image already had
+     *      transparency. This is also safe to return if the result is opaque,
+     *      though it may draw more slowly.
+     *      {@link PixelFormat#OPAQUE} means that the implementation forced the
+     *      image to be opaque. This is safe to return even if the image was
+     *      already opaque.
+     *      {@link PixelFormat#TRANSPARENT} (or any other integer) is not
+     *      allowed, and will result in throwing an
+     *      {@link java.lang.IllegalArgumentException}.
+     */
+    @PixelFormat.Opacity
+    public int postProcess(@NonNull Canvas canvas, int width, int height);
+}
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 6830a74..57db20b 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -60,10 +60,12 @@
     /** Key prefix for user certificates. */
     public static final String USER_CERTIFICATE = "USRCERT_";
 
-    /** Key prefix for user private keys. */
+    /** Key prefix for user private and secret keys. */
     public static final String USER_PRIVATE_KEY = "USRPKEY_";
 
-    /** Key prefix for user secret keys. */
+    /** Key prefix for user secret keys.
+     *  @deprecated use {@code USER_PRIVATE_KEY} for this category instead.
+     */
     public static final String USER_SECRET_KEY = "USRSKEY_";
 
     /** Key prefix for VPN. */
@@ -235,8 +237,7 @@
          * Make sure every type is deleted. There can be all three types, so
          * don't use a conditional here.
          */
-        return deletePrivateKeyTypeForAlias(keystore, alias, uid)
-                & deleteSecretKeyTypeForAlias(keystore, alias, uid)
+        return deleteUserKeyTypeForAlias(keystore, alias, uid)
                 & deleteCertificateTypesForAlias(keystore, alias, uid);
     }
 
@@ -264,34 +265,27 @@
     }
 
     /**
-     * Delete private key for a particular {@code alias}.
+     * Delete user key for a particular {@code alias}.
      * Returns {@code true} if the entry no longer exists.
      */
-    static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) {
-        return deletePrivateKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
+    public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias) {
+        return deleteUserKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
     }
 
     /**
-     * Delete private key for a particular {@code alias}.
+     * Delete user key for a particular {@code alias}.
      * Returns {@code true} if the entry no longer exists.
      */
-    static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
-        return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid);
+    public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
+        return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid) ||
+                keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
     }
 
     /**
-     * Delete secret key for a particular {@code alias}.
+     * Delete legacy prefixed entry for a particular {@code alias}
      * Returns {@code true} if the entry no longer exists.
      */
-    public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
-        return deleteSecretKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
-    }
-
-    /**
-     * Delete secret key for a particular {@code alias}.
-     * Returns {@code true} if the entry no longer exists.
-     */
-    public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
+    public static boolean deleteLegacyKeyForAlias(KeyStore keystore, String alias, int uid) {
         return keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
     }
 }
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index eca52cc..7c7417d 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -35,6 +35,7 @@
 
     boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
     boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+    boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
 
     // APIs used by CertInstaller and DevicePolicyManager
     String installCaCertificate(in byte[] caCertificate);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 988e32c..f1d1e16 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -305,7 +305,7 @@
                 KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
                         mRng, (mKeySizeBits + 7) / 8);
         int flags = 0;
-        String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias();
+        String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
         KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
         boolean success = false;
         try {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index f36c00c..55e6519 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -196,7 +196,7 @@
     }
 
     @NonNull
-    public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
+    private static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
             @NonNull AndroidKeyStorePublicKey publicKey) {
         String keyAlgorithm = publicKey.getAlgorithm();
         if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
@@ -212,17 +212,25 @@
     }
 
     @NonNull
-    public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
-            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
+    private static KeyCharacteristics getKeyCharacteristics(@NonNull KeyStore keyStore,
+            @NonNull String alias, int uid)
             throws UnrecoverableKeyException {
         KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
         int errorCode = keyStore.getKeyCharacteristics(
-                privateKeyAlias, null, null, uid, keyCharacteristics);
+                alias, null, null, uid, keyCharacteristics);
         if (errorCode != KeyStore.NO_ERROR) {
             throw (UnrecoverableKeyException)
-                    new UnrecoverableKeyException("Failed to obtain information about private key")
-                    .initCause(KeyStore.getKeyStoreException(errorCode));
+                    new UnrecoverableKeyException("Failed to obtain information about key")
+                            .initCause(KeyStore.getKeyStoreException(errorCode));
         }
+        return keyCharacteristics;
+    }
+
+    @NonNull
+    private static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
+            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+            KeyCharacteristics keyCharacteristics)
+            throws UnrecoverableKeyException {
         ExportResult exportResult = keyStore.exportKey(
                 privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid);
         if (exportResult.resultCode != KeyStore.NO_ERROR) {
@@ -252,37 +260,56 @@
     }
 
     @NonNull
-    public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+    public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
             @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
             throws UnrecoverableKeyException {
+        return loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
+                getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+    }
+
+    @NonNull
+    private static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+            @NonNull KeyCharacteristics keyCharacteristics)
+            throws UnrecoverableKeyException {
         AndroidKeyStorePublicKey publicKey =
-                loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid);
+                loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
+                        keyCharacteristics);
         AndroidKeyStorePrivateKey privateKey =
                 AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
         return new KeyPair(publicKey, privateKey);
     }
 
     @NonNull
-    public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+    public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
             @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
             throws UnrecoverableKeyException {
-        KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid);
+        return loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
+                getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+    }
+
+    @NonNull
+    private static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+            @NonNull KeyCharacteristics keyCharacteristics)
+            throws UnrecoverableKeyException {
+        KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
+                keyCharacteristics);
         return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
     }
 
     @NonNull
-    public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
-            @NonNull KeyStore keyStore, @NonNull String secretKeyAlias, int uid)
+    public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
             throws UnrecoverableKeyException {
-        KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
-        int errorCode = keyStore.getKeyCharacteristics(
-                secretKeyAlias, null, null, uid, keyCharacteristics);
-        if (errorCode != KeyStore.NO_ERROR) {
-            throw (UnrecoverableKeyException)
-                    new UnrecoverableKeyException("Failed to obtain information about key")
-                            .initCause(KeyStore.getKeyStoreException(errorCode));
-        }
+        return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, privateKeyAlias, uid,
+                getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+    }
 
+    @NonNull
+    private static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
+            @NonNull String secretKeyAlias, int uid, @NonNull KeyCharacteristics keyCharacteristics)
+            throws UnrecoverableKeyException {
         Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
         if (keymasterAlgorithm == null) {
             throw new UnrecoverableKeyException("Key algorithm unknown");
@@ -310,6 +337,29 @@
         return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString);
     }
 
+    public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
+            @NonNull KeyStore keyStore, @NonNull String userKeyAlias, int uid)
+            throws UnrecoverableKeyException  {
+        KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid);
+
+        Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
+        if (keymasterAlgorithm == null) {
+            throw new UnrecoverableKeyException("Key algorithm unknown");
+        }
+
+        if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC ||
+                keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
+            return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid,
+                    keyCharacteristics);
+        } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
+                keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
+            return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, userKeyAlias, uid,
+                    keyCharacteristics);
+        } else {
+            throw new UnrecoverableKeyException("Key algorithm unknown");
+        }
+    }
+
     /**
      * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID.
      * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 0379863..fdb885db 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -64,7 +64,10 @@
         AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key;
         String keyAliasInKeystore = keystoreKey.getAlias();
         String entryAlias;
-        if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
+        if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
+            entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
+        } else if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)){
+            // key has legacy prefix
             entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
         } else {
             throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index bab4010..d73a9e2 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -89,18 +89,14 @@
     @Override
     public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
             UnrecoverableKeyException {
-        if (isPrivateKeyEntry(alias)) {
-            String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
-            return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
-                    mKeyStore, privateKeyAlias, mUid);
-        } else if (isSecretKeyEntry(alias)) {
-            String secretKeyAlias = Credentials.USER_SECRET_KEY + alias;
-            return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(
-                    mKeyStore, secretKeyAlias, mUid);
-        } else {
-            // Key not found
-            return null;
+        String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+        if (!mKeyStore.contains(userKeyAlias, mUid)) {
+            // try legacy prefix for backward compatibility
+            userKeyAlias = Credentials.USER_SECRET_KEY + alias;
+            if (!mKeyStore.contains(userKeyAlias, mUid)) return null;
         }
+        return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, userKeyAlias,
+                mUid);
     }
 
     @Override
@@ -540,7 +536,7 @@
             } else {
                 // Keep the stored private key around -- delete all other entry types
                 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
-                Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid);
+                Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
             }
 
             // Store the leaf certificate
@@ -565,7 +561,7 @@
                     Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
                 } else {
                     Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
-                    Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid);
+                    Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
                 }
             }
         }
@@ -588,12 +584,17 @@
             if (keyAliasInKeystore == null) {
                 throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
             }
-            if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
-                throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
-                        + keyAliasInKeystore);
+            String keyAliasPrefix = Credentials.USER_PRIVATE_KEY;
+            if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
+                // try legacy prefix
+                keyAliasPrefix = Credentials.USER_SECRET_KEY;
+                if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
+                    throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
+                            + keyAliasInKeystore);
+                }
             }
             String keyEntryAlias =
-                    keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
+                    keyAliasInKeystore.substring(keyAliasPrefix.length());
             if (!entryAlias.equals(keyEntryAlias)) {
                 throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
                         + " alias: " + entryAlias + " != " + keyEntryAlias);
@@ -728,7 +729,7 @@
         }
 
         Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid);
-        String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
+        String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias;
         int errorCode = mKeyStore.importKey(
                 keyAliasInKeystore,
                 args,
@@ -827,24 +828,10 @@
     }
 
     private boolean isKeyEntry(String alias) {
-        return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
+        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) ||
+                mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
     }
 
-    private boolean isPrivateKeyEntry(String alias) {
-        if (alias == null) {
-            throw new NullPointerException("alias == null");
-        }
-
-        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid);
-    }
-
-    private boolean isSecretKeyEntry(String alias) {
-        if (alias == null) {
-            throw new NullPointerException("alias == null");
-        }
-
-        return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
-    }
 
     private boolean isCertificateEntry(String alias) {
         if (alias == null) {
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3fe75cf..5b95c81 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6880,6 +6880,9 @@
         return UNKNOWN_ERROR;
     }
 
+    // The number of resources overlaid that were not explicitly marked overlayable.
+    size_t forcedOverlayCount = 0u;
+
     KeyedVector<uint8_t, IdmapTypeMap> map;
 
     // overlaid packages are assumed to contain only one package group
@@ -6919,6 +6922,7 @@
                 continue;
             }
 
+            uint32_t typeSpecFlags = 0u;
             const String16 overlayType(resName.type, resName.typeLen);
             const String16 overlayName(resName.name, resName.nameLen);
             uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
@@ -6926,14 +6930,23 @@
                                                               overlayType.string(),
                                                               overlayType.size(),
                                                               overlayPackage.string(),
-                                                              overlayPackage.size());
+                                                              overlayPackage.size(),
+                                                              &typeSpecFlags);
             if (overlayResID == 0) {
+                // No such target resource was found.
                 if (typeMap.entryMap.isEmpty()) {
                     typeMap.entryOffset++;
                 }
                 continue;
             }
 
+            // Now that we know this is being overlaid, check if it can be, and emit a warning if
+            // it can't.
+            if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) &
+                    ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) {
+                forcedOverlayCount++;
+            }
+
             if (typeMap.overlayTypeId == -1) {
                 typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
             }
@@ -7012,6 +7025,10 @@
         typeData += entryCount * 2;
     }
 
+    if (forcedOverlayCount > 0) {
+        ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount);
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 20d0178..8cf4de9 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1339,9 +1339,13 @@
     // Number of uint32_t entry configuration masks that follow.
     uint32_t entryCount;
 
-    enum {
+    enum : uint32_t {
         // Additional flag indicating an entry is public.
-        SPEC_PUBLIC = 0x40000000
+        SPEC_PUBLIC = 0x40000000u,
+
+        // Additional flag indicating an entry is overlayable at runtime.
+        // Added in Android-P.
+        SPEC_OVERLAYABLE = 0x80000000u,
     };
 };
 
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 0c17328..18ef75e 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk
index e45258c..767dff6 100644
--- a/libs/androidfw/tests/data/basic/basic_de_fr.apk
+++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
index 4ae1a7c..58953f5 100644
--- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
index a240d4c..103f656 100644
--- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
index fd3d9b2..61369d5 100644
--- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index d619800..5682ed4 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,11 +19,15 @@
 
 PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
 
-aapt package \
-    -M AndroidManifest.xml \
-    -S res \
-    -A assets \
+aapt2 compile --dir res -o compiled.flata
+aapt2 link \
     -I $PATH_TO_FRAMEWORK_RES \
-    --split hdpi --split xhdpi --split xxhdpi --split fr,de \
-    -F basic.apk \
-    -f
+    --manifest AndroidManifest.xml \
+    -A assets \
+    --split basic_hdpi-v4.apk:hdpi \
+    --split basic_xhdpi-v4.apk:xhdpi \
+    --split basic_xxhdpi-v4.apk:xxhdpi \
+    --split basic_de_fr.apk:de,fr \
+    -o basic.apk \
+    compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 638c983..6c47459 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -60,4 +60,9 @@
         <item>2</item>
         <item>3</item>
     </integer-array>
+
+    <overlayable>
+        <item type="string" name="test2" />
+        <item type="array" name="integerArray1" />
+    </overlayable>
 </resources>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
index 112f373..716b1bd 100755
--- a/libs/androidfw/tests/data/overlay/build
+++ b/libs/androidfw/tests/data/overlay/build
@@ -17,4 +17,6 @@
 
 set -e
 
-aapt package -M AndroidManifest.xml -S res -F overlay.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -o overlay.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index 40bf17c..33f9611 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index afdd339..f41956c 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -27,9 +27,11 @@
 
 #include <cutils/ashmem.h>
 #include <log/log.h>
+#include <sstream>
 
 #include "Properties.h"
 #include "utils/TimeUtils.h"
+#include "utils/Trace.h"
 
 namespace android {
 namespace uirenderer {
@@ -150,6 +152,19 @@
             (*mGlobalData)->reportJankType((JankType)i);
         }
     }
+
+    // Log daveys since they are weird and we don't know what they are (b/70339576)
+    if (totalDuration >= 700_ms) {
+        static int sDaveyCount = 0;
+        std::stringstream ss;
+        ss << "Davey! duration=" << ns2ms(totalDuration) << "ms; ";
+        for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
+            ss << FrameInfoNames[i] << "=" << frame[i] << ", ";
+        }
+        ALOGI("%s", ss.str().c_str());
+        // Just so we have something that counts up, the value is largely irrelevant
+        ATRACE_INT(ss.str().c_str(), ++sDaveyCount);
+    }
 }
 
 void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 8c0ca5c..0a9a74e 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -21,7 +21,6 @@
 #include "hwui/Typeface.h"
 #include "protos/hwui.pb.h"
 
-#include <../src/sysinfo.h>
 #include <benchmark/benchmark.h>
 #include <getopt.h>
 #include <pthread.h>
@@ -345,9 +344,6 @@
         name_field_width += 5;
 
         benchmark::BenchmarkReporter::Context context;
-        context.num_cpus = benchmark::NumCPUs();
-        context.mhz_per_cpu = benchmark::CyclesPerSecond() / 1000000.0f;
-        context.cpu_scaling_enabled = benchmark::CpuScalingEnabled();
         context.name_field_width = name_field_width;
         gBenchmarkReporter->ReportContext(context);
     }
diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk
index 8aa4b10..5f3e407 100644
--- a/libs/incident/Android.mk
+++ b/libs/incident/Android.mk
@@ -31,7 +31,6 @@
 
 LOCAL_SRC_FILES := \
         ../../core/java/android/os/IIncidentManager.aidl \
-        ../../core/java/android/os/IIncidentReportCompletedListener.aidl \
         ../../core/java/android/os/IIncidentReportStatusListener.aidl \
         src/IncidentReportArgs.cpp
 
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 7c60467..e3af655 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -551,18 +551,20 @@
     }
 
     // Animate spots that are fading out and being removed.
-    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+    for (size_t i = 0; i < mLocked.spots.size();) {
         Spot* spot = mLocked.spots.itemAt(i);
         if (spot->id == Spot::INVALID_ID) {
             spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
             if (spot->alpha <= 0) {
-                mLocked.spots.removeAt(i--);
+                mLocked.spots.removeAt(i);
                 releaseSpotLocked(spot);
+                continue;
             } else {
                 spot->sprite->setAlpha(spot->alpha);
                 keepAnimating = true;
             }
         }
+        ++i;
     }
     return keepAnimating;
 }
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 8f341a8..f9075cfd 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -71,6 +71,7 @@
     void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
 
     int getGnssYearOfHardware();
+    String getGnssHardwareModelName();
 
     int getGnssBatchSize(String packageName);
     boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d15ab33..4802b23 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -19,6 +19,7 @@
 import com.android.internal.location.ProviderProperties;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -225,6 +226,12 @@
     public static final String HIGH_POWER_REQUEST_CHANGE_ACTION =
         "android.location.HIGH_POWER_REQUEST_CHANGE";
 
+    /**
+     * The value returned by {@link LocationManager#getGnssHardwareModelName()} when the hardware
+     * does not support providing the actual value.
+     */
+    public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
+
     // Map from LocationListeners to their associated ListenerTransport objects
     private HashMap<LocationListener,ListenerTransport> mListeners =
         new HashMap<LocationListener,ListenerTransport>();
@@ -1969,11 +1976,10 @@
     }
 
     /**
-     * Returns the system information of the GPS hardware.
-     * May return 0 if GPS hardware is earlier than 2016.
-     * @hide
+     * Returns the model year of the GNSS hardware and software build.
+     *
+     * May return 0 if the model year is less than 2016.
      */
-    @TestApi
     public int getGnssYearOfHardware() {
         try {
             return mService.getGnssYearOfHardware();
@@ -1983,6 +1989,22 @@
     }
 
     /**
+     * Returns the Model Name (including Vendor and Hardware/Software Version) of the GNSS hardware
+     * driver.
+     *
+     * Will return {@link LocationManager#GNSS_HARDWARE_MODEL_NAME_UNKNOWN} when the GNSS hardware
+     * abstraction layer does not support providing this value.
+     */
+    @NonNull
+    public String getGnssHardwareModelName() {
+        try {
+            return mService.getGnssHardwareModelName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the batch size (in number of Location objects) that are supported by the batching
      * interface.
      *
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 19d467a..1a97b6b 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,9 +16,12 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.util.SparseIntArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.TreeSet;
 
 /**
@@ -120,6 +123,57 @@
      */
     public static final int TYPE_USB_HEADSET       = 22;
 
+    /** @hide */
+    @IntDef(flag = false, prefix = "TYPE", value = {
+            TYPE_BUILTIN_EARPIECE,
+            TYPE_BUILTIN_SPEAKER,
+            TYPE_WIRED_HEADSET,
+            TYPE_WIRED_HEADPHONES,
+            TYPE_BLUETOOTH_SCO,
+            TYPE_BLUETOOTH_A2DP,
+            TYPE_HDMI,
+            TYPE_DOCK,
+            TYPE_USB_ACCESSORY,
+            TYPE_USB_DEVICE,
+            TYPE_USB_HEADSET,
+            TYPE_TELEPHONY,
+            TYPE_LINE_ANALOG,
+            TYPE_HDMI_ARC,
+            TYPE_LINE_DIGITAL,
+            TYPE_FM,
+            TYPE_AUX_LINE,
+            TYPE_IP }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioDeviceTypeOut {}
+
+    /** @hide */
+    /*package*/ static boolean isValidAudioDeviceTypeOut(int type) {
+        switch (type) {
+            case TYPE_BUILTIN_EARPIECE:
+            case TYPE_BUILTIN_SPEAKER:
+            case TYPE_WIRED_HEADSET:
+            case TYPE_WIRED_HEADPHONES:
+            case TYPE_BLUETOOTH_SCO:
+            case TYPE_BLUETOOTH_A2DP:
+            case TYPE_HDMI:
+            case TYPE_DOCK:
+            case TYPE_USB_ACCESSORY:
+            case TYPE_USB_DEVICE:
+            case TYPE_USB_HEADSET:
+            case TYPE_TELEPHONY:
+            case TYPE_LINE_ANALOG:
+            case TYPE_HDMI_ARC:
+            case TYPE_LINE_DIGITAL:
+            case TYPE_FM:
+            case TYPE_AUX_LINE:
+            case TYPE_IP:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     private final AudioDevicePort mPort;
 
     AudioDeviceInfo(AudioDevicePort port) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 58976ca..f87c846 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -52,6 +53,8 @@
 import android.util.Slog;
 import android.view.KeyEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -911,13 +914,28 @@
 
     /**
      * Returns the minimum volume index for a particular stream.
-     *
+     * @param streamType The stream type whose minimum volume index is returned. Must be one of
+     *     {@link #STREAM_VOICE_CALL}, {@link #STREAM_SYSTEM},
+     *     {@link #STREAM_RING}, {@link #STREAM_MUSIC}, {@link #STREAM_ALARM},
+     *     {@link #STREAM_NOTIFICATION}, {@link #STREAM_DTMF} or {@link #STREAM_ACCESSIBILITY}.
+     * @return The minimum valid volume index for the stream.
+     * @see #getStreamVolume(int)
+     */
+    public int getStreamMinVolume(int streamType) {
+        if (!isPublicStreamType(streamType)) {
+            throw new IllegalArgumentException("Invalid stream type " + streamType);
+        }
+        return getStreamMinVolumeInt(streamType);
+    }
+
+    /**
+     * @hide
+     * Same as {@link #getStreamMinVolume(int)} but without the check on the public stream type.
      * @param streamType The stream type whose minimum volume index is returned.
      * @return The minimum valid volume index for the stream.
      * @see #getStreamVolume(int)
-     * @hide
      */
-    public int getStreamMinVolume(int streamType) {
+    public int getStreamMinVolumeInt(int streamType) {
         final IAudioService service = getService();
         try {
             return service.getStreamMinVolume(streamType);
@@ -943,6 +961,72 @@
         }
     }
 
+    // keep in sync with frameworks/av/services/audiopolicy/common/include/Volume.h
+    private static final float VOLUME_MIN_DB = -758.0f;
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "STREAM", value = {
+            STREAM_VOICE_CALL,
+            STREAM_SYSTEM,
+            STREAM_RING,
+            STREAM_MUSIC,
+            STREAM_ALARM,
+            STREAM_NOTIFICATION,
+            STREAM_DTMF,
+            STREAM_ACCESSIBILITY }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PublicStreamTypes {}
+
+    /**
+     * Returns the volume in dB (decibel) for the given stream type at the given volume index, on
+     * the given type of audio output device.
+     * @param streamType stream type for which the volume is queried.
+     * @param index the volume index for which the volume is queried. The index value must be
+     *     between the minimum and maximum index values for the given stream type (see
+     *     {@link #getStreamMinVolume(int)} and {@link #getStreamMaxVolume(int)}).
+     * @param deviceType the type of audio output device for which volume is queried.
+     * @return a volume expressed in dB.
+     *     A negative value indicates the audio signal is attenuated. A typical maximum value
+     *     at the maximum volume index is 0 dB (no attenuation nor amplification). Muting is
+     *     reflected by a value of {@link Float#NEGATIVE_INFINITY}.
+     */
+    public float getStreamVolumeDb(@PublicStreamTypes int streamType, int index,
+            @AudioDeviceInfo.AudioDeviceTypeOut int deviceType) {
+        if (!isPublicStreamType(streamType)) {
+            throw new IllegalArgumentException("Invalid stream type " + streamType);
+        }
+        if (index > getStreamMaxVolume(streamType) || index < getStreamMinVolume(streamType)) {
+            throw new IllegalArgumentException("Invalid stream volume index " + index);
+        }
+        if (!AudioDeviceInfo.isValidAudioDeviceTypeOut(deviceType)) {
+            throw new IllegalArgumentException("Invalid audio output device type " + deviceType);
+        }
+        final float gain = AudioSystem.getStreamVolumeDB(streamType, index,
+                AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType));
+        if (gain <= VOLUME_MIN_DB) {
+            return Float.NEGATIVE_INFINITY;
+        } else {
+            return gain;
+        }
+    }
+
+    private static boolean isPublicStreamType(int streamType) {
+        switch (streamType) {
+            case STREAM_VOICE_CALL:
+            case STREAM_SYSTEM:
+            case STREAM_RING:
+            case STREAM_MUSIC:
+            case STREAM_ALARM:
+            case STREAM_NOTIFICATION:
+            case STREAM_DTMF:
+            case STREAM_ACCESSIBILITY:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     /**
      * Get last audible volume before stream was muted.
      *
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index ba29d2d..a647dcc 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -30,6 +30,7 @@
 import android.os.BatteryManager;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.storage.StorageVolume;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Files;
@@ -40,21 +41,31 @@
 
 import dalvik.system.CloseGuard;
 
+import com.google.android.collect.Sets;
+
 import java.io.File;
-import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 /**
+ * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
+ * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
+ * operations are also reflected in MediaProvider if possible.
+ * operations
  * {@hide}
  */
 public class MtpDatabase implements AutoCloseable {
-    private static final String TAG = "MtpDatabase";
+    private static final String TAG = MtpDatabase.class.getSimpleName();
 
-    private final Context mUserContext;
     private final Context mContext;
-    private final String mPackageName;
     private final ContentProviderClient mMediaProvider;
     private final String mVolumeName;
     private final Uri mObjectsUri;
@@ -63,527 +74,36 @@
     private final AtomicBoolean mClosed = new AtomicBoolean();
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
-    // path to primary storage
-    private final String mMediaStoragePath;
-    // if not null, restrict all queries to these subdirectories
-    private final String[] mSubDirectories;
-    // where clause for restricting queries to files in mSubDirectories
-    private String mSubDirectoriesWhere;
-    // where arguments for restricting queries to files in mSubDirectories
-    private String[] mSubDirectoriesWhereArgs;
-
-    private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
+    private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
 
     // cached property groups for single properties
-    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
-            = new HashMap<Integer, MtpPropertyGroup>();
+    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
 
     // cached property groups for all properties for a given format
-    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
-            = new HashMap<Integer, MtpPropertyGroup>();
-
-    // true if the database has been modified in the current MTP session
-    private boolean mDatabaseModified;
+    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
 
     // SharedPreferences for writable MTP device properties
     private SharedPreferences mDeviceProperties;
-    private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
 
-    private static final String[] ID_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-    };
-    private static final String[] PATH_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-            Files.FileColumns.DATA, // 1
-    };
-    private static final String[] FORMAT_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-            Files.FileColumns.FORMAT, // 1
-    };
-    private static final String[] PATH_FORMAT_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-            Files.FileColumns.DATA, // 1
-            Files.FileColumns.FORMAT, // 2
-    };
-    private static final String[] OBJECT_INFO_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-            Files.FileColumns.STORAGE_ID, // 1
-            Files.FileColumns.FORMAT, // 2
-            Files.FileColumns.PARENT, // 3
-            Files.FileColumns.DATA, // 4
-            Files.FileColumns.DATE_ADDED, // 5
-            Files.FileColumns.DATE_MODIFIED, // 6
-    };
-    private static final String ID_WHERE = Files.FileColumns._ID + "=?";
-    private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
-
-    private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
-    private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
-    private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
-    private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
-                                            + Files.FileColumns.FORMAT + "=?";
-    private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
-                                            + Files.FileColumns.PARENT + "=?";
-    private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
-                                            + Files.FileColumns.PARENT + "=?";
-    private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
-                                            + Files.FileColumns.PARENT + "=?";
-
-    private MtpServer mServer;
-
-    // read from native code
+    // Cached device properties
     private int mBatteryLevel;
     private int mBatteryScale;
-
     private int mDeviceType;
 
+    private MtpServer mServer;
+    private MtpStorageManager mManager;
+
+    private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
+    private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
+    private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
+    private static final String NO_MEDIA = ".nomedia";
+
     static {
         System.loadLibrary("media_jni");
     }
 
-    private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
-          @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
-                int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
-                if (newLevel != mBatteryLevel) {
-                    mBatteryLevel = newLevel;
-                    if (mServer != null) {
-                        // send device property changed event
-                        mServer.sendDevicePropertyChanged(
-                                MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
-                    }
-                }
-            }
-        }
-    };
-
-    public MtpDatabase(Context context, Context userContext, String volumeName, String storagePath,
-            String[] subDirectories) {
-        native_setup();
-
-        mContext = context;
-        mUserContext = userContext;
-        mPackageName = context.getPackageName();
-        mMediaProvider = userContext.getContentResolver()
-                .acquireContentProviderClient(MediaStore.AUTHORITY);
-        mVolumeName = volumeName;
-        mMediaStoragePath = storagePath;
-        mObjectsUri = Files.getMtpObjectsUri(volumeName);
-        mMediaScanner = new MediaScanner(context, mVolumeName);
-
-        mSubDirectories = subDirectories;
-        if (subDirectories != null) {
-            // Compute "where" string for restricting queries to subdirectories
-            StringBuilder builder = new StringBuilder();
-            builder.append("(");
-            int count = subDirectories.length;
-            for (int i = 0; i < count; i++) {
-                builder.append(Files.FileColumns.DATA + "=? OR "
-                        + Files.FileColumns.DATA + " LIKE ?");
-                if (i != count - 1) {
-                    builder.append(" OR ");
-                }
-            }
-            builder.append(")");
-            mSubDirectoriesWhere = builder.toString();
-
-            // Compute "where" arguments for restricting queries to subdirectories
-            mSubDirectoriesWhereArgs = new String[count * 2];
-            for (int i = 0, j = 0; i < count; i++) {
-                String path = subDirectories[i];
-                mSubDirectoriesWhereArgs[j++] = path;
-                mSubDirectoriesWhereArgs[j++] = path + "/%";
-            }
-        }
-
-        initDeviceProperties(context);
-        mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
-
-        mCloseGuard.open("close");
-    }
-
-    public void setServer(MtpServer server) {
-        mServer = server;
-
-        // always unregister before registering
-        try {
-            mContext.unregisterReceiver(mBatteryReceiver);
-        } catch (IllegalArgumentException e) {
-            // wasn't previously registered, ignore
-        }
-
-        // register for battery notifications when we are connected
-        if (server != null) {
-            mContext.registerReceiver(mBatteryReceiver,
-                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-        }
-    }
-
-    @Override
-    public void close() {
-        mCloseGuard.close();
-        if (mClosed.compareAndSet(false, true)) {
-            mMediaScanner.close();
-            mMediaProvider.close();
-            native_finalize();
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-
-            close();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    public void addStorage(MtpStorage storage) {
-        mStorageMap.put(storage.getPath(), storage);
-    }
-
-    public void removeStorage(MtpStorage storage) {
-        mStorageMap.remove(storage.getPath());
-    }
-
-    private void initDeviceProperties(Context context) {
-        final String devicePropertiesName = "device-properties";
-        mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
-        File databaseFile = context.getDatabasePath(devicePropertiesName);
-
-        if (databaseFile.exists()) {
-            // for backward compatibility - read device properties from sqlite database
-            // and migrate them to shared prefs
-            SQLiteDatabase db = null;
-            Cursor c = null;
-            try {
-                db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
-                if (db != null) {
-                    c = db.query("properties", new String[] { "_id", "code", "value" },
-                            null, null, null, null, null);
-                    if (c != null) {
-                        SharedPreferences.Editor e = mDeviceProperties.edit();
-                        while (c.moveToNext()) {
-                            String name = c.getString(1);
-                            String value = c.getString(2);
-                            e.putString(name, value);
-                        }
-                        e.commit();
-                    }
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "failed to migrate device properties", e);
-            } finally {
-                if (c != null) c.close();
-                if (db != null) db.close();
-            }
-            context.deleteDatabase(devicePropertiesName);
-        }
-    }
-
-    // check to see if the path is contained in one of our storage subdirectories
-    // returns true if we have no special subdirectories
-    private boolean inStorageSubDirectory(String path) {
-        if (mSubDirectories == null) return true;
-        if (path == null) return false;
-
-        boolean allowed = false;
-        int pathLength = path.length();
-        for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
-            String subdir = mSubDirectories[i];
-            int subdirLength = subdir.length();
-            if (subdirLength < pathLength &&
-                    path.charAt(subdirLength) == '/' &&
-                    path.startsWith(subdir)) {
-                allowed = true;
-            }
-        }
-        return allowed;
-    }
-
-    // check to see if the path matches one of our storage subdirectories
-    // returns true if we have no special subdirectories
-    private boolean isStorageSubDirectory(String path) {
-    if (mSubDirectories == null) return false;
-        for (int i = 0; i < mSubDirectories.length; i++) {
-            if (path.equals(mSubDirectories[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // returns true if the path is in the storage root
-    private boolean inStorageRoot(String path) {
-        try {
-            File f = new File(path);
-            String canonical = f.getCanonicalPath();
-            for (String root: mStorageMap.keySet()) {
-                if (canonical.startsWith(root)) {
-                    return true;
-                }
-            }
-        } catch (IOException e) {
-            // ignore
-        }
-        return false;
-    }
-
-    private int beginSendObject(String path, int format, int parent,
-                         int storageId, long size, long modified) {
-        // if the path is outside of the storage root, do not allow access
-        if (!inStorageRoot(path)) {
-            Log.e(TAG, "attempt to put file outside of storage area: " + path);
-            return -1;
-        }
-        // if mSubDirectories is not null, do not allow copying files to any other locations
-        if (!inStorageSubDirectory(path)) return -1;
-
-        // make sure the object does not exist
-        if (path != null) {
-            Cursor c = null;
-            try {
-                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
-                        new String[] { path }, null, null);
-                if (c != null && c.getCount() > 0) {
-                    Log.w(TAG, "file already exists in beginSendObject: " + path);
-                    return -1;
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in beginSendObject", e);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-        }
-
-        mDatabaseModified = true;
-        ContentValues values = new ContentValues();
-        values.put(Files.FileColumns.DATA, path);
-        values.put(Files.FileColumns.FORMAT, format);
-        values.put(Files.FileColumns.PARENT, parent);
-        values.put(Files.FileColumns.STORAGE_ID, storageId);
-        values.put(Files.FileColumns.SIZE, size);
-        values.put(Files.FileColumns.DATE_MODIFIED, modified);
-
-        try {
-            Uri uri = mMediaProvider.insert(mObjectsUri, values);
-            if (uri != null) {
-                return Integer.parseInt(uri.getPathSegments().get(2));
-            } else {
-                return -1;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in beginSendObject", e);
-            return -1;
-        }
-    }
-
-    private void endSendObject(String path, int handle, int format, boolean succeeded) {
-        if (succeeded) {
-            // handle abstract playlists separately
-            // they do not exist in the file system so don't use the media scanner here
-            if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
-                // extract name from path
-                String name = path;
-                int lastSlash = name.lastIndexOf('/');
-                if (lastSlash >= 0) {
-                    name = name.substring(lastSlash + 1);
-                }
-                // strip trailing ".pla" from the name
-                if (name.endsWith(".pla")) {
-                    name = name.substring(0, name.length() - 4);
-                }
-
-                ContentValues values = new ContentValues(1);
-                values.put(Audio.Playlists.DATA, path);
-                values.put(Audio.Playlists.NAME, name);
-                values.put(Files.FileColumns.FORMAT, format);
-                values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
-                values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
-                try {
-                    Uri uri = mMediaProvider.insert(
-                            Audio.Playlists.EXTERNAL_CONTENT_URI, values);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "RemoteException in endSendObject", e);
-                }
-            } else {
-                mMediaScanner.scanMtpFile(path, handle, format);
-            }
-        } else {
-            deleteFile(handle);
-        }
-    }
-
-    private void doScanDirectory(String path) {
-        String[] scanPath;
-        scanPath = new String[] { path };
-        mMediaScanner.scanDirectories(scanPath);
-    }
-
-    private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
-        String where;
-        String[] whereArgs;
-
-        if (storageID == 0xFFFFFFFF) {
-            // query all stores
-            if (format == 0) {
-                // query all formats
-                if (parent == 0) {
-                    // query all objects
-                    where = null;
-                    whereArgs = null;
-                } else {
-                    if (parent == 0xFFFFFFFF) {
-                        // all objects in root of store
-                        parent = 0;
-                    }
-                    where = PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(parent) };
-                }
-            } else {
-                // query specific format
-                if (parent == 0) {
-                    // query all objects
-                    where = FORMAT_WHERE;
-                    whereArgs = new String[] { Integer.toString(format) };
-                } else {
-                    if (parent == 0xFFFFFFFF) {
-                        // all objects in root of store
-                        parent = 0;
-                    }
-                    where = FORMAT_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(format),
-                                               Integer.toString(parent) };
-                }
-            }
-        } else {
-            // query specific store
-            if (format == 0) {
-                // query all formats
-                if (parent == 0) {
-                    // query all objects
-                    where = STORAGE_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID) };
-                } else {
-                    if (parent == 0xFFFFFFFF) {
-                        // all objects in root of store
-                        parent = 0;
-                        where = STORAGE_PARENT_WHERE;
-                        whereArgs = new String[]{Integer.toString(storageID),
-                                Integer.toString(parent)};
-                    }  else {
-                        // If a parent is specified, the storage is redundant
-                        where = PARENT_WHERE;
-                        whereArgs = new String[]{Integer.toString(parent)};
-                    }
-                }
-            } else {
-                // query specific format
-                if (parent == 0) {
-                    // query all objects
-                    where = STORAGE_FORMAT_WHERE;
-                    whereArgs = new String[] {  Integer.toString(storageID),
-                                                Integer.toString(format) };
-                } else {
-                    if (parent == 0xFFFFFFFF) {
-                        // all objects in root of store
-                        parent = 0;
-                        where = STORAGE_FORMAT_PARENT_WHERE;
-                        whereArgs = new String[]{Integer.toString(storageID),
-                                Integer.toString(format),
-                                Integer.toString(parent)};
-                    } else {
-                        // If a parent is specified, the storage is redundant
-                        where = FORMAT_PARENT_WHERE;
-                        whereArgs = new String[]{Integer.toString(format),
-                                Integer.toString(parent)};
-                    }
-                }
-            }
-        }
-
-        // if we are restricting queries to mSubDirectories, we need to add the restriction
-        // onto our "where" arguments
-        if (mSubDirectoriesWhere != null) {
-            if (where == null) {
-                where = mSubDirectoriesWhere;
-                whereArgs = mSubDirectoriesWhereArgs;
-            } else {
-                where = where + " AND " + mSubDirectoriesWhere;
-
-                // create new array to hold whereArgs and mSubDirectoriesWhereArgs
-                String[] newWhereArgs =
-                        new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
-                int i, j;
-                for (i = 0; i < whereArgs.length; i++) {
-                    newWhereArgs[i] = whereArgs[i];
-                }
-                for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
-                    newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
-                }
-                whereArgs = newWhereArgs;
-            }
-        }
-
-        return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
-                whereArgs, null, null);
-    }
-
-    private int[] getObjectList(int storageID, int format, int parent) {
-        Cursor c = null;
-        try {
-            c = createObjectQuery(storageID, format, parent);
-            if (c == null) {
-                return null;
-            }
-            int count = c.getCount();
-            if (count > 0) {
-                int[] result = new int[count];
-                for (int i = 0; i < count; i++) {
-                    c.moveToNext();
-                    result[i] = c.getInt(0);
-                }
-                return result;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectList", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return null;
-    }
-
-    private int getNumObjects(int storageID, int format, int parent) {
-        Cursor c = null;
-        try {
-            c = createObjectQuery(storageID, format, parent);
-            if (c != null) {
-                return c.getCount();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getNumObjects", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return -1;
-    }
-
-    private int[] getSupportedPlaybackFormats() {
-        return new int[] {
-            // allow transfering arbitrary files
+    private static final int[] PLAYBACK_FORMATS = {
+            // allow transferring arbitrary files
             MtpConstants.FORMAT_UNDEFINED,
 
             MtpConstants.FORMAT_ASSOCIATION,
@@ -613,45 +133,23 @@
             MtpConstants.FORMAT_FLAC,
             MtpConstants.FORMAT_DNG,
             MtpConstants.FORMAT_HEIF,
-        };
-    }
+    };
 
-    private int[] getSupportedCaptureFormats() {
-        // no capture formats yet
-        return null;
-    }
-
-    static final int[] FILE_PROPERTIES = {
-            // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
-            // and IMAGE_PROPERTIES below
+    private static final int[] FILE_PROPERTIES = {
             MtpConstants.PROPERTY_STORAGE_ID,
             MtpConstants.PROPERTY_OBJECT_FORMAT,
             MtpConstants.PROPERTY_PROTECTION_STATUS,
             MtpConstants.PROPERTY_OBJECT_SIZE,
             MtpConstants.PROPERTY_OBJECT_FILE_NAME,
             MtpConstants.PROPERTY_DATE_MODIFIED,
-            MtpConstants.PROPERTY_PARENT_OBJECT,
             MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_PARENT_OBJECT,
             MtpConstants.PROPERTY_NAME,
             MtpConstants.PROPERTY_DISPLAY_NAME,
             MtpConstants.PROPERTY_DATE_ADDED,
     };
 
-    static final int[] AUDIO_PROPERTIES = {
-            // NOTE must match FILE_PROPERTIES above
-            MtpConstants.PROPERTY_STORAGE_ID,
-            MtpConstants.PROPERTY_OBJECT_FORMAT,
-            MtpConstants.PROPERTY_PROTECTION_STATUS,
-            MtpConstants.PROPERTY_OBJECT_SIZE,
-            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
-            MtpConstants.PROPERTY_DATE_MODIFIED,
-            MtpConstants.PROPERTY_PARENT_OBJECT,
-            MtpConstants.PROPERTY_PERSISTENT_UID,
-            MtpConstants.PROPERTY_NAME,
-            MtpConstants.PROPERTY_DISPLAY_NAME,
-            MtpConstants.PROPERTY_DATE_ADDED,
-
-            // audio specific properties
+    private static final int[] AUDIO_PROPERTIES = {
             MtpConstants.PROPERTY_ARTIST,
             MtpConstants.PROPERTY_ALBUM_NAME,
             MtpConstants.PROPERTY_ALBUM_ARTIST,
@@ -667,45 +165,25 @@
             MtpConstants.PROPERTY_SAMPLE_RATE,
     };
 
-    static final int[] VIDEO_PROPERTIES = {
-            // NOTE must match FILE_PROPERTIES above
-            MtpConstants.PROPERTY_STORAGE_ID,
-            MtpConstants.PROPERTY_OBJECT_FORMAT,
-            MtpConstants.PROPERTY_PROTECTION_STATUS,
-            MtpConstants.PROPERTY_OBJECT_SIZE,
-            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
-            MtpConstants.PROPERTY_DATE_MODIFIED,
-            MtpConstants.PROPERTY_PARENT_OBJECT,
-            MtpConstants.PROPERTY_PERSISTENT_UID,
-            MtpConstants.PROPERTY_NAME,
-            MtpConstants.PROPERTY_DISPLAY_NAME,
-            MtpConstants.PROPERTY_DATE_ADDED,
-
-            // video specific properties
+    private static final int[] VIDEO_PROPERTIES = {
             MtpConstants.PROPERTY_ARTIST,
             MtpConstants.PROPERTY_ALBUM_NAME,
             MtpConstants.PROPERTY_DURATION,
             MtpConstants.PROPERTY_DESCRIPTION,
     };
 
-    static final int[] IMAGE_PROPERTIES = {
-            // NOTE must match FILE_PROPERTIES above
-            MtpConstants.PROPERTY_STORAGE_ID,
-            MtpConstants.PROPERTY_OBJECT_FORMAT,
-            MtpConstants.PROPERTY_PROTECTION_STATUS,
-            MtpConstants.PROPERTY_OBJECT_SIZE,
-            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
-            MtpConstants.PROPERTY_DATE_MODIFIED,
-            MtpConstants.PROPERTY_PARENT_OBJECT,
-            MtpConstants.PROPERTY_PERSISTENT_UID,
-            MtpConstants.PROPERTY_NAME,
-            MtpConstants.PROPERTY_DISPLAY_NAME,
-            MtpConstants.PROPERTY_DATE_ADDED,
-
-            // image specific properties
+    private static final int[] IMAGE_PROPERTIES = {
             MtpConstants.PROPERTY_DESCRIPTION,
     };
 
+    private static final int[] DEVICE_PROPERTIES = {
+            MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
+            MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
+            MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
+            MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
+            MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
+    };
+
     private int[] getSupportedObjectProperties(int format) {
         switch (format) {
             case MtpConstants.FORMAT_MP3:
@@ -713,183 +191,541 @@
             case MtpConstants.FORMAT_WMA:
             case MtpConstants.FORMAT_OGG:
             case MtpConstants.FORMAT_AAC:
-                return AUDIO_PROPERTIES;
+                return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+                        Arrays.stream(AUDIO_PROPERTIES)).toArray();
             case MtpConstants.FORMAT_MPEG:
             case MtpConstants.FORMAT_3GP_CONTAINER:
             case MtpConstants.FORMAT_WMV:
-                return VIDEO_PROPERTIES;
+                return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+                        Arrays.stream(VIDEO_PROPERTIES)).toArray();
             case MtpConstants.FORMAT_EXIF_JPEG:
             case MtpConstants.FORMAT_GIF:
             case MtpConstants.FORMAT_PNG:
             case MtpConstants.FORMAT_BMP:
             case MtpConstants.FORMAT_DNG:
             case MtpConstants.FORMAT_HEIF:
-                return IMAGE_PROPERTIES;
+                return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+                        Arrays.stream(IMAGE_PROPERTIES)).toArray();
             default:
                 return FILE_PROPERTIES;
         }
     }
 
     private int[] getSupportedDeviceProperties() {
-        return new int[] {
-            MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
-            MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
-            MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
-            MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
-            MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
-        };
+        return DEVICE_PROPERTIES;
+    }
+
+    private int[] getSupportedPlaybackFormats() {
+        return PLAYBACK_FORMATS;
+    }
+
+    private int[] getSupportedCaptureFormats() {
+        // no capture formats yet
+        return null;
+    }
+
+    private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+                int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+                if (newLevel != mBatteryLevel) {
+                    mBatteryLevel = newLevel;
+                    if (mServer != null) {
+                        // send device property changed event
+                        mServer.sendDevicePropertyChanged(
+                                MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
+                    }
+                }
+            }
+        }
+    };
+
+    public MtpDatabase(Context context, Context userContext, String volumeName,
+            String[] subDirectories) {
+        native_setup();
+        mContext = context;
+        mMediaProvider = userContext.getContentResolver()
+                .acquireContentProviderClient(MediaStore.AUTHORITY);
+        mVolumeName = volumeName;
+        mObjectsUri = Files.getMtpObjectsUri(volumeName);
+        mMediaScanner = new MediaScanner(context, mVolumeName);
+        mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
+            @Override
+            public void sendObjectAdded(int id) {
+                if (MtpDatabase.this.mServer != null)
+                    MtpDatabase.this.mServer.sendObjectAdded(id);
+            }
+
+            @Override
+            public void sendObjectRemoved(int id) {
+                if (MtpDatabase.this.mServer != null)
+                    MtpDatabase.this.mServer.sendObjectRemoved(id);
+            }
+        }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
+
+        initDeviceProperties(context);
+        mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
+        mCloseGuard.open("close");
+    }
+
+    public void setServer(MtpServer server) {
+        mServer = server;
+        // always unregister before registering
+        try {
+            mContext.unregisterReceiver(mBatteryReceiver);
+        } catch (IllegalArgumentException e) {
+            // wasn't previously registered, ignore
+        }
+        // register for battery notifications when we are connected
+        if (server != null) {
+            mContext.registerReceiver(mBatteryReceiver,
+                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        }
+    }
+
+    @Override
+    public void close() {
+        mManager.close();
+        mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
+            mMediaScanner.close();
+            mMediaProvider.close();
+            native_finalize();
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public void addStorage(StorageVolume storage) {
+        MtpStorage mtpStorage = mManager.addMtpStorage(storage);
+        mStorageMap.put(storage.getPath(), mtpStorage);
+        mServer.addStorage(mtpStorage);
+    }
+
+    public void removeStorage(StorageVolume storage) {
+        MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
+        if (mtpStorage == null) {
+            return;
+        }
+        mServer.removeStorage(mtpStorage);
+        mManager.removeMtpStorage(mtpStorage);
+        mStorageMap.remove(storage.getPath());
+    }
+
+    private void initDeviceProperties(Context context) {
+        final String devicePropertiesName = "device-properties";
+        mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
+                Context.MODE_PRIVATE);
+        File databaseFile = context.getDatabasePath(devicePropertiesName);
+
+        if (databaseFile.exists()) {
+            // for backward compatibility - read device properties from sqlite database
+            // and migrate them to shared prefs
+            SQLiteDatabase db = null;
+            Cursor c = null;
+            try {
+                db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
+                if (db != null) {
+                    c = db.query("properties", new String[]{"_id", "code", "value"},
+                            null, null, null, null, null);
+                    if (c != null) {
+                        SharedPreferences.Editor e = mDeviceProperties.edit();
+                        while (c.moveToNext()) {
+                            String name = c.getString(1);
+                            String value = c.getString(2);
+                            e.putString(name, value);
+                        }
+                        e.commit();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "failed to migrate device properties", e);
+            } finally {
+                if (c != null) c.close();
+                if (db != null) db.close();
+            }
+            context.deleteDatabase(devicePropertiesName);
+        }
+    }
+
+    private int beginSendObject(String path, int format, int parent, int storageId) {
+        MtpStorageManager.MtpObject parentObj =
+                parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
+        if (parentObj == null) {
+            return -1;
+        }
+
+        Path objPath = Paths.get(path);
+        return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
+    }
+
+    private void endSendObject(int handle, boolean succeeded) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null || !mManager.endSendObject(obj, succeeded)) {
+            Log.e(TAG, "Failed to successfully end send object");
+            return;
+        }
+        // Add the new file to MediaProvider
+        if (succeeded) {
+            String path = obj.getPath().toString();
+            int format = obj.getFormat();
+            // Get parent info from MediaProvider, since the id is different from MTP's
+            ContentValues values = new ContentValues();
+            values.put(Files.FileColumns.DATA, path);
+            values.put(Files.FileColumns.FORMAT, format);
+            values.put(Files.FileColumns.SIZE, obj.getSize());
+            values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
+            try {
+                if (obj.getParent().isRoot()) {
+                    values.put(Files.FileColumns.PARENT, 0);
+                } else {
+                    int parentId = findInMedia(obj.getParent().getPath());
+                    if (parentId != -1) {
+                        values.put(Files.FileColumns.PARENT, parentId);
+                    } else {
+                        // The parent isn't in MediaProvider. Don't add the new file.
+                        return;
+                    }
+                }
+
+                Uri uri = mMediaProvider.insert(mObjectsUri, values);
+                if (uri != null) {
+                    rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in beginSendObject", e);
+            }
+        }
+    }
+
+    private void rescanFile(String path, int handle, int format) {
+        // handle abstract playlists separately
+        // they do not exist in the file system so don't use the media scanner here
+        if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
+            // extract name from path
+            String name = path;
+            int lastSlash = name.lastIndexOf('/');
+            if (lastSlash >= 0) {
+                name = name.substring(lastSlash + 1);
+            }
+            // strip trailing ".pla" from the name
+            if (name.endsWith(".pla")) {
+                name = name.substring(0, name.length() - 4);
+            }
+
+            ContentValues values = new ContentValues(1);
+            values.put(Audio.Playlists.DATA, path);
+            values.put(Audio.Playlists.NAME, name);
+            values.put(Files.FileColumns.FORMAT, format);
+            values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
+            values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
+            try {
+                mMediaProvider.insert(
+                        Audio.Playlists.EXTERNAL_CONTENT_URI, values);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in endSendObject", e);
+            }
+        } else {
+            mMediaScanner.scanMtpFile(path, handle, format);
+        }
+    }
+
+    private int[] getObjectList(int storageID, int format, int parent) {
+        Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
+                format, storageID);
+        if (objectStream == null) {
+            return null;
+        }
+        return objectStream.mapToInt(MtpStorageManager.MtpObject::getId).toArray();
+    }
+
+    private int getNumObjects(int storageID, int format, int parent) {
+        Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
+                format, storageID);
+        if (objectStream == null) {
+            return -1;
+        }
+        return (int) objectStream.count();
     }
 
     private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
-                        int groupCode, int depth) {
+            int groupCode, int depth) {
         // FIXME - implement group support
-        if (groupCode != 0) {
-            return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
+        if (property == 0) {
+            if (groupCode == 0) {
+                return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
+            }
+            return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
+        }
+        if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
+            // request all objects starting at root
+            handle = 0xFFFFFFFF;
+            depth = 0;
+        }
+        if (!(depth == 0 || depth == 1)) {
+            // we only support depth 0 and 1
+            // depth 0: single object, depth 1: immediate children
+            return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
+        }
+        Stream<MtpStorageManager.MtpObject> objectStream = Stream.of();
+        if (handle == 0xFFFFFFFF) {
+            // All objects are requested
+            objectStream = mManager.getObjects(0, format, 0xFFFFFFFF);
+            if (objectStream == null) {
+                return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+            }
+        } else if (handle != 0) {
+            // Add the requested object if format matches
+            MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+            if (obj == null) {
+                return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+            }
+            if (obj.getFormat() == format || format == 0) {
+                objectStream = Stream.of(obj);
+            }
+        }
+        if (handle == 0 || depth == 1) {
+            if (handle == 0) {
+                handle = 0xFFFFFFFF;
+            }
+            // Get the direct children of root or this object.
+            Stream<MtpStorageManager.MtpObject> childStream = mManager.getObjects(handle, format,
+                    0xFFFFFFFF);
+            if (childStream == null) {
+                return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+            }
+            objectStream = Stream.concat(objectStream, childStream);
         }
 
+        MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
         MtpPropertyGroup propertyGroup;
-        if (property == 0xffffffff) {
-            if (format == 0 && handle != 0 && handle != 0xffffffff) {
-                // return properties based on the object's format
-                format = getObjectFormat(handle);
+        Iterator<MtpStorageManager.MtpObject> iter = objectStream.iterator();
+        while (iter.hasNext()) {
+            MtpStorageManager.MtpObject obj = iter.next();
+            if (property == 0xffffffff) {
+                // Get all properties supported by this object
+                propertyGroup = mPropertyGroupsByFormat.get(obj.getFormat());
+                if (propertyGroup == null) {
+                    int[] propertyList = getSupportedObjectProperties(format);
+                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
+                            propertyList);
+                    mPropertyGroupsByFormat.put(format, propertyGroup);
+                }
+            } else {
+                // Get this property value
+                final int[] propertyList = new int[]{property};
+                propertyGroup = mPropertyGroupsByProperty.get(property);
+                if (propertyGroup == null) {
+                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
+                            propertyList);
+                    mPropertyGroupsByProperty.put(property, propertyGroup);
+                }
             }
-            propertyGroup = mPropertyGroupsByFormat.get(format);
-            if (propertyGroup == null) {
-                int[] propertyList = getSupportedObjectProperties(format);
-                propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
-                        mVolumeName, propertyList);
-                mPropertyGroupsByFormat.put(format, propertyGroup);
-            }
-        } else {
-            propertyGroup = mPropertyGroupsByProperty.get(property);
-            if (propertyGroup == null) {
-                final int[] propertyList = new int[] { property };
-                propertyGroup = new MtpPropertyGroup(
-                        this, mMediaProvider, mVolumeName, propertyList);
-                mPropertyGroupsByProperty.put(property, propertyGroup);
+            int err = propertyGroup.getPropertyList(obj, ret);
+            if (err != MtpConstants.RESPONSE_OK) {
+                return new MtpPropertyList(err);
             }
         }
-
-        return propertyGroup.getPropertyList(handle, format, depth);
+        return ret;
     }
 
     private int renameFile(int handle, String newName) {
-        Cursor c = null;
-
-        // first compute current path
-        String path = null;
-        String[] whereArgs = new String[] {  Integer.toString(handle) };
-        try {
-            c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
-                    whereArgs, null, null);
-            if (c != null && c.moveToNext()) {
-                path = c.getString(1);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectFilePath", e);
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        if (path == null) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
             return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
         }
-
-        // do not allow renaming any of the special subdirectories
-        if (isStorageSubDirectory(path)) {
-            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
-        }
+        Path oldPath = obj.getPath();
 
         // now rename the file.  make sure this succeeds before updating database
-        File oldFile = new File(path);
-        int lastSlash = path.lastIndexOf('/');
-        if (lastSlash <= 1) {
+        if (!mManager.beginRenameObject(obj, newName))
             return MtpConstants.RESPONSE_GENERAL_ERROR;
+        Path newPath = obj.getPath();
+        boolean success = oldPath.toFile().renameTo(newPath.toFile());
+        if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
+            Log.e(TAG, "Failed to end rename object");
         }
-        String newPath = path.substring(0, lastSlash + 1) + newName;
-        File newFile = new File(newPath);
-        boolean success = oldFile.renameTo(newFile);
         if (!success) {
-            Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
             return MtpConstants.RESPONSE_GENERAL_ERROR;
         }
 
-        // finally update database
+        // finally update MediaProvider
         ContentValues values = new ContentValues();
-        values.put(Files.FileColumns.DATA, newPath);
-        int updated = 0;
+        values.put(Files.FileColumns.DATA, newPath.toString());
+        String[] whereArgs = new String[]{oldPath.toString()};
         try {
             // note - we are relying on a special case in MediaProvider.update() to update
             // the paths for all children in the case where this is a directory.
-            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+            mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
         }
-        if (updated == 0) {
-            Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
-            // this shouldn't happen, but if it does we need to rename the file to its original name
-            newFile.renameTo(oldFile);
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
-        }
 
         // check if nomedia status changed
-        if (newFile.isDirectory()) {
+        if (obj.isDir()) {
             // for directories, check if renamed from something hidden to something non-hidden
-            if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
+            if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
                 // directory was unhidden
                 try {
-                    mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
+                    mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
                 } catch (RemoteException e) {
                     Log.e(TAG, "failed to unhide/rescan for " + newPath);
                 }
             }
         } else {
             // for files, check if renamed from .nomedia to something else
-            if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
-                    && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
+            if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
+                    && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
                 try {
-                    mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
+                    mMediaProvider.call(MediaStore.UNHIDE_CALL,
+                            oldPath.getParent().toString(), null);
                 } catch (RemoteException e) {
                     Log.e(TAG, "failed to unhide/rescan for " + newPath);
                 }
             }
         }
-
         return MtpConstants.RESPONSE_OK;
     }
 
-    private int moveObject(int handle, int newParent, int newStorage, String newPath) {
-        String[] whereArgs = new String[] {  Integer.toString(handle) };
+    private int beginMoveObject(int handle, int newParent, int newStorage) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        MtpStorageManager.MtpObject parent = newParent == 0 ?
+                mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+        if (obj == null || parent == null)
+            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
 
-        // do not allow renaming any of the special subdirectories
-        if (isStorageSubDirectory(newPath)) {
-            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+        boolean allowed = mManager.beginMoveObject(obj, parent);
+        return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
+    }
+
+    private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
+            int objId, boolean success) {
+        MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
+                mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
+        MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
+                mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+        MtpStorageManager.MtpObject obj = mManager.getObject(objId);
+        String name = obj.getName();
+        if (newParentObj == null || oldParentObj == null
+                ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
+            Log.e(TAG, "Failed to end move object");
+            return;
         }
 
-        // update database
+        obj = mManager.getObject(objId);
+        if (!success || obj == null)
+            return;
+        // Get parent info from MediaProvider, since the id is different from MTP's
         ContentValues values = new ContentValues();
-        values.put(Files.FileColumns.DATA, newPath);
-        values.put(Files.FileColumns.PARENT, newParent);
-        values.put(Files.FileColumns.STORAGE_ID, newStorage);
-        int updated = 0;
+        Path path = newParentObj.getPath().resolve(name);
+        Path oldPath = oldParentObj.getPath().resolve(name);
+        values.put(Files.FileColumns.DATA, path.toString());
+        if (obj.getParent().isRoot()) {
+            values.put(Files.FileColumns.PARENT, 0);
+        } else {
+            int parentId = findInMedia(path.getParent());
+            if (parentId != -1) {
+                values.put(Files.FileColumns.PARENT, parentId);
+            } else {
+                // The new parent isn't in MediaProvider, so delete the object instead
+                deleteFromMedia(oldPath, obj.isDir());
+                return;
+            }
+        }
+        // update MediaProvider
+        Cursor c = null;
+        String[] whereArgs = new String[]{oldPath.toString()};
         try {
-            // note - we are relying on a special case in MediaProvider.update() to update
-            // the paths for all children in the case where this is a directory.
-            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+            int parentId = -1;
+            if (!oldParentObj.isRoot()) {
+                parentId = findInMedia(oldPath.getParent());
+            }
+            if (oldParentObj.isRoot() || parentId != -1) {
+                // Old parent exists in MediaProvider - perform a move
+                // note - we are relying on a special case in MediaProvider.update() to update
+                // the paths for all children in the case where this is a directory.
+                mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
+            } else {
+                // Old parent doesn't exist - add the object
+                values.put(Files.FileColumns.FORMAT, obj.getFormat());
+                values.put(Files.FileColumns.SIZE, obj.getSize());
+                values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
+                Uri uri = mMediaProvider.insert(mObjectsUri, values);
+                if (uri != null) {
+                    rescanFile(path.toString(),
+                            Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
+                }
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
         }
-        if (updated == 0) {
-            Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
+    }
+
+    private int beginCopyObject(int handle, int newParent, int newStorage) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        MtpStorageManager.MtpObject parent = newParent == 0 ?
+                mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+        if (obj == null || parent == null)
+            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+        return mManager.beginCopyObject(obj, parent);
+    }
+
+    private void endCopyObject(int handle, boolean success) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null || !mManager.endCopyObject(obj, success)) {
+            Log.e(TAG, "Failed to end copy object");
+            return;
         }
-        return MtpConstants.RESPONSE_OK;
+        if (!success) {
+            return;
+        }
+        String path = obj.getPath().toString();
+        int format = obj.getFormat();
+        // Get parent info from MediaProvider, since the id is different from MTP's
+        ContentValues values = new ContentValues();
+        values.put(Files.FileColumns.DATA, path);
+        values.put(Files.FileColumns.FORMAT, format);
+        values.put(Files.FileColumns.SIZE, obj.getSize());
+        values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
+        try {
+            if (obj.getParent().isRoot()) {
+                values.put(Files.FileColumns.PARENT, 0);
+            } else {
+                int parentId = findInMedia(obj.getParent().getPath());
+                if (parentId != -1) {
+                    values.put(Files.FileColumns.PARENT, parentId);
+                } else {
+                    // The parent isn't in MediaProvider. Don't add the new file.
+                    return;
+                }
+            }
+            if (obj.isDir()) {
+                mMediaScanner.scanDirectories(new String[]{path});
+            } else {
+                Uri uri = mMediaProvider.insert(mObjectsUri, values);
+                if (uri != null) {
+                    rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in beginSendObject", e);
+        }
     }
 
     private int setObjectProperty(int handle, int property,
-                            long intValue, String stringValue) {
+            long intValue, String stringValue) {
         switch (property) {
             case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
                 return renameFile(handle, stringValue);
@@ -912,24 +748,23 @@
                 value.getChars(0, length, outStringValue, 0);
                 outStringValue[length] = 0;
                 return MtpConstants.RESPONSE_OK;
-
             case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
                 // use screen size as max image size
-                Display display = ((WindowManager)mContext.getSystemService(
+                Display display = ((WindowManager) mContext.getSystemService(
                         Context.WINDOW_SERVICE)).getDefaultDisplay();
                 int width = display.getMaximumSizeDimension();
                 int height = display.getMaximumSizeDimension();
-                String imageSize = Integer.toString(width) + "x" +  Integer.toString(height);
+                String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
                 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
                 outStringValue[imageSize.length()] = 0;
                 return MtpConstants.RESPONSE_OK;
-
             case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
                 outIntValue[0] = mDeviceType;
                 return MtpConstants.RESPONSE_OK;
-
-            // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
-
+            case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
+                outIntValue[0] = mBatteryLevel;
+                outIntValue[1] = mBatteryScale;
+                return MtpConstants.RESPONSE_OK;
             default:
                 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
         }
@@ -950,179 +785,144 @@
     }
 
     private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
-                        char[] outName, long[] outCreatedModified) {
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
-                            ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
-            if (c != null && c.moveToNext()) {
-                outStorageFormatParent[0] = c.getInt(1);
-                outStorageFormatParent[1] = c.getInt(2);
-                outStorageFormatParent[2] = c.getInt(3);
-
-                // extract name from path
-                String path = c.getString(4);
-                int lastSlash = path.lastIndexOf('/');
-                int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
-                int end = path.length();
-                if (end - start > 255) {
-                    end = start + 255;
-                }
-                path.getChars(start, end, outName, 0);
-                outName[end - start] = 0;
-
-                outCreatedModified[0] = c.getLong(5);
-                outCreatedModified[1] = c.getLong(6);
-                // use modification date as creation date if date added is not set
-                if (outCreatedModified[0] == 0) {
-                    outCreatedModified[0] = outCreatedModified[1];
-                }
-                return true;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectInfo", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
+            char[] outName, long[] outCreatedModified) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return false;
         }
-        return false;
+        outStorageFormatParent[0] = obj.getStorageId();
+        outStorageFormatParent[1] = obj.getFormat();
+        outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
+
+        int nameLen = Integer.min(obj.getName().length(), 255);
+        obj.getName().getChars(0, nameLen, outName, 0);
+        outName[nameLen] = 0;
+
+        outCreatedModified[0] = obj.getModifiedTime();
+        outCreatedModified[1] = obj.getModifiedTime();
+        return true;
     }
 
     private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
-        if (handle == 0) {
-            // special case root directory
-            mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
-            outFilePath[mMediaStoragePath.length()] = 0;
-            outFileLengthFormat[0] = 0;
-            outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
-            return MtpConstants.RESPONSE_OK;
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
         }
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
-                            ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
-            if (c != null && c.moveToNext()) {
-                String path = c.getString(1);
-                path.getChars(0, path.length(), outFilePath, 0);
-                outFilePath[path.length()] = 0;
-                // File transfers from device to host will likely fail if the size is incorrect.
-                // So to be safe, use the actual file size here.
-                outFileLengthFormat[0] = new File(path).length();
-                outFileLengthFormat[1] = c.getLong(2);
-                return MtpConstants.RESPONSE_OK;
-            } else {
-                return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectFilePath", e);
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
+
+        String path = obj.getPath().toString();
+        int pathLen = Integer.min(path.length(), 4096);
+        path.getChars(0, pathLen, outFilePath, 0);
+        outFilePath[pathLen] = 0;
+
+        outFileLengthFormat[0] = obj.getSize();
+        outFileLengthFormat[1] = obj.getFormat();
+        return MtpConstants.RESPONSE_OK;
     }
 
     private int getObjectFormat(int handle) {
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
-                            ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
-            if (c != null && c.moveToNext()) {
-                return c.getInt(1);
-            } else {
-                return -1;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectFilePath", e);
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
             return -1;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
         }
+        return obj.getFormat();
     }
 
-    private int deleteFile(int handle) {
-        mDatabaseModified = true;
-        String path = null;
-        int format = 0;
+    private int beginDeleteObject(int handle) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+        }
+        if (!mManager.beginRemoveObject(obj)) {
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        }
+        return MtpConstants.RESPONSE_OK;
+    }
 
+    private void endDeleteObject(int handle, boolean success) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return;
+        }
+        if (!mManager.endRemoveObject(obj, success))
+            Log.e(TAG, "Failed to end remove object");
+        if (success)
+            deleteFromMedia(obj.getPath(), obj.isDir());
+    }
+
+    private int findInMedia(Path path) {
+        int ret = -1;
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
-                            ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
+            c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
+                    new String[]{path.toString()}, null, null);
             if (c != null && c.moveToNext()) {
-                // don't convert to media path here, since we will be matching
-                // against paths in the database matching /data/media
-                path = c.getString(1);
-                format = c.getInt(2);
-            } else {
-                return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                ret = c.getInt(0);
             }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error finding " + path + " in MediaProvider");
+        } finally {
+            if (c != null)
+                c.close();
+        }
+        return ret;
+    }
 
-            if (path == null || format == 0) {
-                return MtpConstants.RESPONSE_GENERAL_ERROR;
-            }
-
-            // do not allow deleting any of the special subdirectories
-            if (isStorageSubDirectory(path)) {
-                return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
-            }
-
-            if (format == MtpConstants.FORMAT_ASSOCIATION) {
+    private void deleteFromMedia(Path path, boolean isDir) {
+        try {
+            // Delete the object(s) from MediaProvider, but ignore errors.
+            if (isDir) {
                 // recursive case - delete all children first
-                Uri uri = Files.getMtpObjectsUri(mVolumeName);
-                int count = mMediaProvider.delete(uri,
-                    // the 'like' makes it use the index, the 'lower()' makes it correct
-                    // when the path contains sqlite wildcard characters
-                    "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
-                    new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
+                mMediaProvider.delete(mObjectsUri,
+                        // the 'like' makes it use the index, the 'lower()' makes it correct
+                        // when the path contains sqlite wildcard characters
+                        "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+                        new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
+                                path.toString() + "/"});
             }
 
-            Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
-            if (mMediaProvider.delete(uri, null, null) > 0) {
-                if (format != MtpConstants.FORMAT_ASSOCIATION
-                        && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
+            String[] whereArgs = new String[]{path.toString()};
+            if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
+                if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
                     try {
-                        String parentPath = path.substring(0, path.lastIndexOf("/"));
+                        String parentPath = path.getParent().toString();
                         mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
                     } catch (RemoteException e) {
                         Log.e(TAG, "failed to unhide/rescan for " + path);
                     }
                 }
-                return MtpConstants.RESPONSE_OK;
             } else {
-                return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                Log.i(TAG, "Mediaprovider didn't delete " + path);
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in deleteFile", e);
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
+        } catch (Exception e) {
+            Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
         }
     }
 
     private int[] getObjectReferences(int handle) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null)
+            return null;
+        // Translate this handle to the MediaProvider Handle
+        handle = findInMedia(obj.getPath());
+        if (handle == -1)
+            return null;
         Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
         Cursor c = null;
         try {
-            c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
+            c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
             if (c == null) {
                 return null;
             }
-            int count = c.getCount();
-            if (count > 0) {
-                int[] result = new int[count];
-                for (int i = 0; i < count; i++) {
-                    c.moveToNext();
-                    result[i] = c.getInt(0);
+                ArrayList<Integer> result = new ArrayList<>();
+                while (c.moveToNext()) {
+                    // Translate result handles back into handles for this session.
+                    String refPath = c.getString(0);
+                    MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
+                    if (refObj != null) {
+                        result.add(refObj.getId());
+                    }
                 }
-                return result;
-            }
+                return result.stream().mapToInt(Integer::intValue).toArray();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in getObjectList", e);
         } finally {
@@ -1134,17 +934,29 @@
     }
 
     private int setObjectReferences(int handle, int[] references) {
-        mDatabaseModified = true;
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null)
+            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+        // Translate this handle to the MediaProvider Handle
+        handle = findInMedia(obj.getPath());
+        if (handle == -1)
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
         Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
-        int count = references.length;
-        ContentValues[] valuesList = new ContentValues[count];
-        for (int i = 0; i < count; i++) {
+        ArrayList<ContentValues> valuesList = new ArrayList<>();
+        for (int id : references) {
+            // Translate each reference id to the MediaProvider Id
+            MtpStorageManager.MtpObject refObj = mManager.getObject(id);
+            if (refObj == null)
+                continue;
+            int refHandle = findInMedia(refObj.getPath());
+            if (refHandle == -1)
+                continue;
             ContentValues values = new ContentValues();
-            values.put(Files.FileColumns._ID, references[i]);
-            valuesList[i] = values;
+            values.put(Files.FileColumns._ID, refHandle);
+            valuesList.add(values);
         }
         try {
-            if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
+            if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
                 return MtpConstants.RESPONSE_OK;
             }
         } catch (RemoteException e) {
@@ -1153,17 +965,6 @@
         return MtpConstants.RESPONSE_GENERAL_ERROR;
     }
 
-    private void sessionStarted() {
-        mDatabaseModified = false;
-    }
-
-    private void sessionEnded() {
-        if (mDatabaseModified) {
-            mUserContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
-            mDatabaseModified = false;
-        }
-    }
-
     // used by the JNI code
     private long mNativeContext;
 
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index dea3008..77d0f34f 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -23,22 +23,21 @@
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Files;
 import android.provider.MediaStore.Images;
-import android.provider.MediaStore.MediaColumns;
 import android.util.Log;
 
 import java.util.ArrayList;
 
+/**
+ * MtpPropertyGroup represents a list of MTP properties.
+ * {@hide}
+ */
 class MtpPropertyGroup {
-
-    private static final String TAG = "MtpPropertyGroup";
+    private static final String TAG = MtpPropertyGroup.class.getSimpleName();
 
     private class Property {
-        // MTP property code
-        int     code;
-        // MTP data type
-        int     type;
-        // column index for our query
-        int     column;
+        int code;
+        int type;
+        int column;
 
         Property(int code, int type, int column) {
             this.code = code;
@@ -47,32 +46,26 @@
         }
     }
 
-    private final MtpDatabase mDatabase;
     private final ContentProviderClient mProvider;
     private final String mVolumeName;
     private final Uri mUri;
 
     // list of all properties in this group
-    private final Property[]    mProperties;
+    private final Property[] mProperties;
 
     // list of columns for database query
-    private String[]             mColumns;
+    private String[] mColumns;
 
-    private static final String ID_WHERE = Files.FileColumns._ID + "=?";
-    private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
-    private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
-    private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
-    private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
+    private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
+
     // constructs a property group for a list of properties
-    public MtpPropertyGroup(MtpDatabase database, ContentProviderClient provider, String volumeName,
-            int[] properties) {
-        mDatabase = database;
+    public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
         mProvider = provider;
         mVolumeName = volumeName;
         mUri = Files.getMtpObjectsUri(volumeName);
 
         int count = properties.length;
-        ArrayList<String> columns = new ArrayList<String>(count);
+        ArrayList<String> columns = new ArrayList<>(count);
         columns.add(Files.FileColumns._ID);
 
         mProperties = new Property[count];
@@ -90,37 +83,29 @@
         String column = null;
         int type;
 
-         switch (code) {
+        switch (code) {
             case MtpConstants.PROPERTY_STORAGE_ID:
-                column = Files.FileColumns.STORAGE_ID;
                 type = MtpConstants.TYPE_UINT32;
                 break;
-             case MtpConstants.PROPERTY_OBJECT_FORMAT:
-                column = Files.FileColumns.FORMAT;
+            case MtpConstants.PROPERTY_OBJECT_FORMAT:
                 type = MtpConstants.TYPE_UINT16;
                 break;
             case MtpConstants.PROPERTY_PROTECTION_STATUS:
-                // protection status is always 0
                 type = MtpConstants.TYPE_UINT16;
                 break;
             case MtpConstants.PROPERTY_OBJECT_SIZE:
-                column = Files.FileColumns.SIZE;
                 type = MtpConstants.TYPE_UINT64;
                 break;
             case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
-                column = Files.FileColumns.DATA;
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_NAME:
-                column = MediaColumns.TITLE;
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_DATE_MODIFIED:
-                column = Files.FileColumns.DATE_MODIFIED;
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_DATE_ADDED:
-                column = Files.FileColumns.DATE_ADDED;
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
@@ -128,12 +113,9 @@
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_PARENT_OBJECT:
-                column = Files.FileColumns.PARENT;
                 type = MtpConstants.TYPE_UINT32;
                 break;
             case MtpConstants.PROPERTY_PERSISTENT_UID:
-                // PUID is concatenation of storageID and object handle
-                column = Files.FileColumns.STORAGE_ID;
                 type = MtpConstants.TYPE_UINT128;
                 break;
             case MtpConstants.PROPERTY_DURATION:
@@ -145,7 +127,6 @@
                 type = MtpConstants.TYPE_UINT16;
                 break;
             case MtpConstants.PROPERTY_DISPLAY_NAME:
-                column = MediaColumns.DISPLAY_NAME;
                 type = MtpConstants.TYPE_STR;
                 break;
             case MtpConstants.PROPERTY_ARTIST:
@@ -195,40 +176,19 @@
         }
     }
 
-   private String queryString(int id, String column) {
-        Cursor c = null;
-        try {
-            // for now we are only reading properties from the "objects" table
-            c = mProvider.query(mUri,
-                            new String [] { Files.FileColumns._ID, column },
-                            ID_WHERE, new String[] { Integer.toString(id) }, null, null);
-            if (c != null && c.moveToNext()) {
-                return c.getString(1);
-            } else {
-                return "";
-            }
-        } catch (Exception e) {
-            return null;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    private String queryAudio(int id, String column) {
+    private String queryAudio(String path, String column) {
         Cursor c = null;
         try {
             c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
-                            new String [] { Files.FileColumns._ID, column },
-                            ID_WHERE, new String[] { Integer.toString(id) }, null, null);
+                            new String [] { column },
+                            PATH_WHERE, new String[] {path}, null, null);
             if (c != null && c.moveToNext()) {
-                return c.getString(1);
+                return c.getString(0);
             } else {
                 return "";
             }
         } catch (Exception e) {
-            return null;
+            return "";
         } finally {
             if (c != null) {
                 c.close();
@@ -236,21 +196,19 @@
         }
     }
 
-    private String queryGenre(int id) {
+    private String queryGenre(String path) {
         Cursor c = null;
         try {
-            Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
-            c = mProvider.query(uri,
-                            new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
-                            null, null, null, null);
+            c = mProvider.query(Audio.Genres.getContentUri(mVolumeName),
+                            new String [] { Audio.GenresColumns.NAME },
+                            PATH_WHERE, new String[] {path}, null, null);
             if (c != null && c.moveToNext()) {
-                return c.getString(1);
+                return c.getString(0);
             } else {
                 return "";
             }
         } catch (Exception e) {
-            Log.e(TAG, "queryGenre exception", e);
-            return null;
+            return "";
         } finally {
             if (c != null) {
                 c.close();
@@ -258,211 +216,127 @@
         }
     }
 
-    private Long queryLong(int id, String column) {
+    /**
+     * Gets the values of the properties represented by this property group for the given
+     * object and adds them to the given property list.
+     * @return Response_OK if the operation succeeded.
+     */
+    public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) {
         Cursor c = null;
-        try {
-            // for now we are only reading properties from the "objects" table
-            c = mProvider.query(mUri,
-                            new String [] { Files.FileColumns._ID, column },
-                            ID_WHERE, new String[] { Integer.toString(id) }, null, null);
-            if (c != null && c.moveToNext()) {
-                return new Long(c.getLong(1));
-            }
-        } catch (Exception e) {
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return null;
-    }
-
-    private static String nameFromPath(String path) {
-        // extract name from full path
-        int start = 0;
-        int lastSlash = path.lastIndexOf('/');
-        if (lastSlash >= 0) {
-            start = lastSlash + 1;
-        }
-        int end = path.length();
-        if (end - start > 255) {
-            end = start + 255;
-        }
-        return path.substring(start, end);
-    }
-
-    MtpPropertyList getPropertyList(int handle, int format, int depth) {
-        //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
-        if (depth > 1) {
-            // we only support depth 0 and 1
-            // depth 0: single object, depth 1: immediate children
-            return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
-        }
-
-        String where;
-        String[] whereArgs;
-        if (format == 0) {
-            if (handle == 0xFFFFFFFF) {
-                // select all objects
-                where = null;
-                whereArgs = null;
-            } else {
-                whereArgs = new String[] { Integer.toString(handle) };
-                if (depth == 1) {
-                    where = PARENT_WHERE;
-                } else {
-                    where = ID_WHERE;
+        int id = object.getId();
+        String path = object.getPath().toString();
+        for (Property property : mProperties) {
+            if (property.column != -1 && c == null) {
+                try {
+                    // Look up the entry in MediaProvider only if one of those properties is needed.
+                    c = mProvider.query(mUri, mColumns,
+                            PATH_WHERE, new String[] {path}, null, null);
+                    if (c != null && !c.moveToNext()) {
+                        c.close();
+                        c = null;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Mediaprovider lookup failed");
                 }
             }
-        } else {
-            if (handle == 0xFFFFFFFF) {
-                // select all objects with given format
-                where = FORMAT_WHERE;
-                whereArgs = new String[] { Integer.toString(format) };
-            } else {
-                whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) };
-                if (depth == 1) {
-                    where = PARENT_FORMAT_WHERE;
-                } else {
-                    where = ID_FORMAT_WHERE;
-                }
-            }
-        }
-
-        Cursor c = null;
-        try {
-            // don't query if not necessary
-            if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
-                c = mProvider.query(mUri, mColumns, where, whereArgs, null, null);
-                if (c == null) {
-                    return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
-                }
-            }
-
-            int count = (c == null ? 1 : c.getCount());
-            MtpPropertyList result = new MtpPropertyList(count * mProperties.length,
-                    MtpConstants.RESPONSE_OK);
-
-            // iterate over all objects in the query
-            for (int objectIndex = 0; objectIndex < count; objectIndex++) {
-                if (c != null) {
-                    c.moveToNext();
-                    handle = (int)c.getLong(0);
-                }
-
-                // iterate over all properties in the query for the given object
-                for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) {
-                    Property property = mProperties[propertyIndex];
-                    int propertyCode = property.code;
-                    int column = property.column;
-
-                    // handle some special cases
-                    switch (propertyCode) {
-                        case MtpConstants.PROPERTY_PROTECTION_STATUS:
-                            // protection status is always 0
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
+            switch (property.code) {
+                case MtpConstants.PROPERTY_PROTECTION_STATUS:
+                    // protection status is always 0
+                    list.append(id, property.code, property.type, 0);
+                    break;
+                case MtpConstants.PROPERTY_NAME:
+                case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
+                case MtpConstants.PROPERTY_DISPLAY_NAME:
+                    list.append(id, property.code, object.getName());
+                    break;
+                case MtpConstants.PROPERTY_DATE_MODIFIED:
+                case MtpConstants.PROPERTY_DATE_ADDED:
+                    // convert from seconds to DateTime
+                    list.append(id, property.code,
+                            format_date_time(object.getModifiedTime()));
+                    break;
+                case MtpConstants.PROPERTY_STORAGE_ID:
+                    list.append(id, property.code, property.type, object.getStorageId());
+                    break;
+                case MtpConstants.PROPERTY_OBJECT_FORMAT:
+                    list.append(id, property.code, property.type, object.getFormat());
+                    break;
+                case MtpConstants.PROPERTY_OBJECT_SIZE:
+                    list.append(id, property.code, property.type, object.getSize());
+                    break;
+                case MtpConstants.PROPERTY_PARENT_OBJECT:
+                    list.append(id, property.code, property.type,
+                            object.getParent().isRoot() ? 0 : object.getParent().getId());
+                    break;
+                case MtpConstants.PROPERTY_PERSISTENT_UID:
+                    // The persistent uid must be unique and never reused among all objects,
+                    // and remain the same between sessions.
+                    long puid = (object.getPath().toString().hashCode() << 32)
+                            + object.getModifiedTime();
+                    list.append(id, property.code, property.type, puid);
+                    break;
+                case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
+                    // release date is stored internally as just the year
+                    int year = 0;
+                    if (c != null)
+                        year = c.getInt(property.column);
+                    String dateTime = Integer.toString(year) + "0101T000000";
+                    list.append(id, property.code, dateTime);
+                    break;
+                case MtpConstants.PROPERTY_TRACK:
+                    int track = 0;
+                    if (c != null)
+                        track = c.getInt(property.column);
+                    list.append(id, property.code, MtpConstants.TYPE_UINT16,
+                            track % 1000);
+                    break;
+                case MtpConstants.PROPERTY_ARTIST:
+                    list.append(id, property.code,
+                            queryAudio(path, Audio.AudioColumns.ARTIST));
+                    break;
+                case MtpConstants.PROPERTY_ALBUM_NAME:
+                    list.append(id, property.code,
+                            queryAudio(path, Audio.AudioColumns.ALBUM));
+                    break;
+                case MtpConstants.PROPERTY_GENRE:
+                    String genre = queryGenre(path);
+                    if (genre != null) {
+                        list.append(id, property.code, genre);
+                    }
+                    break;
+                case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
+                case MtpConstants.PROPERTY_AUDIO_BITRATE:
+                case MtpConstants.PROPERTY_SAMPLE_RATE:
+                    // we don't have these in our database, so return 0
+                    list.append(id, property.code, MtpConstants.TYPE_UINT32, 0);
+                    break;
+                case MtpConstants.PROPERTY_BITRATE_TYPE:
+                case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
+                    // we don't have these in our database, so return 0
+                    list.append(id, property.code, MtpConstants.TYPE_UINT16, 0);
+                    break;
+                default:
+                    switch(property.type) {
+                        case MtpConstants.TYPE_UNDEFINED:
+                            list.append(id, property.code, property.type, 0);
                             break;
-                        case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
-                            // special case - need to extract file name from full path
-                            String value = c.getString(column);
-                            if (value != null) {
-                                result.append(handle, propertyCode, nameFromPath(value));
-                            } else {
-                                result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
-                            }
-                            break;
-                        case MtpConstants.PROPERTY_NAME:
-                            // first try title
-                            String name = c.getString(column);
-                            // then try name
-                            if (name == null) {
-                                name = queryString(handle, Audio.PlaylistsColumns.NAME);
-                            }
-                            // if title and name fail, extract name from full path
-                            if (name == null) {
-                                name = queryString(handle, Files.FileColumns.DATA);
-                                if (name != null) {
-                                    name = nameFromPath(name);
-                                }
-                            }
-                            if (name != null) {
-                                result.append(handle, propertyCode, name);
-                            } else {
-                                result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
-                            }
-                            break;
-                        case MtpConstants.PROPERTY_DATE_MODIFIED:
-                        case MtpConstants.PROPERTY_DATE_ADDED:
-                            // convert from seconds to DateTime
-                            result.append(handle, propertyCode, format_date_time(c.getInt(column)));
-                            break;
-                        case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
-                            // release date is stored internally as just the year
-                            int year = c.getInt(column);
-                            String dateTime = Integer.toString(year) + "0101T000000";
-                            result.append(handle, propertyCode, dateTime);
-                            break;
-                        case MtpConstants.PROPERTY_PERSISTENT_UID:
-                            // PUID is concatenation of storageID and object handle
-                            long puid = c.getLong(column);
-                            puid <<= 32;
-                            puid += handle;
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
-                            break;
-                        case MtpConstants.PROPERTY_TRACK:
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT16,
-                                        c.getInt(column) % 1000);
-                            break;
-                        case MtpConstants.PROPERTY_ARTIST:
-                            result.append(handle, propertyCode,
-                                    queryAudio(handle, Audio.AudioColumns.ARTIST));
-                            break;
-                        case MtpConstants.PROPERTY_ALBUM_NAME:
-                            result.append(handle, propertyCode,
-                                    queryAudio(handle, Audio.AudioColumns.ALBUM));
-                            break;
-                        case MtpConstants.PROPERTY_GENRE:
-                            String genre = queryGenre(handle);
-                            if (genre != null) {
-                                result.append(handle, propertyCode, genre);
-                            } else {
-                                result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
-                            }
-                            break;
-                        case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
-                        case MtpConstants.PROPERTY_AUDIO_BITRATE:
-                        case MtpConstants.PROPERTY_SAMPLE_RATE:
-                            // we don't have these in our database, so return 0
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT32, 0);
-                            break;
-                        case MtpConstants.PROPERTY_BITRATE_TYPE:
-                        case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
-                            // we don't have these in our database, so return 0
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
+                        case MtpConstants.TYPE_STR:
+                            String value = "";
+                            if (c != null)
+                                value = c.getString(property.column);
+                            list.append(id, property.code, value);
                             break;
                         default:
-                            if (property.type == MtpConstants.TYPE_STR) {
-                                result.append(handle, propertyCode, c.getString(column));
-                            } else if (property.type == MtpConstants.TYPE_UNDEFINED) {
-                                result.append(handle, propertyCode, property.type, 0);
-                            } else {
-                                result.append(handle, propertyCode, property.type,
-                                        c.getLong(column));
-                            }
-                            break;
+                            long longValue = 0L;
+                            if (c != null)
+                                longValue = c.getLong(property.column);
+                            list.append(id, property.code, property.type, longValue);
                     }
-                }
-            }
-
-            return result;
-        } catch (RemoteException e) {
-            return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
-        } finally {
-            if (c != null) {
-                c.close();
             }
         }
-        // impossible to get here, so no return statement
+        if (c != null)
+            c.close();
+        return MtpConstants.RESPONSE_OK;
     }
 
     private native String format_date_time(long seconds);
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index f9bc603..ede90da 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -16,6 +16,9 @@
 
 package android.mtp;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Encapsulates the ObjectPropList dataset used by the GetObjectPropList command.
  * The fields of this class are read by JNI code in android_media_MtpDatabase.cpp
@@ -23,56 +26,70 @@
 
 class MtpPropertyList {
 
-    // number of results returned
-    private int             mCount;
-    // maximum number of results
-    private final int       mMaxCount;
-    // result code for GetObjectPropList
-    public int              mResult;
     // list of object handles (first field in quadruplet)
-    public final int[]      mObjectHandles;
-    // list of object propery codes (second field in quadruplet)
-    public final int[]      mPropertyCodes;
+    private List<Integer> mObjectHandles;
+    // list of object property codes (second field in quadruplet)
+    private List<Integer> mPropertyCodes;
     // list of data type codes (third field in quadruplet)
-    public final int[]     mDataTypes;
+    private List<Integer> mDataTypes;
     // list of long int property values (fourth field in quadruplet, when value is integer type)
-    public long[]     mLongValues;
+    private List<Long> mLongValues;
     // list of long int property values (fourth field in quadruplet, when value is string type)
-    public String[]   mStringValues;
+    private List<String> mStringValues;
 
-    // constructor only called from MtpDatabase
-    public MtpPropertyList(int maxCount, int result) {
-        mMaxCount = maxCount;
-        mResult = result;
-        mObjectHandles = new int[maxCount];
-        mPropertyCodes = new int[maxCount];
-        mDataTypes = new int[maxCount];
-        // mLongValues and mStringValues are created lazily since both might not be necessary
+    // Return value of this operation
+    private int mCode;
+
+    public MtpPropertyList(int code) {
+        mCode = code;
+        mObjectHandles = new ArrayList<>();
+        mPropertyCodes = new ArrayList<>();
+        mDataTypes = new ArrayList<>();
+        mLongValues = new ArrayList<>();
+        mStringValues = new ArrayList<>();
     }
 
     public void append(int handle, int property, int type, long value) {
-        int index = mCount++;
-        if (mLongValues == null) {
-            mLongValues = new long[mMaxCount];
-        }
-        mObjectHandles[index] = handle;
-        mPropertyCodes[index] = property;
-        mDataTypes[index] = type;
-        mLongValues[index] = value;
+        mObjectHandles.add(handle);
+        mPropertyCodes.add(property);
+        mDataTypes.add(type);
+        mLongValues.add(value);
+        mStringValues.add(null);
     }
 
     public void append(int handle, int property, String value) {
-        int index = mCount++;
-        if (mStringValues == null) {
-            mStringValues = new String[mMaxCount];
-        }
-        mObjectHandles[index] = handle;
-        mPropertyCodes[index] = property;
-        mDataTypes[index] = MtpConstants.TYPE_STR;
-        mStringValues[index] = value;
+        mObjectHandles.add(handle);
+        mPropertyCodes.add(property);
+        mDataTypes.add(MtpConstants.TYPE_STR);
+        mStringValues.add(value);
+        mLongValues.add(0L);
     }
 
-    public void setResult(int result) {
-        mResult = result;
+    public int getCode() {
+        return mCode;
+    }
+
+    public int getCount() {
+        return mObjectHandles.size();
+    }
+
+    public int[] getObjectHandles() {
+        return mObjectHandles.stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    public int[] getPropertyCodes() {
+        return mPropertyCodes.stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    public int[] getDataTypes() {
+        return mDataTypes.stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    public long[] getLongValues() {
+        return mLongValues.stream().mapToLong(Long::longValue).toArray();
+    }
+
+    public String[] getStringValues() {
+        return mStringValues.toArray(new String[0]);
     }
 }
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 6ca442c..c72b827 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -31,15 +31,13 @@
     private final int mStorageId;
     private final String mPath;
     private final String mDescription;
-    private final long mReserveSpace;
     private final boolean mRemovable;
     private final long mMaxFileSize;
 
-    public MtpStorage(StorageVolume volume, Context context) {
-        mStorageId = volume.getStorageId();
+    public MtpStorage(StorageVolume volume, int storageId) {
+        mStorageId = storageId;
         mPath = volume.getPath();
-        mDescription = volume.getDescription(context);
-        mReserveSpace = volume.getMtpReserveSpace() * 1024L * 1024L;
+        mDescription = volume.getDescription(null);
         mRemovable = volume.isRemovable();
         mMaxFileSize = volume.getMaxFileSize();
     }
@@ -72,16 +70,6 @@
     }
 
    /**
-     * Returns the amount of space to reserve on the storage file system.
-     * This can be set to a non-zero value to prevent MTP from filling up the entire storage.
-     *
-     * @return reserved space in bytes.
-     */
-    public final long getReserveSpace() {
-        return mReserveSpace;
-    }
-
-   /**
      * Returns true if the storage is removable.
      *
      * @return is removable
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
new file mode 100644
index 0000000..bdc8741
--- /dev/null
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -0,0 +1,1210 @@
+/*
+ * 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.mtp;
+
+import android.media.MediaFile;
+import android.os.FileObserver;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * MtpStorageManager provides functionality for listing, tracking, and notifying MtpServer of
+ * filesystem changes. As directories are listed, this class will cache the results,
+ * and send events when objects are added/removed from cached directories.
+ * {@hide}
+ */
+public class MtpStorageManager {
+    private static final String TAG = MtpStorageManager.class.getSimpleName();
+    public static boolean sDebug = false;
+
+    // Inotify flags not provided by FileObserver
+    private static final int IN_ONLYDIR = 0x01000000;
+    private static final int IN_Q_OVERFLOW = 0x00004000;
+    private static final int IN_IGNORED    = 0x00008000;
+    private static final int IN_ISDIR = 0x40000000;
+
+    private class MtpObjectObserver extends FileObserver {
+        MtpObject mObject;
+
+        MtpObjectObserver(MtpObject object) {
+            super(object.getPath().toString(),
+                    MOVED_FROM | MOVED_TO | DELETE | CREATE | IN_ONLYDIR);
+            mObject = object;
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            synchronized (MtpStorageManager.this) {
+                if ((event & IN_Q_OVERFLOW) != 0) {
+                    // We are out of space in the inotify queue.
+                    Log.e(TAG, "Received Inotify overflow event!");
+                }
+                MtpObject obj = mObject.getChild(path);
+                if ((event & MOVED_TO) != 0 || (event & CREATE) != 0) {
+                    if (sDebug)
+                        Log.i(TAG, "Got inotify added event for " + path + " " + event);
+                    handleAddedObject(mObject, path, (event & IN_ISDIR) != 0);
+                } else if ((event & MOVED_FROM) != 0 || (event & DELETE) != 0) {
+                    if (obj == null) {
+                        Log.w(TAG, "Object was null in event " + path);
+                        return;
+                    }
+                    if (sDebug)
+                        Log.i(TAG, "Got inotify removed event for " + path + " " + event);
+                    handleRemovedObject(obj);
+                } else if ((event & IN_IGNORED) != 0) {
+                    if (sDebug)
+                        Log.i(TAG, "inotify for " + mObject.getPath() + " deleted");
+                    if (mObject.mObserver != null)
+                        mObject.mObserver.stopWatching();
+                    mObject.mObserver = null;
+                } else {
+                    Log.w(TAG, "Got unrecognized event " + path + " " + event);
+                }
+            }
+        }
+
+        @Override
+        public void finalize() {
+            // If the server shuts down and starts up again, the new server's observers can be
+            // invalidated by the finalize() calls of the previous server's observers.
+            // Hence, disable the automatic stopWatching() call in FileObserver#finalize, and
+            // always call stopWatching() manually whenever an observer should be shut down.
+        }
+    }
+
+    /**
+     * Describes how the object is being acted on, to determine how events are handled.
+     */
+    private enum MtpObjectState {
+        NORMAL,
+        FROZEN,             // Object is going to be modified in this session.
+        FROZEN_ADDED,       // Object was frozen, and has been added.
+        FROZEN_REMOVED,     // Object was frozen, and has been removed.
+        FROZEN_ONESHOT_ADD, // Object is waiting for single add event before being unfrozen.
+        FROZEN_ONESHOT_DEL, // Object is waiting for single remove event and will then be removed.
+    }
+
+    /**
+     * Describes the current operation being done on an object. Determines whether observers are
+     * created on new folders.
+     */
+    private enum MtpOperation {
+        NONE,     // Any new folders not added as part of the session are immediately observed.
+        ADD,      // New folders added as part of the session are immediately observed.
+        RENAME,   // Renamed or moved folders are not immediately observed.
+        COPY,     // Copied folders are immediately observed iff the original was.
+        DELETE,   // Exists for debugging purposes only.
+    }
+
+    /** MtpObject represents either a file or directory in an associated storage. **/
+    public static class MtpObject {
+        // null for root objects
+        private MtpObject mParent;
+
+        private String mName;
+        private int mId;
+        private MtpObjectState mState;
+        private MtpOperation mOp;
+
+        private boolean mVisited;
+        private boolean mIsDir;
+
+        // null if not a directory
+        private HashMap<String, MtpObject> mChildren;
+        // null if not both a directory and visited
+        private FileObserver mObserver;
+
+        MtpObject(String name, int id, MtpObject parent, boolean isDir) {
+            mId = id;
+            mName = name;
+            mParent = parent;
+            mObserver = null;
+            mVisited = false;
+            mState = MtpObjectState.NORMAL;
+            mIsDir = isDir;
+            mOp = MtpOperation.NONE;
+
+            mChildren = mIsDir ? new HashMap<>() : null;
+        }
+
+        /** Public methods for getting object info **/
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getId() {
+            return mId;
+        }
+
+        public boolean isDir() {
+            return mIsDir;
+        }
+
+        public int getFormat() {
+            return mIsDir ? MtpConstants.FORMAT_ASSOCIATION : MediaFile.getFormatCode(mName, null);
+        }
+
+        public int getStorageId() {
+            return getRoot().getId();
+        }
+
+        public long getModifiedTime() {
+            return getPath().toFile().lastModified() / 1000;
+        }
+
+        public MtpObject getParent() {
+            return mParent;
+        }
+
+        public MtpObject getRoot() {
+            return isRoot() ? this : mParent.getRoot();
+        }
+
+        public long getSize() {
+            return mIsDir ? 0 : getPath().toFile().length();
+        }
+
+        public Path getPath() {
+            return isRoot() ? Paths.get(mName) : mParent.getPath().resolve(mName);
+        }
+
+        public boolean isRoot() {
+            return mParent == null;
+        }
+
+        /** For MtpStorageManager only **/
+
+        private void setName(String name) {
+            mName = name;
+        }
+
+        private void setId(int id) {
+            mId = id;
+        }
+
+        private boolean isVisited() {
+            return mVisited;
+        }
+
+        private void setParent(MtpObject parent) {
+            mParent = parent;
+        }
+
+        private void setDir(boolean dir) {
+            if (dir != mIsDir) {
+                mIsDir = dir;
+                mChildren = mIsDir ? new HashMap<>() : null;
+            }
+        }
+
+        private void setVisited(boolean visited) {
+            mVisited = visited;
+        }
+
+        private MtpObjectState getState() {
+            return mState;
+        }
+
+        private void setState(MtpObjectState state) {
+            mState = state;
+            if (mState == MtpObjectState.NORMAL)
+                mOp = MtpOperation.NONE;
+        }
+
+        private MtpOperation getOperation() {
+            return mOp;
+        }
+
+        private void setOperation(MtpOperation op) {
+            mOp = op;
+        }
+
+        private FileObserver getObserver() {
+            return mObserver;
+        }
+
+        private void setObserver(FileObserver observer) {
+            mObserver = observer;
+        }
+
+        private void addChild(MtpObject child) {
+            mChildren.put(child.getName(), child);
+        }
+
+        private MtpObject getChild(String name) {
+            return mChildren.get(name);
+        }
+
+        private Collection<MtpObject> getChildren() {
+            return mChildren.values();
+        }
+
+        private boolean exists() {
+            return getPath().toFile().exists();
+        }
+
+        private MtpObject copy(boolean recursive) {
+            MtpObject copy = new MtpObject(mName, mId, mParent, mIsDir);
+            copy.mIsDir = mIsDir;
+            copy.mVisited = mVisited;
+            copy.mState = mState;
+            copy.mChildren = mIsDir ? new HashMap<>() : null;
+            if (recursive && mIsDir) {
+                for (MtpObject child : mChildren.values()) {
+                    MtpObject childCopy = child.copy(true);
+                    childCopy.setParent(copy);
+                    copy.addChild(childCopy);
+                }
+            }
+            return copy;
+        }
+    }
+
+    /**
+     * A class that processes generated filesystem events.
+     */
+    public static abstract class MtpNotifier {
+        /**
+         * Called when an object is added.
+         */
+        public abstract void sendObjectAdded(int id);
+
+        /**
+         * Called when an object is deleted.
+         */
+        public abstract void sendObjectRemoved(int id);
+    }
+
+    private MtpNotifier mMtpNotifier;
+
+    // A cache of MtpObjects. The objects in the cache are keyed by object id.
+    // The root object of each storage isn't in this map since they all have ObjectId 0.
+    // Instead, they can be found in mRoots keyed by storageId.
+    private HashMap<Integer, MtpObject> mObjects;
+
+    // A cache of the root MtpObject for each storage, keyed by storage id.
+    private HashMap<Integer, MtpObject> mRoots;
+
+    // Object and Storage ids are allocated incrementally and not to be reused.
+    private int mNextObjectId;
+    private int mNextStorageId;
+
+    // Special subdirectories. When set, only return objects rooted in these directories, and do
+    // not allow them to be modified.
+    private Set<String> mSubdirectories;
+
+    private volatile boolean mCheckConsistency;
+    private Thread mConsistencyThread;
+
+    public MtpStorageManager(MtpNotifier notifier, Set<String> subdirectories) {
+        mMtpNotifier = notifier;
+        mSubdirectories = subdirectories;
+        mObjects = new HashMap<>();
+        mRoots = new HashMap<>();
+        mNextObjectId = 1;
+        mNextStorageId = 1;
+
+        mCheckConsistency = false; // Set to true to turn on automatic consistency checking
+        mConsistencyThread = new Thread(() -> {
+            while (mCheckConsistency) {
+                try {
+                    Thread.sleep(15 * 1000);
+                } catch (InterruptedException e) {
+                    return;
+                }
+                if (MtpStorageManager.this.checkConsistency()) {
+                    Log.v(TAG, "Cache is consistent");
+                } else {
+                    Log.w(TAG, "Cache is not consistent");
+                }
+            }
+        });
+        if (mCheckConsistency)
+            mConsistencyThread.start();
+    }
+
+    /**
+     * Clean up resources used by the storage manager.
+     */
+    public synchronized void close() {
+        Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(),
+                mObjects.values().stream());
+
+        Iterator<MtpObject> iter = objs.iterator();
+        while (iter.hasNext()) {
+            // Close all FileObservers.
+            MtpObject obj = iter.next();
+            if (obj.getObserver() != null) {
+                obj.getObserver().stopWatching();
+                obj.setObserver(null);
+            }
+        }
+
+        // Shut down the consistency checking thread
+        if (mCheckConsistency) {
+            mCheckConsistency = false;
+            mConsistencyThread.interrupt();
+            try {
+                mConsistencyThread.join();
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Sets the special subdirectories, which are the subdirectories of root storage that queries
+     * are restricted to. Must be done before any root storages are accessed.
+     * @param subDirs Subdirectories to set, or null to reset.
+     */
+    public synchronized void setSubdirectories(Set<String> subDirs) {
+        mSubdirectories = subDirs;
+    }
+
+    /**
+     * Allocates an MTP storage id for the given volume and add it to current roots.
+     * @param volume Storage to add.
+     * @return the associated MtpStorage
+     */
+    public synchronized MtpStorage addMtpStorage(StorageVolume volume) {
+        int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1;
+        MtpObject root = new MtpObject(volume.getPath(), storageId, null, true);
+        MtpStorage storage = new MtpStorage(volume, storageId);
+        mRoots.put(storageId, root);
+        return storage;
+    }
+
+    /**
+     * Removes the given storage and all associated items from the cache.
+     * @param storage Storage to remove.
+     */
+    public synchronized void removeMtpStorage(MtpStorage storage) {
+        removeObjectFromCache(getStorageRoot(storage.getStorageId()), true, true);
+    }
+
+    /**
+     * Checks if the given object can be renamed, moved, or deleted.
+     * If there are special subdirectories, they cannot be modified.
+     * @param obj Object to check.
+     * @return Whether object can be modified.
+     */
+    private synchronized boolean isSpecialSubDir(MtpObject obj) {
+        return obj.getParent().isRoot() && mSubdirectories != null
+                && !mSubdirectories.contains(obj.getName());
+    }
+
+    /**
+     * Get the object with the specified path. Visit any necessary directories on the way.
+     * @param path Full path of the object to find.
+     * @return The desired object, or null if it cannot be found.
+     */
+    public synchronized MtpObject getByPath(String path) {
+        MtpObject obj = null;
+        for (MtpObject root : mRoots.values()) {
+            if (path.startsWith(root.getName())) {
+                obj = root;
+                path = path.substring(root.getName().length());
+            }
+        }
+        for (String name : path.split("/")) {
+            if (obj == null || !obj.isDir())
+                return null;
+            if ("".equals(name))
+                continue;
+            if (!obj.isVisited())
+                getChildren(obj);
+            obj = obj.getChild(name);
+        }
+        return obj;
+    }
+
+    /**
+     * Get the object with specified id.
+     * @param id Id of object. must not be 0 or 0xFFFFFFFF
+     * @return Object, or null if error.
+     */
+    public synchronized MtpObject getObject(int id) {
+        if (id == 0 || id == 0xFFFFFFFF) {
+            Log.w(TAG, "Can't get root storages with getObject()");
+            return null;
+        }
+        if (!mObjects.containsKey(id)) {
+            Log.w(TAG, "Id " + id + " doesn't exist");
+            return null;
+        }
+        return mObjects.get(id);
+    }
+
+    /**
+     * Get the storage with specified id.
+     * @param id Storage id.
+     * @return Object that is the root of the storage, or null if error.
+     */
+    public MtpObject getStorageRoot(int id) {
+        if (!mRoots.containsKey(id)) {
+            Log.w(TAG, "StorageId " + id + " doesn't exist");
+            return null;
+        }
+        return mRoots.get(id);
+    }
+
+    private int getNextObjectId() {
+        int ret = mNextObjectId;
+        // Treat the id as unsigned int
+        mNextObjectId = (int) ((long) mNextObjectId + 1);
+        return ret;
+    }
+
+    private int getNextStorageId() {
+        return mNextStorageId++;
+    }
+
+    /**
+     * Get all objects matching the given parent, format, and storage
+     * @param parent object id of the parent. 0 for all objects, 0xFFFFFFFF for all object in root
+     * @param format format of returned objects. 0 for any format
+     * @param storageId storage id to look in. 0xFFFFFFFF for all storages
+     * @return A stream of matched objects, or null if error
+     */
+    public synchronized Stream<MtpObject> getObjects(int parent, int format, int storageId) {
+        boolean recursive = parent == 0;
+        if (parent == 0xFFFFFFFF)
+            parent = 0;
+        if (storageId == 0xFFFFFFFF) {
+            // query all stores
+            if (parent == 0) {
+                // Get the objects of this format and parent in each store.
+                ArrayList<Stream<MtpObject>> streamList = new ArrayList<>();
+                for (MtpObject root : mRoots.values()) {
+                    streamList.add(getObjects(root, format, recursive));
+                }
+                return Stream.of(streamList).flatMap(Collection::stream).reduce(Stream::concat)
+                        .orElseGet(Stream::empty);
+            }
+        }
+        MtpObject obj = parent == 0 ? getStorageRoot(storageId) : getObject(parent);
+        if (obj == null)
+            return null;
+        return getObjects(obj, format, recursive);
+    }
+
+    private synchronized Stream<MtpObject> getObjects(MtpObject parent, int format, boolean rec) {
+        Collection<MtpObject> children = getChildren(parent);
+        if (children == null)
+            return null;
+        Stream<MtpObject> ret = Stream.of(children).flatMap(Collection::stream);
+
+        if (format != 0) {
+            ret = ret.filter(o -> o.getFormat() == format);
+        }
+        if (rec) {
+            // Get all objects recursively.
+            ArrayList<Stream<MtpObject>> streamList = new ArrayList<>();
+            streamList.add(ret);
+            for (MtpObject o : children) {
+                if (o.isDir())
+                    streamList.add(getObjects(o, format, true));
+            }
+            ret = Stream.of(streamList).filter(Objects::nonNull).flatMap(Collection::stream)
+                    .reduce(Stream::concat).orElseGet(Stream::empty);
+        }
+        return ret;
+    }
+
+    /**
+     * Return the children of the given object. If the object hasn't been visited yet, add
+     * its children to the cache and start observing it.
+     * @param object the parent object
+     * @return The collection of child objects or null if error
+     */
+    private synchronized Collection<MtpObject> getChildren(MtpObject object) {
+        if (object == null || !object.isDir()) {
+            Log.w(TAG, "Can't find children of " + (object == null ? "null" : object.getId()));
+            return null;
+        }
+        if (!object.isVisited()) {
+            Path dir = object.getPath();
+            /*
+             * If a file is added after the observer starts watching the directory, but before
+             * the contents are listed, it will generate an event that will get processed
+             * after this synchronized function returns. We handle this by ignoring object
+             * added events if an object at that path already exists.
+             */
+            if (object.getObserver() != null)
+                Log.e(TAG, "Observer is not null!");
+            object.setObserver(new MtpObjectObserver(object));
+            object.getObserver().startWatching();
+            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
+                for (Path file : stream) {
+                    addObjectToCache(object, file.getFileName().toString(),
+                            file.toFile().isDirectory());
+                }
+            } catch (IOException | DirectoryIteratorException e) {
+                Log.e(TAG, e.toString());
+                object.getObserver().stopWatching();
+                object.setObserver(null);
+                return null;
+            }
+            object.setVisited(true);
+        }
+        return object.getChildren();
+    }
+
+    /**
+     * Create a new object from the given path and add it to the cache.
+     * @param parent The parent object
+     * @param newName Path of the new object
+     * @return the new object if success, else null
+     */
+    private synchronized MtpObject addObjectToCache(MtpObject parent, String newName,
+            boolean isDir) {
+        if (!parent.isRoot() && getObject(parent.getId()) != parent)
+            // parent object has been removed
+            return null;
+        if (parent.getChild(newName) != null) {
+            // Object already exists
+            return null;
+        }
+        if (mSubdirectories != null && parent.isRoot() && !mSubdirectories.contains(newName)) {
+            // Not one of the restricted subdirectories.
+            return null;
+        }
+
+        MtpObject obj = new MtpObject(newName, getNextObjectId(), parent, isDir);
+        mObjects.put(obj.getId(), obj);
+        parent.addChild(obj);
+        return obj;
+    }
+
+    /**
+     * Remove the given path from the cache.
+     * @param removed The removed object
+     * @param removeGlobal Whether to remove the object from the global id map
+     * @param recursive Whether to also remove its children recursively.
+     * @return true if successfully removed
+     */
+    private synchronized boolean removeObjectFromCache(MtpObject removed, boolean removeGlobal,
+            boolean recursive) {
+        boolean ret = removed.isRoot()
+                || removed.getParent().mChildren.remove(removed.getName(), removed);
+        if (!ret && sDebug)
+            Log.w(TAG, "Failed to remove from parent " + removed.getPath());
+        if (removed.isRoot()) {
+            ret = mRoots.remove(removed.getId(), removed) && ret;
+        } else if (removeGlobal) {
+            ret = mObjects.remove(removed.getId(), removed) && ret;
+        }
+        if (!ret && sDebug)
+            Log.w(TAG, "Failed to remove from global cache " + removed.getPath());
+        if (removed.getObserver() != null) {
+            removed.getObserver().stopWatching();
+            removed.setObserver(null);
+        }
+        if (removed.isDir() && recursive) {
+            // Remove all descendants from cache recursively
+            Collection<MtpObject> children = new ArrayList<>(removed.getChildren());
+            for (MtpObject child : children) {
+                ret = removeObjectFromCache(child, removeGlobal, true) && ret;
+            }
+        }
+        return ret;
+    }
+
+    private synchronized void handleAddedObject(MtpObject parent, String path, boolean isDir) {
+        MtpOperation op = MtpOperation.NONE;
+        MtpObject obj = parent.getChild(path);
+        if (obj != null) {
+            MtpObjectState state = obj.getState();
+            op = obj.getOperation();
+            if (obj.isDir() != isDir && state != MtpObjectState.FROZEN_REMOVED)
+                Log.d(TAG, "Inconsistent directory info! " + obj.getPath());
+            obj.setDir(isDir);
+            switch (state) {
+                case FROZEN:
+                case FROZEN_REMOVED:
+                    obj.setState(MtpObjectState.FROZEN_ADDED);
+                    break;
+                case FROZEN_ONESHOT_ADD:
+                    obj.setState(MtpObjectState.NORMAL);
+                    break;
+                case NORMAL:
+                case FROZEN_ADDED:
+                    // This can happen when handling listed object in a new directory.
+                    return;
+                default:
+                    Log.w(TAG, "Unexpected state in add " + path + " " + state);
+            }
+            if (sDebug)
+                Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op);
+        } else {
+            obj = MtpStorageManager.this.addObjectToCache(parent, path, isDir);
+            if (obj != null) {
+                MtpStorageManager.this.mMtpNotifier.sendObjectAdded(obj.getId());
+            } else {
+                if (sDebug)
+                    Log.w(TAG, "object " + path + " already exists");
+                return;
+            }
+        }
+        if (isDir) {
+            // If this was added as part of a rename do not visit or send events.
+            if (op == MtpOperation.RENAME)
+                return;
+
+            // If it was part of a copy operation, then only add observer if it was visited before.
+            if (op == MtpOperation.COPY && !obj.isVisited())
+                return;
+
+            if (obj.getObserver() != null) {
+                Log.e(TAG, "Observer is not null!");
+                return;
+            }
+            obj.setObserver(new MtpObjectObserver(obj));
+            obj.getObserver().startWatching();
+            obj.setVisited(true);
+
+            // It's possible that objects were added to a watched directory before the watch can be
+            // created, so manually handle those.
+            try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) {
+                for (Path file : stream) {
+                    if (sDebug)
+                        Log.i(TAG, "Manually handling event for " + file.getFileName().toString());
+                    handleAddedObject(obj, file.getFileName().toString(),
+                            file.toFile().isDirectory());
+                }
+            } catch (IOException | DirectoryIteratorException e) {
+                Log.e(TAG, e.toString());
+                obj.getObserver().stopWatching();
+                obj.setObserver(null);
+            }
+        }
+    }
+
+    private synchronized void handleRemovedObject(MtpObject obj) {
+        MtpObjectState state = obj.getState();
+        MtpOperation op = obj.getOperation();
+        switch (state) {
+            case FROZEN_ADDED:
+                obj.setState(MtpObjectState.FROZEN_REMOVED);
+                break;
+            case FROZEN_ONESHOT_DEL:
+                removeObjectFromCache(obj, op != MtpOperation.RENAME, false);
+                break;
+            case FROZEN:
+                obj.setState(MtpObjectState.FROZEN_REMOVED);
+                break;
+            case NORMAL:
+                if (MtpStorageManager.this.removeObjectFromCache(obj, true, true))
+                    MtpStorageManager.this.mMtpNotifier.sendObjectRemoved(obj.getId());
+                break;
+            default:
+                // This shouldn't happen; states correspond to objects that don't exist
+                Log.e(TAG, "Got unexpected object remove for " + obj.getName());
+        }
+        if (sDebug)
+            Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op);
+    }
+
+    /**
+     * Block the caller until all events currently in the event queue have been
+     * read and processed. Used for testing purposes.
+     */
+    public void flushEvents() {
+        try {
+            // TODO make this smarter
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+
+        }
+    }
+
+    /**
+     * Dumps a representation of the cache to log.
+     */
+    public synchronized void dump() {
+        for (int key : mObjects.keySet()) {
+            MtpObject obj = mObjects.get(key);
+            Log.i(TAG, key + " | " + (obj.getParent() == null ? obj.getParent().getId() : "null")
+                    + " | " + obj.getName() + " | " + (obj.isDir() ? "dir" : "obj")
+                    + " | " + (obj.isVisited() ? "v" : "nv") + " | " + obj.getState());
+        }
+    }
+
+    /**
+     * Checks consistency of the cache. This checks whether all objects have correct links
+     * to their parent, and whether directories are missing or have extraneous objects.
+     * @return true iff cache is consistent
+     */
+    public synchronized boolean checkConsistency() {
+        Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(),
+                mObjects.values().stream());
+        Iterator<MtpObject> iter = objs.iterator();
+        boolean ret = true;
+        while (iter.hasNext()) {
+            MtpObject obj = iter.next();
+            if (!obj.exists()) {
+                Log.w(TAG, "Object doesn't exist " + obj.getPath() + " " + obj.getId());
+                ret = false;
+            }
+            if (obj.getState() != MtpObjectState.NORMAL) {
+                Log.w(TAG, "Object " + obj.getPath() + " in state " + obj.getState());
+                ret = false;
+            }
+            if (obj.getOperation() != MtpOperation.NONE) {
+                Log.w(TAG, "Object " + obj.getPath() + " in operation " + obj.getOperation());
+                ret = false;
+            }
+            if (!obj.isRoot() && mObjects.get(obj.getId()) != obj) {
+                Log.w(TAG, "Object " + obj.getPath() + " is not in map correctly");
+                ret = false;
+            }
+            if (obj.getParent() != null) {
+                if (obj.getParent().isRoot() && obj.getParent()
+                        != mRoots.get(obj.getParent().getId())) {
+                    Log.w(TAG, "Root parent is not in root mapping " + obj.getPath());
+                    ret = false;
+                }
+                if (!obj.getParent().isRoot() && obj.getParent()
+                        != mObjects.get(obj.getParent().getId())) {
+                    Log.w(TAG, "Parent is not in object mapping " + obj.getPath());
+                    ret = false;
+                }
+                if (obj.getParent().getChild(obj.getName()) != obj) {
+                    Log.w(TAG, "Child does not exist in parent " + obj.getPath());
+                    ret = false;
+                }
+            }
+            if (obj.isDir()) {
+                if (obj.isVisited() == (obj.getObserver() == null)) {
+                    Log.w(TAG, obj.getPath() + " is " + (obj.isVisited() ? "" : "not ")
+                            + " visited but observer is " + obj.getObserver());
+                    ret = false;
+                }
+                if (!obj.isVisited() && obj.getChildren().size() > 0) {
+                    Log.w(TAG, obj.getPath() + " is not visited but has children");
+                    ret = false;
+                }
+                try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) {
+                    Set<String> files = new HashSet<>();
+                    for (Path file : stream) {
+                        if (obj.isVisited() &&
+                                obj.getChild(file.getFileName().toString()) == null &&
+                                (mSubdirectories == null || !obj.isRoot() ||
+                                        mSubdirectories.contains(file.getFileName().toString()))) {
+                            Log.w(TAG, "File exists in fs but not in children " + file);
+                            ret = false;
+                        }
+                        files.add(file.toString());
+                    }
+                    for (MtpObject child : obj.getChildren()) {
+                        if (!files.contains(child.getPath().toString())) {
+                            Log.w(TAG, "File in children doesn't exist in fs " + child.getPath());
+                            ret = false;
+                        }
+                        if (child != mObjects.get(child.getId())) {
+                            Log.w(TAG, "Child is not in object map " + child.getPath());
+                            ret = false;
+                        }
+                    }
+                } catch (IOException | DirectoryIteratorException e) {
+                    Log.w(TAG, e.toString());
+                    ret = false;
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Informs MtpStorageManager that an object with the given path is about to be added.
+     * @param parent The parent object of the object to be added.
+     * @param name Filename of object to add.
+     * @return Object id of the added object, or -1 if it cannot be added.
+     */
+    public synchronized int beginSendObject(MtpObject parent, String name, int format) {
+        if (sDebug)
+            Log.v(TAG, "beginSendObject " + name);
+        if (!parent.isDir())
+            return -1;
+        if (parent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name))
+            return -1;
+        getChildren(parent); // Ensure parent is visited
+        MtpObject obj  = addObjectToCache(parent, name, format == MtpConstants.FORMAT_ASSOCIATION);
+        if (obj == null)
+            return -1;
+        obj.setState(MtpObjectState.FROZEN);
+        obj.setOperation(MtpOperation.ADD);
+        return obj.getId();
+    }
+
+    /**
+     * Clean up the object state after a sendObject operation.
+     * @param obj The object, returned from beginAddObject().
+     * @param succeeded Whether the file was successfully created.
+     * @return Whether cache state was successfully cleaned up.
+     */
+    public synchronized boolean endSendObject(MtpObject obj, boolean succeeded) {
+        if (sDebug)
+            Log.v(TAG, "endSendObject " + succeeded);
+        return generalEndAddObject(obj, succeeded, true);
+    }
+
+    /**
+     * Informs MtpStorageManager that the given object is about to be renamed.
+     * If this returns true, it must be followed with an endRenameObject()
+     * @param obj Object to be renamed.
+     * @param newName New name of the object.
+     * @return Whether renaming is allowed.
+     */
+    public synchronized boolean beginRenameObject(MtpObject obj, String newName) {
+        if (sDebug)
+            Log.v(TAG, "beginRenameObject " + obj.getName() + " " + newName);
+        if (obj.isRoot())
+            return false;
+        if (isSpecialSubDir(obj))
+            return false;
+        if (obj.getParent().getChild(newName) != null)
+            // Object already exists in parent with that name.
+            return false;
+
+        MtpObject oldObj = obj.copy(false);
+        obj.setName(newName);
+        obj.getParent().addChild(obj);
+        oldObj.getParent().addChild(oldObj);
+        return generalBeginRenameObject(oldObj, obj);
+    }
+
+    /**
+     * Cleans up cache state after a rename operation and sends any events that were missed.
+     * @param obj The object being renamed, the same one that was passed in beginRenameObject().
+     * @param oldName The previous name of the object.
+     * @param success Whether the rename operation succeeded.
+     * @return Whether state was successfully cleaned up.
+     */
+    public synchronized boolean endRenameObject(MtpObject obj, String oldName, boolean success) {
+        if (sDebug)
+            Log.v(TAG, "endRenameObject " + success);
+        MtpObject parent = obj.getParent();
+        MtpObject oldObj = parent.getChild(oldName);
+        if (!success) {
+            // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+            // Switch the objects, except for their name and state.
+            MtpObject temp = oldObj;
+            MtpObjectState oldState = oldObj.getState();
+            temp.setName(obj.getName());
+            temp.setState(obj.getState());
+            oldObj = obj;
+            oldObj.setName(oldName);
+            oldObj.setState(oldState);
+            obj = temp;
+            parent.addChild(obj);
+            parent.addChild(oldObj);
+        }
+        return generalEndRenameObject(oldObj, obj, success);
+    }
+
+    /**
+     * Informs MtpStorageManager that the given object is about to be deleted by the initiator,
+     * so don't send an event.
+     * @param obj Object to be deleted.
+     * @return Whether cache deletion is allowed.
+     */
+    public synchronized boolean beginRemoveObject(MtpObject obj) {
+        if (sDebug)
+            Log.v(TAG, "beginRemoveObject " + obj.getName());
+        return !obj.isRoot() && !isSpecialSubDir(obj)
+                && generalBeginRemoveObject(obj, MtpOperation.DELETE);
+    }
+
+    /**
+     * Clean up cache state after a delete operation and send any events that were missed.
+     * @param obj Object to be deleted, same one passed in beginRemoveObject().
+     * @param success Whether operation was completed successfully.
+     * @return Whether cache state is correct.
+     */
+    public synchronized boolean endRemoveObject(MtpObject obj, boolean success) {
+        if (sDebug)
+            Log.v(TAG, "endRemoveObject " + success);
+        boolean ret = true;
+        if (obj.isDir()) {
+            for (MtpObject child : new ArrayList<>(obj.getChildren()))
+                if (child.getOperation() == MtpOperation.DELETE)
+                    ret = endRemoveObject(child, success) && ret;
+        }
+        return generalEndRemoveObject(obj, success, true) && ret;
+    }
+
+    /**
+     * Informs MtpStorageManager that the given object is about to be moved to a new parent.
+     * @param obj Object to be moved.
+     * @param newParent The new parent object.
+     * @return Whether the move is allowed.
+     */
+    public synchronized boolean beginMoveObject(MtpObject obj, MtpObject newParent) {
+        if (sDebug)
+            Log.v(TAG, "beginMoveObject " + newParent.getPath());
+        if (obj.isRoot())
+            return false;
+        if (isSpecialSubDir(obj))
+            return false;
+        getChildren(newParent); // Ensure parent is visited
+        if (newParent.getChild(obj.getName()) != null)
+            // Object already exists in parent with that name.
+            return false;
+        if (obj.getStorageId() != newParent.getStorageId()) {
+            /*
+             * The move is occurring across storages. The observers will not remain functional
+             * after the move, and the move will not be atomic. We have to copy the file tree
+             * to the destination and recreate the observers once copy is complete.
+             */
+            MtpObject newObj = obj.copy(true);
+            newObj.setParent(newParent);
+            newParent.addChild(newObj);
+            return generalBeginRemoveObject(obj, MtpOperation.RENAME)
+                    && generalBeginCopyObject(newObj, false);
+        }
+        // Move obj to new parent, create a dummy object in the old parent.
+        MtpObject oldObj = obj.copy(false);
+        obj.setParent(newParent);
+        oldObj.getParent().addChild(oldObj);
+        obj.getParent().addChild(obj);
+        return generalBeginRenameObject(oldObj, obj);
+    }
+
+    /**
+     * Clean up cache state after a move operation and send any events that were missed.
+     * @param oldParent The old parent object.
+     * @param newParent The new parent object.
+     * @param name The name of the object being moved.
+     * @param success Whether operation was completed successfully.
+     * @return Whether cache state is correct.
+     */
+    public synchronized boolean endMoveObject(MtpObject oldParent, MtpObject newParent, String name,
+            boolean success) {
+        if (sDebug)
+            Log.v(TAG, "endMoveObject " + success);
+        MtpObject oldObj = oldParent.getChild(name);
+        MtpObject newObj = newParent.getChild(name);
+        if (oldObj == null || newObj == null)
+            return false;
+        if (oldParent.getStorageId() != newObj.getStorageId()) {
+            boolean ret = endRemoveObject(oldObj, success);
+            return generalEndCopyObject(newObj, success, true) && ret;
+        }
+        if (!success) {
+            // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+            // Switch the objects, except for their parent and state.
+            MtpObject temp = oldObj;
+            MtpObjectState oldState = oldObj.getState();
+            temp.setParent(newObj.getParent());
+            temp.setState(newObj.getState());
+            oldObj = newObj;
+            oldObj.setParent(oldParent);
+            oldObj.setState(oldState);
+            newObj = temp;
+            newObj.getParent().addChild(newObj);
+            oldParent.addChild(oldObj);
+        }
+        return generalEndRenameObject(oldObj, newObj, success);
+    }
+
+    /**
+     * Informs MtpStorageManager that the given object is about to be copied recursively.
+     * @param object Object to be copied
+     * @param newParent New parent for the object.
+     * @return The object id for the new copy, or -1 if error.
+     */
+    public synchronized int beginCopyObject(MtpObject object, MtpObject newParent) {
+        if (sDebug)
+            Log.v(TAG, "beginCopyObject " + object.getName() + " to " + newParent.getPath());
+        String name = object.getName();
+        if (!newParent.isDir())
+            return -1;
+        if (newParent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name))
+            return -1;
+        getChildren(newParent); // Ensure parent is visited
+        if (newParent.getChild(name) != null)
+            return -1;
+        MtpObject newObj  = object.copy(object.isDir());
+        newParent.addChild(newObj);
+        newObj.setParent(newParent);
+        if (!generalBeginCopyObject(newObj, true))
+            return -1;
+        return newObj.getId();
+    }
+
+    /**
+     * Cleans up cache state after a copy operation.
+     * @param object Object that was copied.
+     * @param success Whether the operation was successful.
+     * @return Whether cache state is consistent.
+     */
+    public synchronized boolean endCopyObject(MtpObject object, boolean success) {
+        if (sDebug)
+            Log.v(TAG, "endCopyObject " + object.getName() + " " + success);
+        return generalEndCopyObject(object, success, false);
+    }
+
+    private synchronized boolean generalEndAddObject(MtpObject obj, boolean succeeded,
+            boolean removeGlobal) {
+        switch (obj.getState()) {
+            case FROZEN:
+                // Object was never created.
+                if (succeeded) {
+                    // The operation was successful so the event must still be in the queue.
+                    obj.setState(MtpObjectState.FROZEN_ONESHOT_ADD);
+                } else {
+                    // The operation failed and never created the file.
+                    if (!removeObjectFromCache(obj, removeGlobal, false)) {
+                        return false;
+                    }
+                }
+                break;
+            case FROZEN_ADDED:
+                obj.setState(MtpObjectState.NORMAL);
+                if (!succeeded) {
+                    MtpObject parent = obj.getParent();
+                    // The operation failed but some other process created the file. Send an event.
+                    if (!removeObjectFromCache(obj, removeGlobal, false))
+                        return false;
+                    handleAddedObject(parent, obj.getName(), obj.isDir());
+                }
+                // else: The operation successfully created the object.
+                break;
+            case FROZEN_REMOVED:
+                if (!removeObjectFromCache(obj, removeGlobal, false))
+                    return false;
+                if (succeeded) {
+                    // Some other process deleted the object. Send an event.
+                    mMtpNotifier.sendObjectRemoved(obj.getId());
+                }
+                // else: Mtp deleted the object as part of cleanup. Don't send an event.
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private synchronized boolean generalEndRemoveObject(MtpObject obj, boolean success,
+            boolean removeGlobal) {
+        switch (obj.getState()) {
+            case FROZEN:
+                if (success) {
+                    // Object was deleted successfully, and event is still in the queue.
+                    obj.setState(MtpObjectState.FROZEN_ONESHOT_DEL);
+                } else {
+                    // Object was not deleted.
+                    obj.setState(MtpObjectState.NORMAL);
+                }
+                break;
+            case FROZEN_ADDED:
+                // Object was deleted, and then readded.
+                obj.setState(MtpObjectState.NORMAL);
+                if (success) {
+                    // Some other process readded the object.
+                    MtpObject parent = obj.getParent();
+                    if (!removeObjectFromCache(obj, removeGlobal, false))
+                        return false;
+                    handleAddedObject(parent, obj.getName(), obj.isDir());
+                }
+                // else : Object still exists after failure.
+                break;
+            case FROZEN_REMOVED:
+                if (!removeObjectFromCache(obj, removeGlobal, false))
+                    return false;
+                if (!success) {
+                    // Some other process deleted the object.
+                    mMtpNotifier.sendObjectRemoved(obj.getId());
+                }
+                // else : This process deleted the object as part of the operation.
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private synchronized boolean generalBeginRenameObject(MtpObject fromObj, MtpObject toObj) {
+        fromObj.setState(MtpObjectState.FROZEN);
+        toObj.setState(MtpObjectState.FROZEN);
+        fromObj.setOperation(MtpOperation.RENAME);
+        toObj.setOperation(MtpOperation.RENAME);
+        return true;
+    }
+
+    private synchronized boolean generalEndRenameObject(MtpObject fromObj, MtpObject toObj,
+            boolean success) {
+        boolean ret = generalEndRemoveObject(fromObj, success, !success);
+        return generalEndAddObject(toObj, success, success) && ret;
+    }
+
+    private synchronized boolean generalBeginRemoveObject(MtpObject obj, MtpOperation op) {
+        obj.setState(MtpObjectState.FROZEN);
+        obj.setOperation(op);
+        if (obj.isDir()) {
+            for (MtpObject child : obj.getChildren())
+                generalBeginRemoveObject(child, op);
+        }
+        return true;
+    }
+
+    private synchronized boolean generalBeginCopyObject(MtpObject obj, boolean newId) {
+        obj.setState(MtpObjectState.FROZEN);
+        obj.setOperation(MtpOperation.COPY);
+        if (newId) {
+            obj.setId(getNextObjectId());
+            mObjects.put(obj.getId(), obj);
+        }
+        if (obj.isDir())
+            for (MtpObject child : obj.getChildren())
+                if (!generalBeginCopyObject(child, newId))
+                    return false;
+        return true;
+    }
+
+    private synchronized boolean generalEndCopyObject(MtpObject obj, boolean success, boolean addGlobal) {
+        if (success && addGlobal)
+            mObjects.put(obj.getId(), obj);
+        boolean ret = true;
+        if (obj.isDir()) {
+            for (MtpObject child : new ArrayList<>(obj.getChildren())) {
+                if (child.getOperation() == MtpOperation.COPY)
+                    ret = generalEndCopyObject(child, success, addGlobal) && ret;
+            }
+        }
+        ret = generalEndAddObject(obj, success, success || !addGlobal) && ret;
+        return ret;
+    }
+}
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 4e8c72b..23ef84f6 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -19,7 +19,7 @@
 
 #include "android_media_Utils.h"
 #include "mtp.h"
-#include "MtpDatabase.h"
+#include "IMtpDatabase.h"
 #include "MtpDataPacket.h"
 #include "MtpObjectInfo.h"
 #include "MtpProperty.h"
@@ -55,7 +55,7 @@
 
 static jmethodID method_beginSendObject;
 static jmethodID method_endSendObject;
-static jmethodID method_doScanDirectory;
+static jmethodID method_rescanFile;
 static jmethodID method_getObjectList;
 static jmethodID method_getNumObjects;
 static jmethodID method_getSupportedPlaybackFormats;
@@ -68,35 +68,34 @@
 static jmethodID method_getObjectPropertyList;
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
-static jmethodID method_deleteFile;
-static jmethodID method_moveObject;
+static jmethodID method_beginDeleteObject;
+static jmethodID method_endDeleteObject;
+static jmethodID method_beginMoveObject;
+static jmethodID method_endMoveObject;
+static jmethodID method_beginCopyObject;
+static jmethodID method_endCopyObject;
 static jmethodID method_getObjectReferences;
 static jmethodID method_setObjectReferences;
-static jmethodID method_sessionStarted;
-static jmethodID method_sessionEnded;
 
 static jfieldID field_context;
-static jfieldID field_batteryLevel;
-static jfieldID field_batteryScale;
-static jfieldID field_deviceType;
 
-// MtpPropertyList fields
-static jfieldID field_mCount;
-static jfieldID field_mResult;
-static jfieldID field_mObjectHandles;
-static jfieldID field_mPropertyCodes;
-static jfieldID field_mDataTypes;
-static jfieldID field_mLongValues;
-static jfieldID field_mStringValues;
+// MtpPropertyList methods
+static jmethodID method_getCode;
+static jmethodID method_getCount;
+static jmethodID method_getObjectHandles;
+static jmethodID method_getPropertyCodes;
+static jmethodID method_getDataTypes;
+static jmethodID method_getLongValues;
+static jmethodID method_getStringValues;
 
 
-MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
-    return (MtpDatabase *)env->GetLongField(database, field_context);
+IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
+    return (IMtpDatabase *)env->GetLongField(database, field_context);
 }
 
 // ----------------------------------------------------------------------------
 
-class MyMtpDatabase : public MtpDatabase {
+class MtpDatabase : public IMtpDatabase {
 private:
     jobject         mDatabase;
     jintArray       mIntBuffer;
@@ -104,23 +103,20 @@
     jcharArray      mStringBuffer;
 
 public:
-                                    MyMtpDatabase(JNIEnv *env, jobject client);
-    virtual                         ~MyMtpDatabase();
+                                    MtpDatabase(JNIEnv *env, jobject client);
+    virtual                         ~MtpDatabase();
     void                            cleanup(JNIEnv *env);
 
     virtual MtpObjectHandle         beginSendObject(const char* path,
                                             MtpObjectFormat format,
                                             MtpObjectHandle parent,
-                                            MtpStorageID storage,
-                                            uint64_t size,
-                                            time_t modified);
+                                            MtpStorageID storage);
 
-    virtual void                    endSendObject(const char* path,
+    virtual void                    endSendObject(MtpObjectHandle handle, bool succeeded);
+
+    virtual void                    rescanFile(const char* path,
                                             MtpObjectHandle handle,
-                                            MtpObjectFormat format,
-                                            bool succeeded);
-
-    virtual void                    doScanDirectory(const char* path);
+                                            MtpObjectFormat format);
 
     virtual MtpObjectHandleList*    getObjectList(MtpStorageID storageID,
                                     MtpObjectFormat format,
@@ -167,7 +163,8 @@
                                             MtpString& outFilePath,
                                             int64_t& outFileLength,
                                             MtpObjectFormat& outFormat);
-    virtual MtpResponseCode         deleteFile(MtpObjectHandle handle);
+    virtual MtpResponseCode         beginDeleteObject(MtpObjectHandle handle);
+    virtual void                    endDeleteObject(MtpObjectHandle handle, bool succeeded);
 
     bool                            getObjectPropertyInfo(MtpObjectProperty property, int& type);
     bool                            getDevicePropertyInfo(MtpDeviceProperty property, int& type);
@@ -182,12 +179,17 @@
 
     virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property);
 
-    virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
-                                            MtpStorageID newStorage, MtpString& newPath);
+    virtual MtpResponseCode         beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+                                            MtpStorageID newStorage);
 
-    virtual void                    sessionStarted();
+    virtual void                    endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent,
+                                            MtpStorageID oldStorage, MtpStorageID newStorage,
+                                             MtpObjectHandle handle, bool succeeded);
 
-    virtual void                    sessionEnded();
+    virtual MtpResponseCode         beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+                                            MtpStorageID newStorage);
+    virtual void                    endCopyObject(MtpObjectHandle handle, bool succeeded);
+
 };
 
 // ----------------------------------------------------------------------------
@@ -202,7 +204,7 @@
 
 // ----------------------------------------------------------------------------
 
-MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
+MtpDatabase::MtpDatabase(JNIEnv *env, jobject client)
     :   mDatabase(env->NewGlobalRef(client)),
         mIntBuffer(NULL),
         mLongBuffer(NULL),
@@ -228,27 +230,24 @@
     mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
 }
 
-void MyMtpDatabase::cleanup(JNIEnv *env) {
+void MtpDatabase::cleanup(JNIEnv *env) {
     env->DeleteGlobalRef(mDatabase);
     env->DeleteGlobalRef(mIntBuffer);
     env->DeleteGlobalRef(mLongBuffer);
     env->DeleteGlobalRef(mStringBuffer);
 }
 
-MyMtpDatabase::~MyMtpDatabase() {
+MtpDatabase::~MtpDatabase() {
 }
 
-MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path,
+MtpObjectHandle MtpDatabase::beginSendObject(const char* path,
                                                MtpObjectFormat format,
                                                MtpObjectHandle parent,
-                                               MtpStorageID storage,
-                                               uint64_t size,
-                                               time_t modified) {
+                                               MtpStorageID storage) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jstring pathStr = env->NewStringUTF(path);
     MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject,
-            pathStr, (jint)format, (jint)parent, (jint)storage,
-            (jlong)size, (jlong)modified);
+            pathStr, (jint)format, (jint)parent, (jint)storage);
 
     if (pathStr)
         env->DeleteLocalRef(pathStr);
@@ -256,29 +255,26 @@
     return result;
 }
 
-void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle,
-                                  MtpObjectFormat format, bool succeeded) {
+void MtpDatabase::endSendObject(MtpObjectHandle handle, bool succeeded) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_endSendObject, (jint)handle, (jboolean)succeeded);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void MtpDatabase::rescanFile(const char* path, MtpObjectHandle handle,
+                                  MtpObjectFormat format) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jstring pathStr = env->NewStringUTF(path);
-    env->CallVoidMethod(mDatabase, method_endSendObject, pathStr,
-                        (jint)handle, (jint)format, (jboolean)succeeded);
+    env->CallVoidMethod(mDatabase, method_rescanFile, pathStr,
+                        (jint)handle, (jint)format);
 
     if (pathStr)
         env->DeleteLocalRef(pathStr);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
 }
 
-void MyMtpDatabase::doScanDirectory(const char* path) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jstring pathStr = env->NewStringUTF(path);
-    env->CallVoidMethod(mDatabase, method_doScanDirectory, pathStr);
-
-    if (pathStr)
-        env->DeleteLocalRef(pathStr);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID,
+MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID,
                                                   MtpObjectFormat format,
                                                   MtpObjectHandle parent) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -298,7 +294,7 @@
     return list;
 }
 
-int MyMtpDatabase::getNumObjects(MtpStorageID storageID,
+int MtpDatabase::getNumObjects(MtpStorageID storageID,
                                  MtpObjectFormat format,
                                  MtpObjectHandle parent) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -309,7 +305,7 @@
     return result;
 }
 
-MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() {
+MtpObjectFormatList* MtpDatabase::getSupportedPlaybackFormats() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
             method_getSupportedPlaybackFormats);
@@ -327,7 +323,7 @@
     return list;
 }
 
-MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() {
+MtpObjectFormatList* MtpDatabase::getSupportedCaptureFormats() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
             method_getSupportedCaptureFormats);
@@ -345,7 +341,7 @@
     return list;
 }
 
-MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
+MtpObjectPropertyList* MtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
             method_getSupportedObjectProperties, (jint)format);
@@ -363,7 +359,7 @@
     return list;
 }
 
-MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() {
+MtpDevicePropertyList* MtpDatabase::getSupportedDeviceProperties() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
             method_getSupportedDeviceProperties);
@@ -381,7 +377,7 @@
     return list;
 }
 
-MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
                                                       MtpObjectProperty property,
                                                       MtpDataPacket& packet) {
     static_assert(sizeof(jint) >= sizeof(MtpObjectHandle),
@@ -397,42 +393,26 @@
             static_cast<jint>(property),
             0,
             0);
-    MtpResponseCode result = env->GetIntField(list, field_mResult);
-    int count = env->GetIntField(list, field_mCount);
-    if (result == MTP_RESPONSE_OK && count != 1)
+    MtpResponseCode result = env->CallIntMethod(list, method_getCode);
+    jint count = env->CallIntMethod(list, method_getCount);
+    if (count != 1)
         result = MTP_RESPONSE_GENERAL_ERROR;
 
     if (result == MTP_RESPONSE_OK) {
-        jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
-        jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
-        jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
-        jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
-        jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+        jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles);
+        jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes);
+        jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes);
+        jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues);
+        jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues);
 
         jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
         jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
         jint* dataTypes = env->GetIntArrayElements(dataTypesArray, 0);
-        jlong* longValues = (longValuesArray ? env->GetLongArrayElements(longValuesArray, 0) : NULL);
+        jlong* longValues = env->GetLongArrayElements(longValuesArray, 0);
 
         int type = dataTypes[0];
         jlong longValue = (longValues ? longValues[0] : 0);
 
-        // special case date properties, which are strings to MTP
-        // but stored internally as a uint64
-        if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
-            char    date[20];
-            formatDateTime(longValue, date, sizeof(date));
-            packet.putString(date);
-            goto out;
-        }
-        // release date is stored internally as just the year
-        if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) {
-            char    date[20];
-            snprintf(date, sizeof(date), "%04" PRId64 "0101T000000", longValue);
-            packet.putString(date);
-            goto out;
-        }
-
         switch (type) {
             case MTP_TYPE_INT8:
                 packet.putInt8(longValue);
@@ -481,20 +461,16 @@
                 ALOGE("unsupported type in getObjectPropertyValue\n");
                 result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
         }
-out:
         env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
         env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
         env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
-        if (longValues)
-            env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+        env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
 
         env->DeleteLocalRef(objectHandlesArray);
         env->DeleteLocalRef(propertyCodesArray);
         env->DeleteLocalRef(dataTypesArray);
-        if (longValuesArray)
-            env->DeleteLocalRef(longValuesArray);
-        if (stringValuesArray)
-            env->DeleteLocalRef(stringValuesArray);
+        env->DeleteLocalRef(longValuesArray);
+        env->DeleteLocalRef(stringValuesArray);
     }
 
     env->DeleteLocalRef(list);
@@ -559,7 +535,7 @@
     return true;
 }
 
-MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
                                                       MtpObjectProperty property,
                                                       MtpDataPacket& packet) {
     int         type;
@@ -590,80 +566,73 @@
     return result;
 }
 
-MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
+MtpResponseCode MtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
                                                       MtpDataPacket& packet) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
+    int type;
 
-    if (property == MTP_DEVICE_PROPERTY_BATTERY_LEVEL) {
-        // special case - implemented here instead of Java
-        packet.putUInt8((uint8_t)env->GetIntField(mDatabase, field_batteryLevel));
-        return MTP_RESPONSE_OK;
-    } else {
-        int type;
+    if (!getDevicePropertyInfo(property, type))
+        return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
 
-        if (!getDevicePropertyInfo(property, type))
-            return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
-
-        jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
-                    (jint)property, mLongBuffer, mStringBuffer);
-        if (result != MTP_RESPONSE_OK) {
-            checkAndClearExceptionFromCallback(env, __FUNCTION__);
-            return result;
-        }
-
-        jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
-        jlong longValue = longValues[0];
-        env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
-
-        switch (type) {
-            case MTP_TYPE_INT8:
-                packet.putInt8(longValue);
-                break;
-            case MTP_TYPE_UINT8:
-                packet.putUInt8(longValue);
-                break;
-            case MTP_TYPE_INT16:
-                packet.putInt16(longValue);
-                break;
-            case MTP_TYPE_UINT16:
-                packet.putUInt16(longValue);
-                break;
-            case MTP_TYPE_INT32:
-                packet.putInt32(longValue);
-                break;
-            case MTP_TYPE_UINT32:
-                packet.putUInt32(longValue);
-                break;
-            case MTP_TYPE_INT64:
-                packet.putInt64(longValue);
-                break;
-            case MTP_TYPE_UINT64:
-                packet.putUInt64(longValue);
-                break;
-            case MTP_TYPE_INT128:
-                packet.putInt128(longValue);
-                break;
-            case MTP_TYPE_UINT128:
-                packet.putInt128(longValue);
-                break;
-            case MTP_TYPE_STR:
-            {
-                jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
-                packet.putString(str);
-                env->ReleaseCharArrayElements(mStringBuffer, str, 0);
-                break;
-             }
-            default:
-                ALOGE("unsupported type in getDevicePropertyValue\n");
-                return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
-        }
-
+    jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+                (jint)property, mLongBuffer, mStringBuffer);
+    if (result != MTP_RESPONSE_OK) {
         checkAndClearExceptionFromCallback(env, __FUNCTION__);
-        return MTP_RESPONSE_OK;
+        return result;
     }
+
+    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+    jlong longValue = longValues[0];
+    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+
+    switch (type) {
+        case MTP_TYPE_INT8:
+            packet.putInt8(longValue);
+            break;
+        case MTP_TYPE_UINT8:
+            packet.putUInt8(longValue);
+            break;
+        case MTP_TYPE_INT16:
+            packet.putInt16(longValue);
+            break;
+        case MTP_TYPE_UINT16:
+            packet.putUInt16(longValue);
+            break;
+        case MTP_TYPE_INT32:
+            packet.putInt32(longValue);
+            break;
+        case MTP_TYPE_UINT32:
+            packet.putUInt32(longValue);
+            break;
+        case MTP_TYPE_INT64:
+            packet.putInt64(longValue);
+            break;
+        case MTP_TYPE_UINT64:
+            packet.putUInt64(longValue);
+            break;
+        case MTP_TYPE_INT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_UINT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_STR:
+        {
+            jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+            packet.putString(str);
+            env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+            break;
+        }
+        default:
+            ALOGE("unsupported type in getDevicePropertyValue\n");
+            return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+    }
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return MTP_RESPONSE_OK;
 }
 
-MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
+MtpResponseCode MtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
                                                       MtpDataPacket& packet) {
     int         type;
 
@@ -693,11 +662,11 @@
     return result;
 }
 
-MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) {
+MtpResponseCode MtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) {
     return -1;
 }
 
-MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
                                                      uint32_t format, uint32_t property,
                                                      int groupCode, int depth,
                                                      MtpDataPacket& packet) {
@@ -715,16 +684,16 @@
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     if (!list)
         return MTP_RESPONSE_GENERAL_ERROR;
-    int count = env->GetIntField(list, field_mCount);
-    MtpResponseCode result = env->GetIntField(list, field_mResult);
+    int count = env->CallIntMethod(list, method_getCount);
+    MtpResponseCode result = env->CallIntMethod(list, method_getCode);
 
     packet.putUInt32(count);
     if (count > 0) {
-        jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
-        jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
-        jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
-        jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
-        jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+        jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles);
+        jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes);
+        jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes);
+        jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues);
+        jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues);
 
         jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
         jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
@@ -781,7 +750,7 @@
                     break;
                 }
                 default:
-                    ALOGE("bad or unsupported data type in MyMtpDatabase::getObjectPropertyList");
+                    ALOGE("bad or unsupported data type in MtpDatabase::getObjectPropertyList");
                     break;
             }
         }
@@ -789,16 +758,13 @@
         env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
         env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
         env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
-        if (longValues)
-            env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+        env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
 
         env->DeleteLocalRef(objectHandlesArray);
         env->DeleteLocalRef(propertyCodesArray);
         env->DeleteLocalRef(dataTypesArray);
-        if (longValuesArray)
-            env->DeleteLocalRef(longValuesArray);
-        if (stringValuesArray)
-            env->DeleteLocalRef(stringValuesArray);
+        env->DeleteLocalRef(longValuesArray);
+        env->DeleteLocalRef(stringValuesArray);
     }
 
     env->DeleteLocalRef(list);
@@ -822,7 +788,7 @@
     return exif_get_long(e->data, o);
 }
 
-MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle,
                                              MtpObjectInfo& info) {
     MtpString       path;
     int64_t         length;
@@ -914,7 +880,7 @@
     return MTP_RESPONSE_OK;
 }
 
-void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
+void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
     MtpString path;
     int64_t length;
     MtpObjectFormat format;
@@ -979,7 +945,7 @@
     return result;
 }
 
-MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectFilePath(MtpObjectHandle handle,
                                                  MtpString& outFilePath,
                                                  int64_t& outFileLength,
                                                  MtpObjectFormat& outFormat) {
@@ -1005,26 +971,60 @@
     return result;
 }
 
-MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
+MtpResponseCode MtpDatabase::beginDeleteObject(MtpObjectHandle handle) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    MtpResponseCode result = env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle);
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginDeleteObject, (jint)handle);
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return result;
 }
 
-MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
-        MtpStorageID newStorage, MtpString &newPath) {
+void MtpDatabase::endDeleteObject(MtpObjectHandle handle, bool succeeded) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jstring stringValue = env->NewStringUTF((const char *) newPath);
-    MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
-                (jint)handle, (jint)newParent, (jint) newStorage, stringValue);
+    env->CallVoidMethod(mDatabase, method_endDeleteObject, (jint)handle, (jboolean) succeeded);
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    env->DeleteLocalRef(stringValue);
+}
+
+MtpResponseCode MtpDatabase::beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+        MtpStorageID newStorage) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginMoveObject,
+                (jint)handle, (jint)newParent, (jint) newStorage);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return result;
 }
 
+void MtpDatabase::endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent,
+                                            MtpStorageID oldStorage, MtpStorageID newStorage,
+                                             MtpObjectHandle handle, bool succeeded) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_endMoveObject,
+                (jint)oldParent, (jint) newParent, (jint) oldStorage, (jint) newStorage,
+                (jint) handle, (jboolean) succeeded);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+MtpResponseCode MtpDatabase::beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+        MtpStorageID newStorage) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginCopyObject,
+                (jint)handle, (jint)newParent, (jint) newStorage);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+void MtpDatabase::endCopyObject(MtpObjectHandle handle, bool succeeded) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_endCopyObject, (jint)handle, (jboolean)succeeded);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+
 struct PropertyTableEntry {
     MtpObjectProperty   property;
     int                 type;
@@ -1066,7 +1066,7 @@
     {   MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,      MTP_TYPE_UINT32 },
 };
 
-bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
+bool MtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
     int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
     const PropertyTableEntry* entry = kObjectPropertyTable;
     for (int i = 0; i < count; i++, entry++) {
@@ -1078,7 +1078,7 @@
     return false;
 }
 
-bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
+bool MtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
     int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]);
     const PropertyTableEntry* entry = kDevicePropertyTable;
     for (int i = 0; i < count; i++, entry++) {
@@ -1090,7 +1090,7 @@
     return false;
 }
 
-MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) {
+MtpObjectHandleList* MtpDatabase::getObjectReferences(MtpObjectHandle handle) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectReferences,
                 (jint)handle);
@@ -1108,7 +1108,7 @@
     return list;
 }
 
-MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::setObjectReferences(MtpObjectHandle handle,
                                                    MtpObjectHandleList* references) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     int count = references->size();
@@ -1129,7 +1129,7 @@
     return result;
 }
 
-MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
+MtpProperty* MtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
                                                   MtpObjectFormat format) {
     static const int channelEnum[] = {
                                         1,  // mono
@@ -1210,67 +1210,65 @@
     return result;
 }
 
-MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
+MtpProperty* MtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     MtpProperty* result = NULL;
     bool writable = false;
 
-    switch (property) {
-        case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
-        case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
-            writable = true;
-            // fall through
-        case MTP_DEVICE_PROPERTY_IMAGE_SIZE: {
-            result = new MtpProperty(property, MTP_TYPE_STR, writable);
-
-            // get current value
-            jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty,
-                        (jint)property, mLongBuffer, mStringBuffer);
-            if (ret == MTP_RESPONSE_OK) {
+    // get current value
+    jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+        (jint)property, mLongBuffer, mStringBuffer);
+    if (ret == MTP_RESPONSE_OK) {
+        switch (property) {
+            case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+            case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+                writable = true;
+                // fall through
+            case MTP_DEVICE_PROPERTY_IMAGE_SIZE:
+            {
+                result = new MtpProperty(property, MTP_TYPE_STR, writable);
                 jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
                 result->setCurrentValue(str);
                 // for read-only properties it is safe to assume current value is default value
                 if (!writable)
                     result->setDefaultValue(str);
                 env->ReleaseCharArrayElements(mStringBuffer, str, 0);
-            } else {
-                ALOGE("unable to read device property, response: %04X", ret);
+                break;
             }
-            break;
+            case MTP_DEVICE_PROPERTY_BATTERY_LEVEL:
+            {
+                result = new MtpProperty(property, MTP_TYPE_UINT8);
+                jlong* arr = env->GetLongArrayElements(mLongBuffer, 0);
+                result->setFormRange(0, arr[1], 1);
+                result->mCurrentValue.u.u8 = (uint8_t) arr[0];
+                env->ReleaseLongArrayElements(mLongBuffer, arr, 0);
+                break;
+            }
+            case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+            {
+                jlong* arr = env->GetLongArrayElements(mLongBuffer, 0);
+                result = new MtpProperty(property, MTP_TYPE_UINT32);
+                result->mCurrentValue.u.u32 = (uint32_t) arr[0];
+                env->ReleaseLongArrayElements(mLongBuffer, arr, 0);
+                break;
+            }
+            default:
+                ALOGE("Unrecognized property %x", property);
         }
-        case MTP_DEVICE_PROPERTY_BATTERY_LEVEL:
-            result = new MtpProperty(property, MTP_TYPE_UINT8);
-            result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
-            result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
-            break;
-        case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
-            result = new MtpProperty(property, MTP_TYPE_UINT32);
-            result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType);
-            break;
+    } else {
+        ALOGE("unable to read device property, response: %04X", ret);
     }
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return result;
 }
 
-void MyMtpDatabase::sessionStarted() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->CallVoidMethod(mDatabase, method_sessionStarted);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void MyMtpDatabase::sessionEnded() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->CallVoidMethod(mDatabase, method_sessionEnded);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
 // ----------------------------------------------------------------------------
 
 static void
 android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
 {
-    MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
+    MtpDatabase* database = new MtpDatabase(env, thiz);
     env->SetLongField(thiz, field_context, (jlong)database);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
 }
@@ -1278,7 +1276,7 @@
 static void
 android_mtp_MtpDatabase_finalize(JNIEnv *env, jobject thiz)
 {
-    MyMtpDatabase* database = (MyMtpDatabase *)env->GetLongField(thiz, field_context);
+    MtpDatabase* database = (MtpDatabase *)env->GetLongField(thiz, field_context);
     database->cleanup(env);
     delete database;
     env->SetLongField(thiz, field_context, 0);
@@ -1305,6 +1303,13 @@
                                         (void *)android_mtp_MtpPropertyGroup_format_date_time},
 };
 
+#define GET_METHOD_ID(name, jclass, signature)                              \
+    method_##name = env->GetMethodID(jclass, #name, signature);             \
+    if (method_##name == NULL) {                                            \
+        ALOGE("Can't find " #name);                                         \
+        return -1;                                                          \
+    }                                                                       \
+
 int register_android_mtp_MtpDatabase(JNIEnv *env)
 {
     jclass clazz;
@@ -1314,175 +1319,48 @@
         ALOGE("Can't find android/mtp/MtpDatabase");
         return -1;
     }
-    method_beginSendObject = env->GetMethodID(clazz, "beginSendObject", "(Ljava/lang/String;IIIJJ)I");
-    if (method_beginSendObject == NULL) {
-        ALOGE("Can't find beginSendObject");
-        return -1;
-    }
-    method_endSendObject = env->GetMethodID(clazz, "endSendObject", "(Ljava/lang/String;IIZ)V");
-    if (method_endSendObject == NULL) {
-        ALOGE("Can't find endSendObject");
-        return -1;
-    }
-    method_doScanDirectory = env->GetMethodID(clazz, "doScanDirectory", "(Ljava/lang/String;)V");
-    if (method_doScanDirectory == NULL) {
-        ALOGE("Can't find doScanDirectory");
-        return -1;
-    }
-    method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I");
-    if (method_getObjectList == NULL) {
-        ALOGE("Can't find getObjectList");
-        return -1;
-    }
-    method_getNumObjects = env->GetMethodID(clazz, "getNumObjects", "(III)I");
-    if (method_getNumObjects == NULL) {
-        ALOGE("Can't find getNumObjects");
-        return -1;
-    }
-    method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I");
-    if (method_getSupportedPlaybackFormats == NULL) {
-        ALOGE("Can't find getSupportedPlaybackFormats");
-        return -1;
-    }
-    method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I");
-    if (method_getSupportedCaptureFormats == NULL) {
-        ALOGE("Can't find getSupportedCaptureFormats");
-        return -1;
-    }
-    method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I");
-    if (method_getSupportedObjectProperties == NULL) {
-        ALOGE("Can't find getSupportedObjectProperties");
-        return -1;
-    }
-    method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I");
-    if (method_getSupportedDeviceProperties == NULL) {
-        ALOGE("Can't find getSupportedDeviceProperties");
-        return -1;
-    }
-    method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I");
-    if (method_setObjectProperty == NULL) {
-        ALOGE("Can't find setObjectProperty");
-        return -1;
-    }
-    method_getDeviceProperty = env->GetMethodID(clazz, "getDeviceProperty", "(I[J[C)I");
-    if (method_getDeviceProperty == NULL) {
-        ALOGE("Can't find getDeviceProperty");
-        return -1;
-    }
-    method_setDeviceProperty = env->GetMethodID(clazz, "setDeviceProperty", "(IJLjava/lang/String;)I");
-    if (method_setDeviceProperty == NULL) {
-        ALOGE("Can't find setDeviceProperty");
-        return -1;
-    }
-    method_getObjectPropertyList = env->GetMethodID(clazz, "getObjectPropertyList",
-            "(IIIII)Landroid/mtp/MtpPropertyList;");
-    if (method_getObjectPropertyList == NULL) {
-        ALOGE("Can't find getObjectPropertyList");
-        return -1;
-    }
-    method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
-    if (method_getObjectInfo == NULL) {
-        ALOGE("Can't find getObjectInfo");
-        return -1;
-    }
-    method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)I");
-    if (method_getObjectFilePath == NULL) {
-        ALOGE("Can't find getObjectFilePath");
-        return -1;
-    }
-    method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)I");
-    if (method_deleteFile == NULL) {
-        ALOGE("Can't find deleteFile");
-        return -1;
-    }
-    method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I");
-    if (method_moveObject == NULL) {
-        ALOGE("Can't find moveObject");
-        return -1;
-    }
-    method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
-    if (method_getObjectReferences == NULL) {
-        ALOGE("Can't find getObjectReferences");
-        return -1;
-    }
-    method_setObjectReferences = env->GetMethodID(clazz, "setObjectReferences", "(I[I)I");
-    if (method_setObjectReferences == NULL) {
-        ALOGE("Can't find setObjectReferences");
-        return -1;
-    }
-    method_sessionStarted = env->GetMethodID(clazz, "sessionStarted", "()V");
-    if (method_sessionStarted == NULL) {
-        ALOGE("Can't find sessionStarted");
-        return -1;
-    }
-    method_sessionEnded = env->GetMethodID(clazz, "sessionEnded", "()V");
-    if (method_sessionEnded == NULL) {
-        ALOGE("Can't find sessionEnded");
-        return -1;
-    }
+    GET_METHOD_ID(beginSendObject, clazz, "(Ljava/lang/String;III)I");
+    GET_METHOD_ID(endSendObject, clazz, "(IZ)V");
+    GET_METHOD_ID(rescanFile, clazz, "(Ljava/lang/String;II)V");
+    GET_METHOD_ID(getObjectList, clazz, "(III)[I");
+    GET_METHOD_ID(getNumObjects, clazz, "(III)I");
+    GET_METHOD_ID(getSupportedPlaybackFormats, clazz, "()[I");
+    GET_METHOD_ID(getSupportedCaptureFormats, clazz, "()[I");
+    GET_METHOD_ID(getSupportedObjectProperties, clazz, "(I)[I");
+    GET_METHOD_ID(getSupportedDeviceProperties, clazz, "()[I");
+    GET_METHOD_ID(setObjectProperty, clazz, "(IIJLjava/lang/String;)I");
+    GET_METHOD_ID(getDeviceProperty, clazz, "(I[J[C)I");
+    GET_METHOD_ID(setDeviceProperty, clazz, "(IJLjava/lang/String;)I");
+    GET_METHOD_ID(getObjectPropertyList, clazz, "(IIIII)Landroid/mtp/MtpPropertyList;");
+    GET_METHOD_ID(getObjectInfo, clazz, "(I[I[C[J)Z");
+    GET_METHOD_ID(getObjectFilePath, clazz, "(I[C[J)I");
+    GET_METHOD_ID(beginDeleteObject, clazz, "(I)I");
+    GET_METHOD_ID(endDeleteObject, clazz, "(IZ)V");
+    GET_METHOD_ID(beginMoveObject, clazz, "(III)I");
+    GET_METHOD_ID(endMoveObject, clazz, "(IIIIIZ)V");
+    GET_METHOD_ID(beginCopyObject, clazz, "(III)I");
+    GET_METHOD_ID(endCopyObject, clazz, "(IZ)V");
+    GET_METHOD_ID(getObjectReferences, clazz, "(I)[I");
+    GET_METHOD_ID(setObjectReferences, clazz, "(I[I)I");
 
     field_context = env->GetFieldID(clazz, "mNativeContext", "J");
     if (field_context == NULL) {
         ALOGE("Can't find MtpDatabase.mNativeContext");
         return -1;
     }
-    field_batteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I");
-    if (field_batteryLevel == NULL) {
-        ALOGE("Can't find MtpDatabase.mBatteryLevel");
-        return -1;
-    }
-    field_batteryScale = env->GetFieldID(clazz, "mBatteryScale", "I");
-    if (field_batteryScale == NULL) {
-        ALOGE("Can't find MtpDatabase.mBatteryScale");
-        return -1;
-    }
-    field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I");
-    if (field_deviceType == NULL) {
-        ALOGE("Can't find MtpDatabase.mDeviceType");
-        return -1;
-    }
 
-    // now set up fields for MtpPropertyList class
     clazz = env->FindClass("android/mtp/MtpPropertyList");
     if (clazz == NULL) {
         ALOGE("Can't find android/mtp/MtpPropertyList");
         return -1;
     }
-    field_mCount = env->GetFieldID(clazz, "mCount", "I");
-    if (field_mCount == NULL) {
-        ALOGE("Can't find MtpPropertyList.mCount");
-        return -1;
-    }
-    field_mResult = env->GetFieldID(clazz, "mResult", "I");
-    if (field_mResult == NULL) {
-        ALOGE("Can't find MtpPropertyList.mResult");
-        return -1;
-    }
-    field_mObjectHandles = env->GetFieldID(clazz, "mObjectHandles", "[I");
-    if (field_mObjectHandles == NULL) {
-        ALOGE("Can't find MtpPropertyList.mObjectHandles");
-        return -1;
-    }
-    field_mPropertyCodes = env->GetFieldID(clazz, "mPropertyCodes", "[I");
-    if (field_mPropertyCodes == NULL) {
-        ALOGE("Can't find MtpPropertyList.mPropertyCodes");
-        return -1;
-    }
-    field_mDataTypes = env->GetFieldID(clazz, "mDataTypes", "[I");
-    if (field_mDataTypes == NULL) {
-        ALOGE("Can't find MtpPropertyList.mDataTypes");
-        return -1;
-    }
-    field_mLongValues = env->GetFieldID(clazz, "mLongValues", "[J");
-    if (field_mLongValues == NULL) {
-        ALOGE("Can't find MtpPropertyList.mLongValues");
-        return -1;
-    }
-    field_mStringValues = env->GetFieldID(clazz, "mStringValues", "[Ljava/lang/String;");
-    if (field_mStringValues == NULL) {
-        ALOGE("Can't find MtpPropertyList.mStringValues");
-        return -1;
-    }
+    GET_METHOD_ID(getCode, clazz, "()I");
+    GET_METHOD_ID(getCount, clazz, "()I");
+    GET_METHOD_ID(getObjectHandles, clazz, "()[I");
+    GET_METHOD_ID(getPropertyCodes, clazz, "()[I");
+    GET_METHOD_ID(getDataTypes, clazz, "()[I");
+    GET_METHOD_ID(getLongValues, clazz, "()[J");
+    GET_METHOD_ID(getStringValues, clazz, "()[Ljava/lang/String;");
 
     if (AndroidRuntime::registerNativeMethods(env,
                 "android/mtp/MtpDatabase", gMtpDatabaseMethods, NELEM(gMtpDatabaseMethods)))
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index 6ce104d..c76cebe 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -41,7 +41,6 @@
 static jfieldID field_MtpStorage_storageId;
 static jfieldID field_MtpStorage_path;
 static jfieldID field_MtpStorage_description;
-static jfieldID field_MtpStorage_reserveSpace;
 static jfieldID field_MtpStorage_removable;
 static jfieldID field_MtpStorage_maxFileSize;
 
@@ -50,7 +49,7 @@
 // ----------------------------------------------------------------------------
 
 // in android_mtp_MtpDatabase.cpp
-extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
+extern IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
 
 static inline MtpServer* getMtpServer(JNIEnv *env, jobject thiz) {
     return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext);
@@ -162,7 +161,6 @@
         jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
         jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
         jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
-        jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace);
         jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable);
         jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize);
 
@@ -171,7 +169,7 @@
             const char *descriptionStr = env->GetStringUTFChars(description, NULL);
             if (descriptionStr != NULL) {
                 MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr,
-                        reserveSpace, removable, maxFileSize);
+                        removable, maxFileSize);
                 server->addStorage(storage);
                 env->ReleaseStringUTFChars(path, pathStr);
                 env->ReleaseStringUTFChars(description, descriptionStr);
@@ -241,11 +239,6 @@
         ALOGE("Can't find MtpStorage.mDescription");
         return -1;
     }
-    field_MtpStorage_reserveSpace = env->GetFieldID(clazz, "mReserveSpace", "J");
-    if (field_MtpStorage_reserveSpace == NULL) {
-        ALOGE("Can't find MtpStorage.mReserveSpace");
-        return -1;
-    }
     field_MtpStorage_removable = env->GetFieldID(clazz, "mRemovable", "Z");
     if (field_MtpStorage_removable == NULL) {
         ALOGE("Can't find MtpStorage.mRemovable");
diff --git a/media/tests/MtpTests/Android.mk b/media/tests/MtpTests/Android.mk
new file mode 100644
index 0000000..616e600
--- /dev/null
+++ b/media/tests/MtpTests/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := MtpTests
+
+include $(BUILD_PACKAGE)
diff --git a/media/tests/MtpTests/AndroidManifest.xml b/media/tests/MtpTests/AndroidManifest.xml
new file mode 100644
index 0000000..21e2b01
--- /dev/null
+++ b/media/tests/MtpTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.mtp" >
+
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
+
+    <uses-permission  android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.mtp"
+                     android:label="MtpTests"/>
+</manifest>
diff --git a/media/tests/MtpTests/AndroidTest.xml b/media/tests/MtpTests/AndroidTest.xml
new file mode 100644
index 0000000..a61a3b4
--- /dev/null
+++ b/media/tests/MtpTests/AndroidTest.xml
@@ -0,0 +1,15 @@
+<configuration description="Runs sample instrumentation test.">
+    <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="MtpTests.apk"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="MtpTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.mtp"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
new file mode 100644
index 0000000..0d7f3fe
--- /dev/null
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -0,0 +1,1657 @@
+/*
+ * 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.mtp;
+
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.os.storage.StorageVolume;
+import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * Tests for MtpStorageManager functionality.
+ */
+@RunWith(JUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class MtpStorageManagerTest {
+    private static final String TAG = MtpStorageManagerTest.class.getSimpleName();
+
+    private static final String TEMP_DIR = InstrumentationRegistry.getContext().getFilesDir()
+            + "/" + TAG + "/";
+    private static final File TEMP_DIR_FILE = new File(TEMP_DIR);
+
+    private MtpStorageManager manager;
+
+    private ArrayList<Integer> objectsAdded;
+    private ArrayList<Integer> objectsRemoved;
+
+    private File mainStorageDir;
+    private File secondaryStorageDir;
+
+    private MtpStorage mainMtpStorage;
+    private MtpStorage secondaryMtpStorage;
+
+    static {
+        MtpStorageManager.sDebug = true;
+    }
+
+    private static void logMethodName() {
+        Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
+    }
+
+    private static File createNewFile(File parent) {
+        return createNewFile(parent, UUID.randomUUID().toString());
+    }
+
+    private static File createNewFile(File parent, String name) {
+        try {
+            File ret = new File(parent, name);
+            if (!ret.createNewFile())
+                throw new AssertionError("Failed to create file");
+            return ret;
+        } catch (IOException e) {
+            throw new AssertionError(e.getMessage());
+        }
+    }
+
+    private static File createNewDir(File parent, String name) {
+        File ret = new File(parent, name);
+        if (!ret.mkdir())
+            throw new AssertionError("Failed to create file");
+        return ret;
+    }
+
+    private static File createNewDir(File parent) {
+        return createNewDir(parent, UUID.randomUUID().toString());
+    }
+
+    @Before
+    public void before() {
+        Assert.assertTrue(TEMP_DIR_FILE.mkdir());
+        mainStorageDir = createNewDir(TEMP_DIR_FILE);
+        secondaryStorageDir = createNewDir(TEMP_DIR_FILE);
+
+        StorageVolume mainStorage = new StorageVolume("1", mainStorageDir, "", true, false, true,
+                false, -1, UserHandle.CURRENT, "", "");
+        StorageVolume secondaryStorage = new StorageVolume("2", secondaryStorageDir, "", false,
+                false, true, false, -1, UserHandle.CURRENT, "", "");
+
+        objectsAdded = new ArrayList<>();
+        objectsRemoved = new ArrayList<>();
+
+        manager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
+            @Override
+            public void sendObjectAdded(int id) {
+                objectsAdded.add(id);
+            }
+
+            @Override
+            public void sendObjectRemoved(int id) {
+                objectsRemoved.add(id);
+            }
+        }, null);
+
+        mainMtpStorage = manager.addMtpStorage(mainStorage);
+        secondaryMtpStorage = manager.addMtpStorage(secondaryStorage);
+    }
+
+    @After
+    public void after() {
+        manager.close();
+        FileUtils.deleteContentsAndDir(TEMP_DIR_FILE);
+    }
+
+    /** MtpObject getter tests. **/
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetNameRoot() {
+        logMethodName();
+        MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+        Assert.assertEquals(obj.getName(), mainStorageDir.getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetNameNonRoot() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getName(), newFile.getName());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetIdRoot() {
+        logMethodName();
+        MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+        Assert.assertEquals(obj.getId(), mainMtpStorage.getStorageId());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetIdNonRoot() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getId(), 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectIsDirTrue() {
+        logMethodName();
+        MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+        Assert.assertTrue(obj.isDir());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectIsDirFalse() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir, "TEST123.mp3");
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertFalse(stream.findFirst().get().isDir());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetFormatDir() {
+        logMethodName();
+        File newFile = createNewDir(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetFormatNonDir() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir, "TEST123.mp3");
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_MP3);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetStorageId() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getStorageId(), mainMtpStorage.getStorageId());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetLastModified() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getModifiedTime(),
+                newFile.lastModified() / 1000);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetParent() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getParent(),
+                manager.getStorageRoot(mainMtpStorage.getStorageId()));
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetRoot() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getRoot(),
+                manager.getStorageRoot(mainMtpStorage.getStorageId()));
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetPath() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getPath().toString(), newFile.getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetSize() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        try {
+            new FileOutputStream(newFile).write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0});
+        } catch (IOException e) {
+            Assert.fail();
+        }
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getSize(), 8);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpObjectGetSizeDir() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getSize(), 0);
+    }
+
+    /** MtpStorageManager cache access tests. **/
+
+    @Test
+    @SmallTest
+    public void testAddMtpStorage() {
+        logMethodName();
+        Assert.assertEquals(mainMtpStorage.getPath(), mainStorageDir.getPath());
+        Assert.assertNotNull(manager.getStorageRoot(mainMtpStorage.getStorageId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveMtpStorage() {
+        logMethodName();
+        File newFile = createNewFile(secondaryStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                secondaryMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 1);
+
+        manager.removeMtpStorage(secondaryMtpStorage);
+        Assert.assertNull(manager.getStorageRoot(secondaryMtpStorage.getStorageId()));
+        Assert.assertNull(manager.getObject(1));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetByPath() {
+        logMethodName();
+        File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+        MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath());
+        Assert.assertNotNull(obj);
+        Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetByPathError() {
+        logMethodName();
+        File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+        MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath() + "q");
+        Assert.assertNull(obj);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObject() {
+        logMethodName();
+        File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+        MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath());
+        Assert.assertNotNull(obj);
+
+        Assert.assertEquals(manager.getObject(obj.getId()), obj);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectError() {
+        logMethodName();
+        File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+        Assert.assertNull(manager.getObject(42));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetStorageRoot() {
+        logMethodName();
+        MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+        Assert.assertEquals(obj.getPath().toString(), mainStorageDir.getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsParent() {
+        logMethodName();
+        File newDir = createNewDir(createNewDir(mainStorageDir));
+        File newFile = createNewFile(newDir);
+        File newMP3File = createNewFile(newDir, "lalala.mp3");
+        MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath());
+        Assert.assertNotNull(parent);
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(), 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 2);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsFormat() {
+        logMethodName();
+        File newDir = createNewDir(createNewDir(mainStorageDir));
+        File newFile = createNewFile(newDir);
+        File newMP3File = createNewFile(newDir, "lalala.mp3");
+        MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath());
+        Assert.assertNotNull(parent);
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(),
+                MtpConstants.FORMAT_MP3, mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.findFirst().get().getPath().toString(), newMP3File.toString());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsRoot() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        File newFile = createNewFile(mainStorageDir);
+        File newMP3File = createNewFile(newDir, "lalala.mp3");
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 2);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsAll() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        File newFile = createNewFile(mainStorageDir);
+        File newMP3File = createNewFile(newDir, "lalala.mp3");
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 3);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsAllStorages() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        createNewFile(mainStorageDir);
+        createNewFile(newDir, "lalala.mp3");
+        File newDir2 = createNewDir(secondaryStorageDir);
+        createNewFile(secondaryStorageDir);
+        createNewFile(newDir2, "lalala.mp3");
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0, 0xFFFFFFFF);
+        Assert.assertEquals(stream.count(), 6);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetObjectsAllStoragesRoot() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        createNewFile(mainStorageDir);
+        createNewFile(newDir, "lalala.mp3");
+        File newDir2 = createNewDir(secondaryStorageDir);
+        createNewFile(secondaryStorageDir);
+        createNewFile(newDir2, "lalala.mp3");
+
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, 0xFFFFFFFF);
+        Assert.assertEquals(stream.count(), 4);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    /** MtpStorageManager event handling tests. **/
+
+    @Test
+    @SmallTest
+    public void testObjectAdded() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 0);
+
+        File newFile = createNewFile(mainStorageDir);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                newFile.getPath());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testObjectAddedDir() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 0);
+
+        File newDir = createNewDir(mainStorageDir);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                newDir.getPath());
+        Assert.assertTrue(manager.getObject(objectsAdded.get(0)).isDir());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testObjectAddedRecursiveDir() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 0);
+
+        File newDir = createNewDir(createNewDir(createNewDir(mainStorageDir)));
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 3);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(2)).getPath().toString(),
+                newDir.getPath());
+        Assert.assertTrue(manager.getObject(objectsAdded.get(2)).isDir());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testObjectRemoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 1);
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertEquals(objectsRemoved.size(), 1);
+        Assert.assertNull(manager.getObject(objectsRemoved.get(0)));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testObjectMoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        Assert.assertEquals(stream.count(), 1);
+        File toFile = new File(mainStorageDir, "to" + newFile.getName());
+
+        Assert.assertTrue(newFile.renameTo(toFile));
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                toFile.getPath());
+        Assert.assertNull(manager.getObject(objectsRemoved.get(0)));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    /** MtpStorageManager operation tests. Ensure that events are not sent for the main operation,
+        and also test all possible cases of other processes accessing the file at the same time, as
+        well as cases of both failure and success. **/
+
+    @Test
+    @SmallTest
+    public void testSendObjectSuccess() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+
+        File newFile = createNewFile(mainStorageDir, "newFile");
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, true));
+        Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectSuccessDir() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newDir", MtpConstants.FORMAT_ASSOCIATION);
+        Assert.assertEquals(id, 1);
+
+        File newFile = createNewDir(mainStorageDir, "newDir");
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, true));
+        Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Check that new dir receives events
+        File newerFile = createNewFile(newFile);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                newerFile.getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectSuccessDelayed() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, true));
+
+        File newFile = createNewFile(mainStorageDir, "newFile");
+        manager.flushEvents();
+        Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectSuccessDirDelayed() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newDir", MtpConstants.FORMAT_ASSOCIATION);
+        Assert.assertEquals(id, 1);
+
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, true));
+        File newFile = createNewDir(mainStorageDir, "newDir");
+        manager.flushEvents();
+        Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Check that new dir receives events
+        File newerFile = createNewFile(newFile);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                newerFile.getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectSuccessDeleted() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+
+        File newFile = createNewFile(mainStorageDir, "newFile");
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, true));
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId());
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectFailed() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endSendObject(obj, false));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectFailedDeleted() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+
+        File newFile = createNewFile(mainStorageDir, "newFile");
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endSendObject(obj, false));
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendObjectFailedAdded() {
+        logMethodName();
+        Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId());
+        int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                "newFile", MtpConstants.FORMAT_UNDEFINED);
+        Assert.assertEquals(id, 1);
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+
+        File newDir = createNewDir(mainStorageDir, "newFile");
+        manager.flushEvents();
+        Assert.assertTrue(manager.endSendObject(obj, false));
+        Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+        Assert.assertNull(manager.getObject(id));
+        Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+                newDir.getPath());
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events in new dir
+        createNewFile(newDir);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 2);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectSuccess() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRemoveObject(obj, true));
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectDelayed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(manager.endRemoveObject(obj, true));
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectDir() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        createNewFile(createNewDir(newDir));
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        createNewFile(newDir);
+        Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRemoveObject(obj, true));
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+        Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0);
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectDirDelayed() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        createNewFile(createNewDir(newDir));
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(manager.endRemoveObject(obj, true));
+        Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir));
+        manager.flushEvents();
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0);
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectSuccessAdded() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        int id = obj.getId();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(newFile.delete());
+        createNewFile(mainStorageDir, newFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRemoveObject(obj, true));
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertNull(manager.getObject(id));
+        Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectFailed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(manager.endRemoveObject(obj, false));
+        Assert.assertEquals(manager.getObject(obj.getId()), obj);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectFailedDir() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        createNewFile(newDir);
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRemoveObject(obj, false));
+        Assert.assertEquals(manager.getObject(obj.getId()), obj);
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveObjectFailedRemoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRemoveObject(obj));
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRemoveObject(obj, false));
+        Assert.assertEquals(objectsRemoved.size(), 1);
+        Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId());
+        Assert.assertNull(manager.getObject(obj.getId()));
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testCopyObjectSuccess() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+
+        int id = manager.beginCopyObject(fileObj, dirObj);
+        Assert.assertNotEquals(id, -1);
+        createNewFile(newDir, newFile.getName());
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endCopyObject(obj, true));
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testCopyObjectSuccessRecursive() {
+        logMethodName();
+        File newDirFrom = createNewDir(mainStorageDir);
+        File newDirFrom1 = createNewDir(newDirFrom);
+        File newDirFrom2 = createNewFile(newDirFrom1);
+        File delayedFile = createNewFile(newDirFrom);
+        File deletedFile = createNewFile(newDirFrom);
+        File newDirTo = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject toObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(newDirTo.getName())).findFirst().get();
+        MtpStorageManager.MtpObject fromObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(newDirFrom.getName())).findFirst().get();
+
+        manager.getObjects(fromObj.getId(), 0, mainMtpStorage.getStorageId());
+        int id = manager.beginCopyObject(fromObj, toObj);
+        Assert.assertNotEquals(id, -1);
+        File copiedDir = createNewDir(newDirTo, newDirFrom.getName());
+        File copiedDir1 = createNewDir(copiedDir, newDirFrom1.getName());
+        createNewFile(copiedDir1, newDirFrom2.getName());
+        createNewFile(copiedDir, "extraFile");
+        File toDelete = createNewFile(copiedDir, deletedFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(toDelete.delete());
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endCopyObject(obj, true));
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+
+        createNewFile(copiedDir, delayedFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events in the visited dir, but not the unvisited dir.
+        createNewFile(copiedDir);
+        createNewFile(copiedDir1);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 2);
+        Assert.assertEquals(objectsAdded.size(), 2);
+
+        // Number of files/dirs created, minus the one that was deleted.
+        Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 13);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testCopyObjectFailed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+
+        int id = manager.beginCopyObject(fileObj, dirObj);
+        Assert.assertNotEquals(id, -1);
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endCopyObject(obj, false));
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testCopyObjectFailedAdded() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+
+        int id = manager.beginCopyObject(fileObj, dirObj);
+        Assert.assertNotEquals(id, -1);
+        File addedDir = createNewDir(newDir, newFile.getName());
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endCopyObject(obj, false));
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events in new dir
+        createNewFile(addedDir);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 2);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testCopyObjectFailedDeleted() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+
+        int id = manager.beginCopyObject(fileObj, dirObj);
+        Assert.assertNotEquals(id, -1);
+        Assert.assertTrue(createNewFile(newDir, newFile.getName()).delete());
+        manager.flushEvents();
+        MtpStorageManager.MtpObject obj = manager.getObject(id);
+        Assert.assertTrue(manager.endCopyObject(obj, false));
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectSuccess() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        File renamed = new File(mainStorageDir, "renamed");
+        Assert.assertTrue(newFile.renameTo(renamed));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectDirSuccess() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        File renamed = new File(mainStorageDir, "renamed");
+        Assert.assertTrue(newDir.renameTo(renamed));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Don't expect events
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectDirVisitedSuccess() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        File renamed = new File(mainStorageDir, "renamed");
+        Assert.assertTrue(newDir.renameTo(renamed));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectDelayed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true));
+        File renamed = new File(mainStorageDir, "renamed");
+        Assert.assertTrue(newFile.renameTo(renamed));
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectDirVisitedDelayed() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+        File renamed = new File(mainStorageDir, "renamed");
+        Assert.assertTrue(newDir.renameTo(renamed));
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectFailed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectFailedOldRemoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testRenameObjectFailedNewAdded() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+        createNewFile(mainStorageDir, "renamed");
+        manager.flushEvents();
+        Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectSuccess() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File dir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+        File moved = new File(dir, newFile.getName());
+        Assert.assertTrue(newFile.renameTo(moved));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, newFile.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(fileObj.getPath().toString(), moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectDirSuccess() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+        File renamed = new File(newDir, movedDir.getName());
+        Assert.assertTrue(movedDir.renameTo(renamed));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, movedDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Don't expect events
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectDirVisitedSuccess() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+        manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+        File renamed = new File(newDir, movedDir.getName());
+        Assert.assertTrue(movedDir.renameTo(renamed));
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, movedDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectDelayed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File dir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, newFile.getName(), true));
+
+        File moved = new File(dir, newFile.getName());
+        Assert.assertTrue(newFile.renameTo(moved));
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(fileObj.getPath().toString(), moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectDirVisitedDelayed() {
+        logMethodName();
+        File newDir = createNewDir(mainStorageDir);
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+        manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, movedDir.getName(), true));
+
+        File renamed = new File(newDir, movedDir.getName());
+        Assert.assertTrue(movedDir.renameTo(renamed));
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(renamed);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectFailed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File dir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectFailedOldRemoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File dir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectFailedNewAdded() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        File dir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId())
+                .filter(o -> !o.isDir()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+        createNewFile(dir, newFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                dirObj, newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageSuccess() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(newFile.delete());
+        File moved = createNewFile(secondaryStorageDir, newFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                newFile.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObject(fileObj.getId()).getPath().toString(),
+                moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageDirSuccess() {
+        logMethodName();
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(movedObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(movedDir.delete());
+        File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                movedDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+                moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Don't expect events
+        createNewFile(moved);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageDirVisitedSuccess() {
+        logMethodName();
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginMoveObject(movedObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(movedDir.delete());
+        File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                movedDir.getName(), true));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+                moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(moved);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageDelayed() {
+        logMethodName();
+        File movedFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(movedObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                movedFile.getName(), true));
+
+        Assert.assertTrue(movedFile.delete());
+        File moved = createNewFile(secondaryStorageDir, movedFile.getName());
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+                moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageDirVisitedDelayed() {
+        logMethodName();
+        File movedDir = createNewDir(mainStorageDir);
+        MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+        Assert.assertTrue(manager.beginMoveObject(movedObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                movedDir.getName(), true));
+
+        Assert.assertTrue(movedDir.delete());
+        File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+        manager.flushEvents();
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+        Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+                moved.getPath());
+
+        Assert.assertTrue(manager.checkConsistency());
+
+        // Expect events since the dir was visited
+        createNewFile(moved);
+        manager.flushEvents();
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageFailed() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageFailedOldRemoved() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        Assert.assertTrue(newFile.delete());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsRemoved.size(), 1);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveObjectXStorageFailedNewAdded() {
+        logMethodName();
+        File newFile = createNewFile(mainStorageDir);
+        MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+                mainMtpStorage.getStorageId()).findFirst().get();
+        Assert.assertTrue(manager.beginMoveObject(fileObj,
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+        createNewFile(secondaryStorageDir, newFile.getName());
+        manager.flushEvents();
+        Assert.assertTrue(manager.endMoveObject(
+                manager.getStorageRoot(mainMtpStorage.getStorageId()),
+                manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+                newFile.getName(), false));
+
+        Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsRemoved.size(), 0);
+
+        Assert.assertTrue(manager.checkConsistency());
+    }
+}
\ No newline at end of file
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index be2bb53..34d6508 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -20,7 +20,8 @@
 
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
-    com.android.nfc_extras
+    com.android.nfc_extras \
+    android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml
index 5f8877a..a61e6ce 100644
--- a/packages/InputDevices/res/values-bn/strings.xml
+++ b/packages/InputDevices/res/values-bn/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"স্প্যানিশ (ল্যাটিন)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"লাটভিও"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"ফার্সী"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"আজারবাইজানি"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml
index d11df01..915f1b6 100644
--- a/packages/InputDevices/res/values-gu/strings.xml
+++ b/packages/InputDevices/res/values-gu/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"સ્પેનિશ (લેટિન)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"લાતવિયન"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"પર્શિયન"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"અઝરબૈજાની"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml
index 2e6f892..8f2b51a 100644
--- a/packages/InputDevices/res/values-kn/strings.xml
+++ b/packages/InputDevices/res/values-kn/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ಸ್ಪ್ಯಾನಿಶ್ (ಲ್ಯಾಟಿನ್)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ಲ್ಯಾಟ್ವಿಯನ್"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"ಪರ್ಶಿಯನ್"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ಅಜೆರ್ಬೈಜಾನಿ"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml
index dfd9754..d346d9f 100644
--- a/packages/InputDevices/res/values-ml/strings.xml
+++ b/packages/InputDevices/res/values-ml/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"സ്‌പാനിഷ് (ലാറ്റിൻ)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ലാറ്റ്വിയന്‍"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"പേര്‍ഷ്യന്‍"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"അസര്‍ബൈജാനി"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml
index 1cf6a2e..f707730 100644
--- a/packages/InputDevices/res/values-pa/strings.xml
+++ b/packages/InputDevices/res/values-pa/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ਸਪੇਨੀ (ਲਾਤੀਨੀ)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ਲਾਤਵੀਅਨ"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"ਫ਼ਾਰਸੀ"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ਅਜ਼ੇਰਬੈਜਾਨੀ"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml
index ebee5c1..a4d07ac 100644
--- a/packages/InputDevices/res/values-ta/strings.xml
+++ b/packages/InputDevices/res/values-ta/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ஸ்பானிஷ் (லத்தீன்)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"லத்வியன்"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"பெர்சியன்"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"அஜர்பைஜானி"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml
index 141bb95..f7cce96 100644
--- a/packages/InputDevices/res/values-te/strings.xml
+++ b/packages/InputDevices/res/values-te/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"స్పానిష్ (లాటిన్)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"లాత్వియన్"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"పర్షియన్"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"అజర్బైజాన్"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml
index 71ce1cc..ab95bd5 100644
--- a/packages/InputDevices/res/values-ur/strings.xml
+++ b/packages/InputDevices/res/values-ur/strings.xml
@@ -42,6 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ہسپانوی (لاطینی)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"لاتویائی"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"فارسی"</string>
-    <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
-    <skip />
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"آزربائیجانی"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml
index e820469..d6f7b2b 100644
--- a/packages/InputDevices/res/values-uz/strings.xml
+++ b/packages/InputDevices/res/values-uz/strings.xml
@@ -42,5 +42,5 @@
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Ispan (lotin)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latish"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"Fors"</string>
-    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ozarbayjon"</string>
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Ozarbayjon"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 57d1657..22e9d35 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -353,7 +353,7 @@
     <string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"Daltonismoa (gorri-berdeak)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"Protanopia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"Tritanopia (urdin-horia)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolore-zuzenketa"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolorearen zuzenketa"</string>
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Eginbidea esperimentala da eta eragina izan dezake funtzionamenduan."</string>
     <string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
     <string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">^1</xliff:g> inguru gelditzen dira"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f5cf5f6..47d8408 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -401,7 +401,7 @@
     <string name="active_input_method_subtypes" msgid="3596398805424733238">"Métodos de entrada activos"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Usar idiomas do sistema"</string>
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Non se puido abrir a configuración de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
-    <string name="ime_security_warning" msgid="4135828934735934248">"É posible que este método de entrada poida recompilar todo o texto que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de entrada?"</string>
+    <string name="ime_security_warning" msgid="4135828934735934248">"Este método de introdución de texto pode recompilar todo o que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de introdución de texto?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Tras un reinicio, non se pode iniciar esta aplicación ata que desbloquees o teléfono"</string>
     <string name="ims_reg_title" msgid="7609782759207241443">"Estado de rexistro de IMS"</string>
     <string name="ims_reg_status_registered" msgid="933003316932739188">"Rexistrado"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index c3a36e9..fce5dd9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -432,53 +432,9 @@
      * the admin component will be set to {@code null} and userId to {@link UserHandle#USER_NULL}
      */
     public static EnforcedAdmin checkIfMaximumTimeToLockIsSet(Context context) {
-        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        if (dpm == null) {
-            return null;
-        }
-        EnforcedAdmin enforcedAdmin = null;
-        final int userId = UserHandle.myUserId();
-        final UserManager um = UserManager.get(context);
-        final List<UserInfo> profiles = um.getProfiles(userId);
-        final int profilesSize = profiles.size();
-        // As we do not have a separate screen lock timeout settings for work challenge,
-        // we need to combine all profiles maximum time to lock even work challenge is
-        // enabled.
-        for (int i = 0; i < profilesSize; i++) {
-            final UserInfo userInfo = profiles.get(i);
-            final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
-            if (admins == null) {
-                continue;
-            }
-            for (ComponentName admin : admins) {
-                if (dpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
-                    if (enforcedAdmin == null) {
-                        enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
-                    } else {
-                        return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
-                    }
-                    // This same admins could have set policies both on the managed profile
-                    // and on the parent. So, if the admin has set the policy on the
-                    // managed profile here, we don't need to further check if that admin
-                    // has set policy on the parent admin.
-                    continue;
-                }
-                if (userInfo.isManagedProfile()) {
-                    // If userInfo.id is a managed profile, we also need to look at
-                    // the policies set on the parent.
-                    DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm, userInfo);
-                    if (parentDpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
-                        if (enforcedAdmin == null) {
-                            enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
-                        } else {
-                            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
-                        }
-                    }
-                }
-            }
-        }
-        return enforcedAdmin;
+        return checkForLockSetting(context, UserHandle.myUserId(),
+                (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int userId) ->
+                        dpm.getMaximumTimeToLock(admin, userId) > 0);
     }
 
     private interface LockSettingCheck {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fa2499f..5c73d54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -21,6 +21,9 @@
 import android.app.Application;
 import android.app.usage.StorageStats;
 import android.app.usage.StorageStatsManager;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -52,11 +55,6 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
 
 import java.io.File;
 import java.io.IOException;
@@ -595,7 +593,7 @@
                 .replaceAll("").toLowerCase();
     }
 
-    public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
+    public class Session implements LifecycleObserver {
         final Callbacks mCallbacks;
         boolean mResumed;
 
@@ -621,6 +619,7 @@
             }
         }
 
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
         public void onResume() {
             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
             synchronized (mEntriesMap) {
@@ -633,6 +632,7 @@
             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
         }
 
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
         public void onPause() {
             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
             synchronized (mEntriesMap) {
@@ -752,6 +752,7 @@
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
 
+        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
         public void onDestroy() {
             if (!mHasLifecycle) {
                 // TODO: Legacy, remove this later once all usages are switched to Lifecycle
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
new file mode 100644
index 0000000..3c3c70a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -0,0 +1,226 @@
+/*
+ * 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.applications;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Class for managing services matching a given intent and requesting a given permission.
+ */
+public class ServiceListing {
+    private final ContentResolver mContentResolver;
+    private final Context mContext;
+    private final String mTag;
+    private final String mSetting;
+    private final String mIntentAction;
+    private final String mPermission;
+    private final String mNoun;
+    private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
+    private final List<ServiceInfo> mServices = new ArrayList<>();
+    private final List<Callback> mCallbacks = new ArrayList<>();
+
+    private boolean mListening;
+
+    private ServiceListing(Context context, String tag,
+            String setting, String intentAction, String permission, String noun) {
+        mContentResolver = context.getContentResolver();
+        mContext = context;
+        mTag = tag;
+        mSetting = setting;
+        mIntentAction = intentAction;
+        mPermission = permission;
+        mNoun = noun;
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public void removeCallback(Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    public void setListening(boolean listening) {
+        if (mListening == listening) return;
+        mListening = listening;
+        if (mListening) {
+            // listen for package changes
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addDataScheme("package");
+            mContext.registerReceiver(mPackageReceiver, filter);
+            mContentResolver.registerContentObserver(Settings.Secure.getUriFor(mSetting),
+                    false, mSettingsObserver);
+        } else {
+            mContext.unregisterReceiver(mPackageReceiver);
+            mContentResolver.unregisterContentObserver(mSettingsObserver);
+        }
+    }
+
+    private void saveEnabledServices() {
+        StringBuilder sb = null;
+        for (ComponentName cn : mEnabledServices) {
+            if (sb == null) {
+                sb = new StringBuilder();
+            } else {
+                sb.append(':');
+            }
+            sb.append(cn.flattenToString());
+        }
+        Settings.Secure.putString(mContentResolver, mSetting,
+                sb != null ? sb.toString() : "");
+    }
+
+    private void loadEnabledServices() {
+        mEnabledServices.clear();
+        final String flat = Settings.Secure.getString(mContentResolver, mSetting);
+        if (flat != null && !"".equals(flat)) {
+            final String[] names = flat.split(":");
+            for (String name : names) {
+                final ComponentName cn = ComponentName.unflattenFromString(name);
+                if (cn != null) {
+                    mEnabledServices.add(cn);
+                }
+            }
+        }
+    }
+
+    public void reload() {
+        loadEnabledServices();
+        mServices.clear();
+        final int user = ActivityManager.getCurrentUser();
+
+        final PackageManagerWrapper pmWrapper =
+                new PackageManagerWrapper(mContext.getPackageManager());
+        List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
+                new Intent(mIntentAction),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                user);
+
+        for (ResolveInfo resolveInfo : installedServices) {
+            ServiceInfo info = resolveInfo.serviceInfo;
+
+            if (!mPermission.equals(info.permission)) {
+                Slog.w(mTag, "Skipping " + mNoun + " service "
+                        + info.packageName + "/" + info.name
+                        + ": it does not require the permission "
+                        + mPermission);
+                continue;
+            }
+            mServices.add(info);
+        }
+        for (Callback callback : mCallbacks) {
+            callback.onServicesReloaded(mServices);
+        }
+    }
+
+    public boolean isEnabled(ComponentName cn) {
+        return mEnabledServices.contains(cn);
+    }
+
+    public void setEnabled(ComponentName cn, boolean enabled) {
+        if (enabled) {
+            mEnabledServices.add(cn);
+        } else {
+            mEnabledServices.remove(cn);
+        }
+        saveEnabledServices();
+    }
+
+    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            reload();
+        }
+    };
+
+    private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reload();
+        }
+    };
+
+    public interface Callback {
+        void onServicesReloaded(List<ServiceInfo> services);
+    }
+
+    public static class Builder {
+        private final Context mContext;
+        private String mTag;
+        private String mSetting;
+        private String mIntentAction;
+        private String mPermission;
+        private String mNoun;
+
+        public Builder(Context context) {
+            mContext = context;
+        }
+
+        public Builder setTag(String tag) {
+            mTag = tag;
+            return this;
+        }
+
+        public Builder setSetting(String setting) {
+            mSetting = setting;
+            return this;
+        }
+
+        public Builder setIntentAction(String intentAction) {
+            mIntentAction = intentAction;
+            return this;
+        }
+
+        public Builder setPermission(String permission) {
+            mPermission = permission;
+            return this;
+        }
+
+        public Builder setNoun(String noun) {
+            mNoun = noun;
+            return this;
+        }
+
+        public ServiceListing build() {
+            return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 1993b45..3732471 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -73,6 +73,7 @@
                 mCachedDeviceManager, context);
         mProfileManager = new LocalBluetoothProfileManager(context,
                 mLocalAdapter, mCachedDeviceManager, mEventManager);
+        mEventManager.readPairedDevices();
     }
 
     public LocalBluetoothAdapter getBluetoothAdapter() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index a8262c8..974b2a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -271,7 +271,7 @@
      * @param now The current time, used to tell whether daylight savings is active.
      * @return A CharSequence suitable for display as the offset label of {@code tz}.
      */
-    private static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
+    public static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
             TimeZone tz, Date now) {
         final SpannableStringBuilder builder = new SpannableStringBuilder();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
new file mode 100644
index 0000000..2b6d09f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -0,0 +1,108 @@
+/*
+ * 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.fuelgauge;
+
+import android.os.IDeviceIdleController;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.support.annotation.VisibleForTesting;
+import android.util.ArraySet;
+import android.util.Log;
+
+/**
+ * Handles getting/changing the whitelist for the exceptions to battery saving features.
+ */
+public class PowerWhitelistBackend {
+
+    private static final String TAG = "PowerWhitelistBackend";
+
+    private static final String DEVICE_IDLE_SERVICE = "deviceidle";
+
+    private static PowerWhitelistBackend sInstance;
+
+    private final IDeviceIdleController mDeviceIdleService;
+    private final ArraySet<String> mWhitelistedApps = new ArraySet<>();
+    private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>();
+
+    public PowerWhitelistBackend() {
+        mDeviceIdleService = IDeviceIdleController.Stub.asInterface(
+                ServiceManager.getService(DEVICE_IDLE_SERVICE));
+        refreshList();
+    }
+
+    @VisibleForTesting
+    PowerWhitelistBackend(IDeviceIdleController deviceIdleService) {
+        mDeviceIdleService = deviceIdleService;
+        refreshList();
+    }
+
+    public int getWhitelistSize() {
+        return mWhitelistedApps.size();
+    }
+
+    public boolean isSysWhitelisted(String pkg) {
+        return mSysWhitelistedApps.contains(pkg);
+    }
+
+    public boolean isWhitelisted(String pkg) {
+        return mWhitelistedApps.contains(pkg);
+    }
+
+    public void addApp(String pkg) {
+        try {
+            mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
+            mWhitelistedApps.add(pkg);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        }
+    }
+
+    public void removeApp(String pkg) {
+        try {
+            mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
+            mWhitelistedApps.remove(pkg);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        }
+    }
+
+    @VisibleForTesting
+    public void refreshList() {
+        mSysWhitelistedApps.clear();
+        mWhitelistedApps.clear();
+        try {
+            String[] whitelistedApps = mDeviceIdleService.getFullPowerWhitelist();
+            for (String app : whitelistedApps) {
+                mWhitelistedApps.add(app);
+            }
+            String[] sysWhitelistedApps = mDeviceIdleService.getSystemPowerWhitelist();
+            for (String app : sysWhitelistedApps) {
+                mSysWhitelistedApps.add(app);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        }
+    }
+
+    public static PowerWhitelistBackend getInstance() {
+        if (sInstance == null) {
+            sInstance = new PowerWhitelistBackend();
+        }
+        return sInstance;
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 3f826cc..6025d68 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -16,21 +16,21 @@
 
 package com.android.settingslib.location;
 
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
 import android.util.IconDrawableFactory;
 import android.util.Log;
-
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -38,11 +38,13 @@
  */
 public class RecentLocationApps {
     private static final String TAG = RecentLocationApps.class.getSimpleName();
-    private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+    @VisibleForTesting
+    static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
 
     private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
 
-    private static final int[] LOCATION_OPS = new int[] {
+    @VisibleForTesting
+    static final int[] LOCATION_OPS = new int[] {
             AppOpsManager.OP_MONITOR_LOCATION,
             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
     };
@@ -59,6 +61,7 @@
 
     /**
      * Fills a list of applications which queried location recently within specified time.
+     * Apps are sorted by recency. Apps with more recent location requests are in the front.
      */
     public List<Request> getAppList() {
         // Retrieve a location usage list from AppOps
@@ -91,7 +94,18 @@
                 requests.add(request);
             }
         }
+        return requests;
+    }
 
+    public List<Request> getAppListSorted() {
+        List<Request> requests = getAppList();
+        // Sort the list of Requests by recency. Most recent request first.
+        Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
+            @Override
+            public int compare(Request request1, Request request2) {
+                return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
+            }
+        }));
         return requests;
     }
 
@@ -108,10 +122,12 @@
         List<AppOpsManager.OpEntry> entries = ops.getOps();
         boolean highBattery = false;
         boolean normalBattery = false;
+        long locationRequestFinishTime = 0L;
         // Earliest time for a location request to end and still be shown in list.
         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
         for (AppOpsManager.OpEntry entry : entries) {
             if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
+                locationRequestFinishTime = entry.getTime() + entry.getDuration();
                 switch (entry.getOp()) {
                     case AppOpsManager.OP_MONITOR_LOCATION:
                         normalBattery = true;
@@ -133,15 +149,13 @@
         }
 
         // The package is fresh enough, continue.
-
         int uid = ops.getUid();
         int userId = UserHandle.getUserId(uid);
 
         Request request = null;
         try {
-            IPackageManager ipm = AppGlobals.getPackageManager();
-            ApplicationInfo appInfo =
-                    ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
+            ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
             if (appInfo == null) {
                 Log.w(TAG, "Null application info retrieved for package " + packageName
                         + ", userId " + userId);
@@ -158,12 +172,10 @@
                 badgedAppLabel = null;
             }
             request = new Request(packageName, userHandle, icon, appLabel, highBattery,
-                    badgedAppLabel);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error while retrieving application info for package " + packageName
-                    + ", userId " + userId, e);
+                    badgedAppLabel, locationRequestFinishTime);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
         }
-
         return request;
     }
 
@@ -174,15 +186,18 @@
         public final CharSequence label;
         public final boolean isHighBattery;
         public final CharSequence contentDescription;
+        public final long requestFinishTime;
 
         private Request(String packageName, UserHandle userHandle, Drawable icon,
-                CharSequence label, boolean isHighBattery, CharSequence contentDescription) {
+                CharSequence label, boolean isHighBattery, CharSequence contentDescription,
+                long requestFinishTime) {
             this.packageName = packageName;
             this.userHandle = userHandle;
             this.icon = icon;
             this.label = label;
             this.isHighBattery = isHighBattery;
             this.contentDescription = contentDescription;
+            this.requestFinishTime = requestFinishTime;
         }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index 2738027..02a4973 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -49,7 +49,7 @@
 
 LOCAL_JAVA_LIBRARIES := \
     junit \
-    platform-robolectric-3.4.2-prebuilt
+    platform-robolectric-3.5.1-prebuilt
 
 LOCAL_INSTRUMENTATION_FOR := SettingsLibShell
 LOCAL_MODULE := SettingsLibRoboTests
@@ -74,4 +74,4 @@
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
-include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.5.1/run_robotests.mk
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
index 698e442..df850be 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
@@ -38,32 +38,25 @@
         final String resDir = appRoot + "/tests/robotests/res";
         final String assetsDir = appRoot + config.assetDir();
 
-        final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath),
-                Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) {
+        return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir),
+            Fs.fileFromPath(assetsDir), "com.android.settingslib") {
             @Override
             public List<ResourcePath> getIncludedResourcePaths() {
                 List<ResourcePath> paths = super.getIncludedResourcePaths();
-                SettingsLibRobolectricTestRunner.getIncludedResourcePaths(getPackageName(), paths);
+                paths.add(new ResourcePath(
+                    null,
+                    Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
+                    null));
+                paths.add(new ResourcePath(
+                    null,
+                    Fs.fileFromPath("./frameworks/base/core/res/res"),
+                    null));
+                paths.add(new ResourcePath(
+                    null,
+                    Fs.fileFromPath("./frameworks/support/v7/appcompat/res"),
+                    null));
                 return paths;
             }
         };
-        manifest.setPackageName("com.android.settingslib");
-        return manifest;
     }
-
-    static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) {
-        paths.add(new ResourcePath(
-                null,
-                Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
-                null));
-        paths.add(new ResourcePath(
-                null,
-                Fs.fileFromPath("./frameworks/base/core/res/res"),
-                null));
-        paths.add(new ResourcePath(
-                null,
-                Fs.fileFromPath("./frameworks/support/v7/appcompat/res"),
-                null));
-    }
-
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
new file mode 100644
index 0000000..fa31a7d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.provider.Settings;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.ShadowPackageManagerWrapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+    shadows = {ShadowPackageManagerWrapper.class})
+public class ServiceListingTest {
+
+    private static final String TEST_SETTING = "testSetting";
+    private static final String TEST_INTENT = "com.example.intent";
+    private static final String TEST_PERMISSION = "testPermission";
+
+    private ServiceListing mServiceListing;
+
+    @Before
+    public void setUp() {
+        mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setPermission("testPermission")
+                .build();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowPackageManagerWrapper.reset();
+    }
+
+    @Test
+    public void testCallback() {
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+        verify(callback, times(1)).onServicesReloaded(anyList());
+        mServiceListing.removeCallback(callback);
+        mServiceListing.reload();
+        verify(callback, times(1)).onServicesReloaded(anyList());
+    }
+
+    @Test
+    public void testSaveLoad() {
+        ComponentName testComponent1 = new ComponentName("testPackage1", "testClass1");
+        ComponentName testComponent2 = new ComponentName("testPackage2", "testClass2");
+        Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING,
+                testComponent1.flattenToString() + ":" + testComponent2.flattenToString());
+
+        mServiceListing.reload();
+
+        assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
+        assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).contains(testComponent1.flattenToString());
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).contains(testComponent2.flattenToString());
+
+        mServiceListing.setEnabled(testComponent1, false);
+
+        assertThat(mServiceListing.isEnabled(testComponent1)).isFalse();
+        assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).doesNotContain(testComponent1.flattenToString());
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).contains(testComponent2.flattenToString());
+
+        mServiceListing.setEnabled(testComponent1, true);
+
+        assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
+        assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).contains(testComponent1.flattenToString());
+        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+                TEST_SETTING)).contains(testComponent2.flattenToString());
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
new file mode 100644
index 0000000..fc0019d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.fuelgauge;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.IDeviceIdleController;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PowerWhitelistBackendTest {
+
+    private static final String PACKAGE_ONE = "com.example.packageone";
+    private static final String PACKAGE_TWO = "com.example.packagetwo";
+
+    private PowerWhitelistBackend mPowerWhitelistBackend;
+    @Mock
+    private IDeviceIdleController mDeviceIdleService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(new String[] {}).when(mDeviceIdleService).getFullPowerWhitelist();
+        doReturn(new String[] {}).when(mDeviceIdleService).getSystemPowerWhitelist();
+        doNothing().when(mDeviceIdleService).addPowerSaveWhitelistApp(anyString());
+        doNothing().when(mDeviceIdleService).removePowerSaveWhitelistApp(anyString());
+        mPowerWhitelistBackend = new PowerWhitelistBackend(mDeviceIdleService);
+    }
+
+    @Test
+    public void testIsWhitelisted() throws Exception {
+        doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist();
+        mPowerWhitelistBackend.refreshList();
+
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+
+        mPowerWhitelistBackend.addApp(PACKAGE_TWO);
+
+        verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isTrue();
+
+        mPowerWhitelistBackend.removeApp(PACKAGE_TWO);
+
+        verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+
+        mPowerWhitelistBackend.removeApp(PACKAGE_ONE);
+
+        verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+    }
+
+    @Test
+    public void testIsSystemWhitelisted() throws Exception {
+        doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist();
+        mPowerWhitelistBackend.refreshList();
+
+        assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_TWO)).isFalse();
+        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
+
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
new file mode 100644
index 0000000..226166b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -0,0 +1,162 @@
+package com.android.settingslib.location;
+
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(
+        manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION)
+public class RecentLocationAppsTest {
+
+    private static final int TEST_UID = 1234;
+    private static final long NOW = System.currentTimeMillis();
+    // App running duration in milliseconds
+    private static final int DURATION = 10;
+    private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
+    private static final long FOURTEEN_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(14);
+    private static final long TWENTY_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(20);
+    private static final String[] TEST_PACKAGE_NAMES =
+            {"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private UserManager mUserManager;
+    private int mTestUserId;
+    private RecentLocationApps mRecentLocationApps;
+
+
+
+    @Before
+    public void setUp() throws NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
+                .thenReturn("testApplicationLabel");
+        when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
+                .thenReturn("testUserBadgedLabel");
+        mTestUserId = UserHandle.getUserId(TEST_UID);
+        when(mUserManager.getUserProfiles())
+                .thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
+
+        long[] testRequestTime = {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO};
+        List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
+
+        mRecentLocationApps = new RecentLocationApps(mContext);
+    }
+
+    @Test
+    public void testGetAppList_shouldFilterRecentApps() {
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        // Only two of the apps have requested location within 15 min.
+        assertThat(requests).hasSize(2);
+        // Make sure apps are ordered by recency
+        assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+        assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+        assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+        assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+    }
+
+    @Test
+    public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
+        // Add android OS to the list of apps.
+        PackageOps androidSystemPackageOps =
+                createPackageOps(
+                        RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME,
+                        Process.SYSTEM_UID,
+                        AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
+                        ONE_MIN_AGO,
+                        DURATION);
+        long[] testRequestTime =
+                {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO, ONE_MIN_AGO};
+        List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+        appOps.add(androidSystemPackageOps);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        mockTestApplicationInfos(
+                Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME);
+
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        // Android OS shouldn't show up in the list of apps.
+        assertThat(requests).hasSize(2);
+        // Make sure apps are ordered by recency
+        assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+        assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+        assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+        assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+    }
+
+    private void mockTestApplicationInfos(int userId, String... packageNameList)
+            throws NameNotFoundException {
+        for (String packageName : packageNameList) {
+            ApplicationInfo appInfo = new ApplicationInfo();
+            appInfo.packageName = packageName;
+            when(mPackageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
+        }
+    }
+
+    private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
+        List<PackageOps> packageOpsList = new ArrayList<>();
+        for (int i = 0; i < packageNameList.length ; i++) {
+            PackageOps packageOps = createPackageOps(
+                    packageNameList[i],
+                    TEST_UID,
+                    AppOpsManager.OP_MONITOR_LOCATION,
+                    time[i],
+                    DURATION);
+            packageOpsList.add(packageOps);
+        }
+        return packageOpsList;
+    }
+
+    private PackageOps createPackageOps(
+            String packageName, int uid, int op, long time, int duration) {
+        return new PackageOps(
+                packageName,
+                uid,
+                Collections.singletonList(createOpEntryWithTime(op, time, duration)));
+    }
+
+    private OpEntry createOpEntryWithTime(int op, long time, int duration) {
+        return new OpEntry(op, AppOpsManager.MODE_ALLOWED, time, 0L, duration, 0, "");
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java
new file mode 100644
index 0000000..1fdca27
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * 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.testutils.shadow;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.ArrayMap;
+
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Shadow for {@link PackageManagerWrapper} to allow stubbing hidden methods.
+ */
+@Implements(PackageManagerWrapper.class)
+public class ShadowPackageManagerWrapper {
+    private static final Map<Intent, List<ResolveInfo>> intentServices = new ArrayMap<>();
+
+    @Implementation
+    public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) {
+        List<ResolveInfo> list = intentServices.get(intent);
+        return list != null ? list : Collections.emptyList();
+    }
+
+    public static void addResolveInfoForIntent(Intent intent, ResolveInfo info) {
+        List<ResolveInfo> infoList = intentServices.computeIfAbsent(intent, k -> new ArrayList<>());
+        infoList.add(info);
+    }
+
+    public static void reset() {
+        intentServices.clear();
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab42da..d675a7a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -132,6 +132,7 @@
     <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
     <!-- Permission needed to access privileged VR APIs -->
     <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+    <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" />
 
     <application android:label="@string/app_label"
                  android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 5df3989..791f5e9 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="other">ডিভাইসটি <xliff:g id="NUMBER_1">%d</xliff:g> ঘন্টা ধরে আনলক করা হয় নি। পাসওয়ার্ড নিশ্চিত করুন।</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"স্বীকৃত নয়"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="one">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item>
+      <item quantity="other">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="one">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item>
+      <item quantity="other">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index a6ee9a6..c62bab8 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="other">ઉપકરણને <xliff:g id="NUMBER_1">%d</xliff:g> કલાક માટે અનલૉક કરવામાં આવ્યું નથી. પાસવર્ડની પુષ્ટિ કરો.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"ઓળખાયેલ નથી"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="one">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે.</item>
+      <item quantity="other">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે.</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="one">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item>
+      <item quantity="other">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 2ee30e9..2c29112 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="other">ಸಾಧನವನ್ನು <xliff:g id="NUMBER_1">%d</xliff:g> ಗಂಟೆಗಳವರೆಗೆ ಅನ್‌ಲಾಕ್‌ ಮಾಡಿರಲಿಲ್ಲ. ಪಾಸ್‌ವರ್ಡ್‌ ಖಚಿತಪಡಿಸಿ.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="one">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item>
+      <item quantity="other">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="one">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item>
+      <item quantity="other">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 054ce7c..a4f5b7d 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -141,6 +141,6 @@
     </plurals>
     <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
       <item quantity="other">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_1">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
-      <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
+      <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгаарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
     </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 7f0e957..d62537d 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="one">ഉപകരണം <xliff:g id="NUMBER_0">%d</xliff:g> മണിക്കൂറായി അൺലോക്ക് ചെയ്തിട്ടില്ല. പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"തിരിച്ചറിഞ്ഞില്ല"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="other">സിം പിൻ നൽകുക, <xliff:g id="NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു.</item>
+      <item quantity="one">സിം പിൻ നൽകുക, ഉപകരണം അൺലോക്ക് ചെയ്യാൻ കാരിയറുമായി ബന്ധപ്പെടേണ്ടിവരുന്നതിന് മുമ്പ് <xliff:g id="NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു.</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="other">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item>
+      <item quantity="one">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index dc0e53ba..d5d27ca 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="other">ਡੀਵਾਈਸ <xliff:g id="NUMBER_1">%d</xliff:g> ਘੰਟਿਆਂ ਤੋਂ ਅਣਲਾਕ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="one">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ।</item>
+      <item quantity="other">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="one">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item>
+      <item quantity="other">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 727ea5b..2ce57a0 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> மணிநேரமாகச் சாதனம் திறக்கப்படவில்லை. கடவுச்சொல்லை உறுதிப்படுத்தவும்.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"அடையாளங்காண முடியவில்லை"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="other">சிம் பின்னை உள்ளிடவும், மேலும் <xliff:g id="NUMBER_1">%d</xliff:g> முறை முயற்சிக்கலாம்.</item>
+      <item quantity="one">சிம் பின்னை உள்ளிடவும், நீங்கள் <xliff:g id="NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியுமென்பதால், அதற்கு முன்பு மொபைல் நிறுவனத்தைத் தொடர்பு கொண்டு சாதனத்தைத் திறக்க முயலவும்.</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="other">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_1">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item>
+      <item quantity="one">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index ddc5928..bdee4a3 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> గంట పాటు పరికరాన్ని అన్‌లాక్ చేయలేదు. పాస్‌వర్డ్‌ని నమోదు చేయండి.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"గుర్తించలేదు"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="other">SIM పిన్‌ని నమోదు చేయండి, మీకు <xliff:g id="NUMBER_1">%d</xliff:g> ప్రయత్నలు మిగిలి ఉన్నాయి.</item>
+      <item quantity="one">SIM పిన్‌ని నమోదు చేయండి, మీరు మీ పరికరాన్ని అన్‌లాక్ చేయడానికి తప్పనిసరిగా మీ క్యారియర్‌ను సంప్రదించడానికి ముందు మీకు <xliff:g id="NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది.</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="other">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్‌ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_1">%d</xliff:g> ప్రయత్నాలు మిగిలి ఉన్నాయి. వివరాల కోసం కారియర్‌ను సంప్రదించండి.</item>
+      <item quantity="one">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్‌ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది వివరాల కోసం కారియర్‌ను సంప్రదించండి.</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 5440a87..cd99c92 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -135,6 +135,12 @@
       <item quantity="one">آلہ <xliff:g id="NUMBER_0">%d</xliff:g> گھنٹہ سے غیر مقفل نہیں کیا گیا۔ پاسورڈ کی توثیق کریں۔</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"تسلیم شدہ نہیں ہے"</string>
-    <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
-    <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+    <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+      <item quantity="other">‏SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔</item>
+      <item quantity="one">‏SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_0">%d</xliff:g> کوشش بچی ہے، اس کے بعد آپ کو اپنا آلہ غیر مقفل کرنے کیلئے اپنے کیریئر سے رابطہ کرنا ہوگا۔</item>
+    </plurals>
+    <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+      <item quantity="other">‏SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item>
+      <item quantity="one">‏SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_0">%d</xliff:g> کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index ee11cff..204bb8b 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -140,7 +140,7 @@
       <item quantity="one">SIM PIN kodini kiriting, qurilmani qulfdan chiqarish uchun sizda <xliff:g id="NUMBER_0">%d</xliff:g> ta urinish bor.</item>
     </plurals>
     <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
-      <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
-      <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
+      <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
+      <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
     </plurals>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_cast.xml b/packages/SystemUI/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..b86dfea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cast.xml
@@ -0,0 +1,31 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M1 18v2c0 .55 .45 1 1 1h2c0-1.66-1.34-3-3-3zm0-2.94c-.01 .51 .32 .93 .82 1.02
+2.08 .36 3.74 2 4.1 4.08 .09 .48 .5 .84 .99 .84 .61 0 1.09-.54 1-1.14a6.996
+6.996 0 0 0-5.8-5.78c-.59-.09-1.09 .38 -1.11 .98 zm0-4.03c-.01 .52 .34 .96 .85
+1.01 4.26 .43 7.68 3.82 8.1 8.08 .05 .5 .48 .88 .99 .88 .59 0 1.06-.51
+1-1.1-.52-5.21-4.66-9.34-9.87-9.85-.57-.05-1.05 .4 -1.07 .98 zM21 3H3c-1.1 0-2
+.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker.xml b/packages/SystemUI/res/drawable/ic_speaker.xml
new file mode 100644
index 0000000..1ea293c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker.xml
@@ -0,0 +1,26 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_group.xml b/packages/SystemUI/res/drawable/ic_speaker_group.xml
new file mode 100644
index 0000000..d6867d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_group.xml
@@ -0,0 +1,32 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_tv.xml b/packages/SystemUI/res/drawable/ic_tv.xml
new file mode 100644
index 0000000..cc2ae91
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_tv.xml
@@ -0,0 +1,26 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index 22c3bcf..3d0ab35 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -19,6 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/output_chooser"
+    android:minWidth="320dp"
+    android:minHeight="320dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="20dp" >
@@ -39,12 +41,6 @@
         android:gravity="center"
         android:orientation="vertical">
 
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="56dp"
-            android:layout_height="56dp"
-            android:tint="?android:attr/textColorSecondary" />
-
         <TextView
             android:id="@android:id/title"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 43e88ba..3d09b74 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -29,14 +29,6 @@
     android:gravity="center_vertical"
     android:orientation="horizontal">
 
-    <include
-        android:id="@+id/date_time_alarm_group"
-        layout="@layout/status_bar_alarm_group"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="8dp"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent" />
-
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -44,6 +36,18 @@
         android:layout_marginEnd="8dp"
         android:gravity="end">
 
+        <com.android.keyguard.CarrierText
+            android:id="@+id/qs_carrier_text"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center_vertical|start"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textDirection="locale"
+            android:singleLine="true" />
+
         <com.android.systemui.statusbar.phone.MultiUserSwitch
             android:id="@+id/multi_user_switch"
             android:layout_width="48dp"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 2cf3e4a..739a255 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -28,32 +28,29 @@
     android:orientation="horizontal">
 
 
-    <com.android.keyguard.CarrierText
-        android:id="@+id/qs_carrier_text"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:gravity="center_vertical|start"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textDirection="locale"
-        android:singleLine="true" />
-
-    <com.android.systemui.BatteryMeterView android:id="@+id/battery"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        />
-
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
         android:textAppearance="@style/TextAppearance.StatusBar.Clock"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:singleLine="true"
-        android:paddingStart="@dimen/status_bar_clock_starting_padding"
-        android:paddingEnd="@dimen/status_bar_clock_end_padding"
+        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
         android:gravity="center_vertical|start"
         systemui:showDark="false"
+    />
+
+    <android.widget.Space
+        android:id="@+id/space"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:gravity="center_vertical|center_horizontal"
+    />
+
+    <com.android.systemui.BatteryMeterView android:id="@+id/battery"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:gravity="center_vertical|end"
         />
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 6de27ac..17b38cb 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -54,6 +54,17 @@
             android:layout_height="match_parent"
             android:layout="@layout/operator_name" />
 
+        <com.android.systemui.statusbar.policy.Clock
+            android:id="@+id/clock"
+            android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:singleLine="true"
+            android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+            android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+            android:gravity="center_vertical|start"
+        />
+
         <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
              PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
@@ -70,17 +81,6 @@
             >
 
             <include layout="@layout/system_icons" />
-
-            <com.android.systemui.statusbar.policy.Clock
-                android:id="@+id/clock"
-                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:singleLine="true"
-                android:paddingStart="@dimen/status_bar_clock_starting_padding"
-                android:paddingEnd="@dimen/status_bar_clock_end_padding"
-                android:gravity="center_vertical|start"
-                />
         </com.android.keyguard.AlphaOptimizedLinearLayout>
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index b9a228f..15a7d4c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dubbele multitoonfrekwensie"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Toeganklikheid"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Oproepe"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Lui"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreer"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Demp"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 961ac45..ee9acae 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ብሉቱዝ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ድርብ ባለ በርካታ ቅላጼ ድግምግሞሽ"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ተደራሽነት"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"ጥሪዎች"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ጥሪ"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ንዘር"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ድምጸ-ከል አድርግ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8437705..1d8cb4e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -500,6 +500,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"تردد ثنائي متعدد النغمات"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"إمكانية الوصول"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"المكالمات"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"استصدار رنين"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"اهتزاز"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"كتم الصوت"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ae3854f..fd42eb5 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Višestruka frekvencija dualnog tona"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Aktiviraj zvono"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriraj"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Isključi zvuk"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3c9f83a..e82dbff 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -498,6 +498,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Двухтанальны шматчастотны"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Спецыяльныя магчымасці"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Выклікі"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Званок"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вібрацыя"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Гук выключаны"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1cd4c91..ccdc44b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Тонално набиране"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Достъпност"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Обаждания"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Позвъняване"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибриране"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Без звук"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index c823525..96028f7 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ব্লুটুথ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ডুয়েল মাল্টি টোন ফ্রিকোয়েন্সি"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"অ্যাক্সেসযোগ্যতা"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"রিং"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ভাইব্রেট"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"মিউট"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। সশব্দ করতে আলতো চাপুন।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। কম্পন এ সেট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। নিঃশব্দ করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index e87bb45..dacf9d7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Marcatge per tons"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Accessibilitat"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Trucades"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Fes sonar"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibra"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silencia"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e0e14a1..4166697 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -498,6 +498,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tónová volba"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Přístupnost"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Volání"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Vyzvánění"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrace"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ztlumení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index fb886fc..52446f8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tonesignalfrekvens (DTMF)"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Hjælpefunktioner"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Opkald"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Slå lyden fra"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index feb7064..83a8e33 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -496,6 +496,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Doppelton-Mehrfrequenz"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Bedienungshilfen"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Anrufe"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Klingeln lassen"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrieren"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Stummschalten"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 1fe9010..9a61828 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Πολυσυχνότητα διπλού τόνου"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Προσβασιμότητα"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Κλήσεις"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Κουδούνισμα"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Δόνηση"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Σίγαση"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 4af3f59..d808119 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de tono doble"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Accesibilidad"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Llamadas"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hacer sonar"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 27e4920..3a5af25 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Kaks mitme tooniga sagedust"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Juurdepääsetavus"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Kõned"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Helisemine"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreerimine"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vaigistatud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 5d4181e..87348de 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth konexioa"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tonu anitzeko maiztasun duala"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Erabilerraztasuna"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Deiak"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jo tonua"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dardara"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ez jo tonua"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 9360c1d..bb77daa 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"فرکانس دوتایی چند نوایی"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"دسترس‌پذیری"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"تماس‌ها"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"زنگ زدن"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"لرزش"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"بی‌صدا"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 864f8d0..3bebe4b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Äänitaajuusvalinta"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Esteettömyys"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Puhelut"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Soittoääni"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Värinä"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Äänetön"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 5c4394b..a2dde85 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Fréquence double multi ton"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Accessibilité"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Appels"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonnerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sonnerie désactivée"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index a3d580b..9fbcdec 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -280,7 +280,7 @@
     <string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"Rotación bloqueada"</string>
     <string name="quick_settings_rotation_locked_portrait_label" msgid="5102691921442135053">"Vertical"</string>
     <string name="quick_settings_rotation_locked_landscape_label" msgid="8553157770061178719">"Horizontal"</string>
-    <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de entrada"</string>
+    <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de introdución de texto"</string>
     <string name="quick_settings_location_label" msgid="5011327048748762257">"Localización"</string>
     <string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localización desactivada"</string>
     <string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispositivo multimedia"</string>
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de dobre ton"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Accesibilidade"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Facer soar"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 83f7896..418f31e1 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"બ્લૂટૂથ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"દ્વિ બહુ ટોન આવર્તન"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ઍક્સેસિબિલિટી"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"રિંગ કરો"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"વાઇબ્રેટ"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"મ્યૂટ કરો"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. અનમ્યૂટ કરવા માટે ટૅપ કરો."</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. વાઇબ્રેટ પર સેટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e7fc696..1acd6e7 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -492,6 +492,8 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"दोहरी बहु टोन आवृत्ति"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"सुलभता"</string>
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+    <skip />
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"आवाज़ चालू है"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन (वाइब्रेशन)"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"आवाज़ बंद है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 88f5097..7ba9111 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zvuk je isključen"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e81df8f..8aa2c71 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Kéthangú többfrekvenciás jelzésátvitel (DTMF)"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Kisegítő lehetőségek"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Hívások"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Csörgés"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rezgés"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Néma"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 2994b9b..2841074 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Կրկնակի բազմերանգ հաճախականություն"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Մատչելիություն"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Զանգեր"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Սովորական"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Թրթռազանգ"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Անձայն"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 1efc249..66530bd 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tvítóna fjöltíðni"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Aðgengi"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Símtöl"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hringing"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titringur"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Hljóð af"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 2927116..cbbffa9 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -496,6 +496,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"‏טון זוגי מרובה תדרים (DTMF)"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"נגישות"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"שיחות"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"צלצול"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"רטט"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"השתקה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 38622c9..f840225 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"デュアルトーン マルチ周波数"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ユーザー補助機能"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"着信音"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"バイブレーション"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ミュート"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 3f36c72..a9e6ffe 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Қос үнді көп жиілік"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Арнайы мүмкіндіктер"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Қоңыраулар"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шылдырлау"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Діріл"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Дыбысын өшіру"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 1d5d7d4..5819f2a 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -492,9 +492,10 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ប៊្លូធូស"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ហ្វ្រេកង់ពហុសំឡេងទ្វេ"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ភាព​ងាយស្រួល"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"ហៅទូរសព្ទ"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"រោទ៍"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ញ័រ"</string>
-    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទ"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទសំឡេង"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s។ ប៉ះដើម្បីបើកសំឡេង។"</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s។ ប៉ះដើម្បីកំណត់ឲ្យញ័រ។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s។ ប៉ះដើម្បីបិទសំឡេង។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index b2e9d8f..5e7e1e2 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ಬ್ಲೂಟೂತ್‌"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ಡ್ಯುಯಲ್‌ ಬಹು ಟೋನ್ ಆವರ್ತನೆ"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ರಿಂಗ್"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ವೈಬ್ರೇಟ್‌"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ಮ್ಯೂಟ್"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ಅನ್‌ಮ್ಯೂಟ್‌ ಮಾಡುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. ಕಂಪನಕ್ಕೆ ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್‌ ಮಾಡಬಹುದು."</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್‌ ಮಾಡಬಹುದು."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 83018be..d86c32f 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"블루투스"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"듀얼 멀티 톤 주파수"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"접근성"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"통화"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"벨소리"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"진동"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"음소거"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 4aaae3c..e10e2b0 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Көп тондуу жыштык"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Атайын мүмкүнчүлүктөр"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Чалуулар"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шыңгыратуу"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Дирилдөө"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Үнсүз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index bc1e5b9..2587a20 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ບຣູທູດ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"​ການ​ຊ່ວຍ​ເຂົ້າ​ເຖິງ"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"ການໂທ"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"​ເຕືອນ​ດ້ວຍ​ສຽງ"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ສັ່ນເຕືອນ"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ປິດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 53ddcb3..1c97452 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -496,6 +496,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dvigubas kelių tonų dažnis"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Pritaikymas neįgaliesiems"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Skambučiai"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Skambinti"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibruoti"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Nutildyti"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index b1c3db5..c14eb3f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Divtoņu daudzfrekvenču signalizācija"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Pieejamība"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Zvani"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvanīt"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrēt"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Izslēgt skaņu"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 3986e16..e30085c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Двојна повеќетонска фреквенција"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Пристапност"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Повици"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ѕвони"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрации"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Исклучи звук"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 3dfde13..385331e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ബ്ലൂടൂത്ത്"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ഡ്യുവൽ മൾട്ടി റ്റോൺ ഫ്രീക്വൻസി"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ഉപയോഗസഹായി"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"റിംഗ് ചെയ്യുക"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"വൈബ്രേറ്റ് ചെയ്യുക"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"മ്യൂട്ട് ചെയ്യുക"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. അൺമ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 9f09dad..480956c 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -490,6 +490,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Олон дууны давтамж"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Хүртээмж"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Дуудлага"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Хонх дуугаргах"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Чичиргэх"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Хаах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 5b0984d..8a0d661 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -492,6 +492,8 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"दुहेरी एकाधिक टोन वारंंवारता"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"प्रवेशयोग्यता"</string>
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+    <skip />
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"रिंग करा"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 2907761..e074875 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Multifrekuensi dwinada"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Kebolehaksesan"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Panggilan"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dering"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Getar"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Redam"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 51b3b0d..661df03 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"DTMF (dual-tone multi-frequency)"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Tilgjengelighet"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Anrop"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrer"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ignorer"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 0a63ce2..dce9cc4 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -492,6 +492,8 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लुटुथ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"दोहोरो बहु टोनको फ्रिक्वेन्सी"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"पहुँच"</string>
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+    <skip />
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"घन्टी"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कम्पन"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index c6475dc..43503d6 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ਬਲੂਟੁੱਥ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"ਦੂਹਰੀ ਮਲਟੀ ਟੋਨ ਆਵਰਤੀ"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ਪਹੁੰਚਯੋਗਤਾ"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ਘੰਟੀ"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ਥਰਥਰਾਹਟ"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ਮਿਊਟ"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। ਅਣਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। ਥਰਥਰਾਹਟ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index bc2a954..e567e6ba 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -496,6 +496,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Ułatwienia dostępu"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Połączenia"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dzwonek"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Wibracje"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Wyciszenie"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 7f2c643..681d3e4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Multifrequência de duas tonalidades"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Acessibilidade"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Toque"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Desativar som"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 2108455..2a0744e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -496,6 +496,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Frecvență tonuri multiple duale"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Accesibilitate"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Apeluri"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrații"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Blocați"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 151c2b4..694695c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -498,6 +498,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dvojtónová multifrekvencia"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Dostupnosť"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Hovory"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Prezvoniť"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrovať"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vypnúť zvuk"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ee628e1..10d0104 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -498,6 +498,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dvojna večtonska frekvenca"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Funkcije za ljudi s posebnimi potrebami"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Klici"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Utišano"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 7641e6c..665177f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Frekuenca e dyfishtë me shumë tone"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Qasshmëria"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Telefonatat"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Bjeri ziles"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dridhje"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Pa zë"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index b5fab0d..cef0f69 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Вишеструка фреквенција дуалног тона"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Приступачност"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Позиви"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Активирај звоно"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрирај"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Искључи звук"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index cc6f542..c8f8db4 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tonval"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Tillgänglighet"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Samtal"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ringsignal"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Dölj"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b83f10f..8fa370d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Masafa ya ishara ya kampuni ya simu"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Zana za walio na matatizo ya kuona au kusikia"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Simu"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Piga"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tetema"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zima sauti"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9d532e2..6568381 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"புளூடூத்"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"டூயல் டோன் மல்டி ஃப்ரீக்வென்சி"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"அணுகல்தன்மை"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ஒலி"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"அதிர்வு"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"அமைதி"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ஒலி இயக்க, தட்டவும்."</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ஒலியடக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
@@ -762,7 +761,7 @@
     <string name="instant_apps" msgid="6647570248119804907">"இன்ஸ்டண்ட் பயன்பாடுகள்"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"இன்ஸ்டண்ட் பயன்பாடுகளுக்கு நிறுவல் தேவையில்லை."</string>
     <string name="app_info" msgid="6856026610594615344">"ஆப்ஸ் தகவல்"</string>
-    <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குக்குச் செல்"</string>
+    <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குச் செல்"</string>
     <string name="mobile_data" msgid="7094582042819250762">"மொபைல் டேட்டா"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"வைஃபை முடக்கத்தில் உள்ளது"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"புளூடூத் முடக்கத்தில் உள்ளது"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f1c66f5..fa7cb09 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"బ్లూటూత్"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"డ్యూయల్ మల్టీ టోన్ ఫ్రీక్వెన్సీ"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"యాక్సెస్ సామర్థ్యం"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"రింగ్"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"వైబ్రేట్"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"మ్యూట్"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. అన్‌మ్యూట్ చేయడానికి నొక్కండి."</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. వైబ్రేషన్‌కు సెట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. మ్యూట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ea61018..d43efdd 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"บลูทูธ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"การส่งสัญญาณเสียงแบบ 2 เสียงพร้อมกัน"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"การเข้าถึง"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"การโทร"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ทำให้ส่งเสียง"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"สั่น"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ปิดเสียง"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index cdf28d6..4aa9ca4 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Pagiging Naa-access"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Mga Tawag"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ipa-ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"I-vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"I-mute"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 5dc81a1..d028fdd 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Çift ton çoklu frekans"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Erişilebilirlik"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Çağrılar"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zili çaldır"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titreşim"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sesi kapat"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 6498088..73fd5886 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -492,12 +492,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوٹوتھ"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"دوہری ملٹی ٹون فریکوئنسی"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"ایکسیسبیلٹی"</string>
-    <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
     <skip />
-    <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
-    <skip />
-    <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
-    <skip />
+    <string name="volume_ringer_status_normal" msgid="4273142424125855384">"رِنگ کریں"</string>
+    <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"وائبریٹ"</string>
+    <string name="volume_ringer_status_silent" msgid="6896394161022916369">"خاموش کریں"</string>
     <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"‏‎%1$s۔ آواز چالو کرنے کیلئے تھپتھپائیں۔"</string>
     <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"‏‎%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"‏‎%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 273079d..55eb4fb 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Ikkitali ko‘pchastotali ovoz"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Maxsus imkoniyatlar"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Chaqiruvlar"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jiringlatish"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tebranish"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ovozsiz"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index df06298..551decf 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Tần số đa chuông kép"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Trợ năng"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"Cuộc gọi"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Đổ chuông"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rung"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Tắt tiếng"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index f7999a6..2b64d86 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -492,6 +492,8 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"蓝牙"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"双音多频"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"无障碍"</string>
+    <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+    <skip />
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"响铃"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"振动"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"静音"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 188ad7c..382425d 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -494,6 +494,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻訊號"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"無障礙功能"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 75f8015..ef9ea43 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -492,6 +492,7 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"協助工具"</string>
+    <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
     <string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string>
     <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string>
     <string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0715d49..60e9ebf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -48,6 +48,12 @@
     <!-- The end padding for the clock in the status bar. -->
     <dimen name="status_bar_clock_end_padding">0dp</dimen>
 
+    <!-- Starting padding for a left-aligned status bar clock -->
+    <dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
+
+    <!-- End padding for left-aligned status bar clock -->
+    <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
     <dimen name="status_bar_wifi_signal_spacer_width">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 78e621e..fd205dd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1269,6 +1269,14 @@
     <string name="volume_dialog_accessibility_shown_message">%s volume controls shown. Swipe up to dismiss.</string>
     <string name="volume_dialog_accessibility_dismissed_message">Volume controls hidden</string>
 
+    <string name="output_title">Media output</string>
+    <string name="output_calls_title">Phone call output</string>
+    <string name="output_none_found">No devices found</string>
+    <string name="output_none_found_service_off">No devices found. Try turning on <xliff:g id="service" example="Bluetooth">%1$s</xliff:g></string>
+    <string name="output_service_bt">Bluetooth</string>
+    <string name="output_service_wifi">Wi-Fi</string>
+    <string name="output_service_bt_wifi">Bluetooth and Wi-Fi</string>
+
     <!-- Name of special SystemUI debug settings -->
     <string name="system_ui_tuner">System UI Tuner</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index eb2d12e..1c99d38 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -413,15 +413,4 @@
             Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
         }
     }
-
-    /**
-     * Cancels the current thumbnail transtion to/from Recents for the given task id.
-     */
-    public void cancelThumbnailTransition(int taskId) {
-        try {
-            ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d58b69d..2058f15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,6 +71,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
@@ -1111,7 +1112,8 @@
         }
     }
 
-    private KeyguardUpdateMonitor(Context context) {
+    @VisibleForTesting
+    protected KeyguardUpdateMonitor(Context context) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 12f75bb..d3dded0 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -31,6 +31,7 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.InputType;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -78,6 +79,8 @@
      */
     private static final float OVERSHOOT_TIME_POSITION = 0.5f;
 
+    private static char DOT = '\u2022';
+
     /**
      * The raw text size, will be multiplied by the scaled density when drawn
      */
@@ -208,7 +211,7 @@
 
     public void append(char c) {
         int visibleChars = mTextChars.size();
-        String textbefore = mText;
+        CharSequence textbefore = getTransformedText();
         mText = mText + c;
         int newLength = mText.length();
         CharState charState;
@@ -245,7 +248,7 @@
 
     public void deleteLastChar() {
         int length = mText.length();
-        String textbefore = mText;
+        CharSequence textbefore = getTransformedText();
         if (length > 0) {
             mText = mText.substring(0, length - 1);
             CharState charState = mTextChars.get(length - 1);
@@ -259,6 +262,21 @@
         return mText;
     }
 
+    private CharSequence getTransformedText() {
+        int textLength = mTextChars.size();
+        StringBuilder stringBuilder = new StringBuilder(textLength);
+        for (int i = 0; i < textLength; i++) {
+            CharState charState = mTextChars.get(i);
+            // If the dot is disappearing, the character is disappearing entirely. Consider
+            // it gone.
+            if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) {
+                continue;
+            }
+            stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT);
+        }
+        return stringBuilder;
+    }
+
     private CharState obtainCharState(char c) {
         CharState charState;
         if(mCharPool.isEmpty()) {
@@ -272,7 +290,7 @@
     }
 
     public void reset(boolean animated, boolean announce) {
-        String textbefore = mText;
+        CharSequence textbefore = getTransformedText();
         mText = "";
         int length = mTextChars.size();
         int middleIndex = (length - 1) / 2;
@@ -305,7 +323,7 @@
         }
     }
 
-    void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
+    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
                                                    int removedCount, int addedCount) {
         if (AccessibilityManager.getInstance(mContext).isEnabled() &&
                 (isFocused() || isSelected() && isShown())) {
@@ -315,6 +333,10 @@
             event.setRemovedCount(removedCount);
             event.setAddedCount(addedCount);
             event.setBeforeText(beforeText);
+            CharSequence transformedText = getTransformedText();
+            if (!TextUtils.isEmpty(transformedText)) {
+                event.getText().add(transformedText);
+            }
             event.setPassword(true);
             sendAccessibilityEventUnchecked(event);
         }
@@ -332,8 +354,9 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
 
-        info.setClassName(PasswordTextView.class.getName());
+        info.setClassName(EditText.class.getName());
         info.setPassword(true);
+        info.setText(getTransformedText());
 
         info.setEditable(true);
 
@@ -420,7 +443,19 @@
                 = new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
+                boolean textVisibleBefore = isCharVisibleForA11y();
+                float beforeTextSizeFactor = currentTextSizeFactor;
                 currentTextSizeFactor = (float) animation.getAnimatedValue();
+                if (textVisibleBefore != isCharVisibleForA11y()) {
+                    currentTextSizeFactor = beforeTextSizeFactor;
+                    CharSequence beforeText = getTransformedText();
+                    currentTextSizeFactor = (float) animation.getAnimatedValue();
+                    int indexOfThisChar = mTextChars.indexOf(CharState.this);
+                    if (indexOfThisChar >= 0) {
+                        sendAccessibilityEventTypeViewTextChanged(
+                                beforeText, indexOfThisChar, 1, 1);
+                    }
+                }
                 invalidate();
             }
         };
@@ -673,5 +708,13 @@
             }
             return charWidth + mCharPadding * currentWidthFactor;
         }
+
+        public boolean isCharVisibleForA11y() {
+            // The text has size 0 when it is first added, but we want to count it as visible if
+            // it will become visible presently. Count text as visible if an animator
+            // is configured to make it grow.
+            boolean textIsGrowing = textAnimator != null && textAnimationIsGrowing;
+            return (currentTextSizeFactor > 0) || textIsGrowing;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index b194de4..eff84c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -76,6 +76,12 @@
     void playTrustedSound();
 
     /**
+     * When the bouncer is shown or hides
+     * @param shown
+     */
+    void onBouncerVisiblityChanged(boolean shown);
+
+    /**
      * @return true if the screen is on
      */
     boolean isScreenOn();
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index edd1748..6aa465c 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -24,6 +24,7 @@
 import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
+import android.graphics.Region;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -36,7 +37,8 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Emulates a display cutout by drawing its shape in an overlay as supplied by
@@ -85,6 +87,7 @@
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
         lp.setTitle("EmulatedDisplayCutout");
         lp.gravity = Gravity.TOP;
         return lp;
@@ -102,9 +105,8 @@
     };
 
     private static class CutoutView extends View {
-        private Paint mPaint = new Paint();
-        private Path mPath = new Path();
-        private ArrayList<Point> mBoundingPolygon = new ArrayList<>();
+        private final Paint mPaint = new Paint();
+        private final Path mBounds = new Path();
 
         CutoutView(Context context) {
             super(context);
@@ -112,28 +114,22 @@
 
         @Override
         public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-            insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon);
+            if (insets.getDisplayCutout() != null) {
+                insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds);
+            } else {
+                mBounds.reset();
+            }
             invalidate();
-            return insets.consumeCutout();
+            return insets.consumeDisplayCutout();
         }
 
         @Override
         protected void onDraw(Canvas canvas) {
-            if (!mBoundingPolygon.isEmpty()) {
+            if (!mBounds.isEmpty()) {
                 mPaint.setColor(Color.DKGRAY);
                 mPaint.setStyle(Paint.Style.FILL);
 
-                mPath.reset();
-                for (int i = 0; i < mBoundingPolygon.size(); i++) {
-                    Point point = mBoundingPolygon.get(i);
-                    if (i == 0) {
-                        mPath.moveTo(point.x, point.y);
-                    } else {
-                        mPath.lineTo(point.x, point.y);
-                    }
-                }
-                mPath.close();
-                canvas.drawPath(mPath, mPaint);
+                canvas.drawPath(mBounds, mPaint);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index ebeb351..0f3daf5 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,32 +16,42 @@
 
 package com.android.systemui;
 
+import android.app.AlarmManager;
 import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency.DependencyProvider;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.util.function.Consumer;
 
@@ -89,10 +99,10 @@
 
     public ScrimController createScrimController(LightBarController lightBarController,
             ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-            LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
-            DozeParameters dozeParameters) {
+            LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener,
+            DozeParameters dozeParameters, AlarmManager alarmManager) {
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                scrimVisibleListener, dozeParameters);
+                scrimVisibleListener, dozeParameters, alarmManager);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
@@ -114,10 +124,16 @@
             Context context) {
         providers.put(NotificationLockscreenUserManager.class,
                 () -> new NotificationLockscreenUserManager(context));
-        providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
-                Dependency.get(NotificationLockscreenUserManager.class), context));
+        providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
+        providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
+        providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
+        providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
         providers.put(NotificationRemoteInputManager.class,
-                () -> new NotificationRemoteInputManager(
-                        Dependency.get(NotificationLockscreenUserManager.class), context));
+                () -> new NotificationRemoteInputManager(context));
+        providers.put(NotificationListener.class, () -> new NotificationListener(context));
+        providers.put(NotificationLogger.class, NotificationLogger::new);
+        providers.put(NotificationViewHierarchyManager.class,
+                () -> new NotificationViewHierarchyManager(context));
+        providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
new file mode 100644
index 0000000..a89a8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.car;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationEntryManager;
+
+public class CarNotificationEntryManager extends NotificationEntryManager {
+    public CarNotificationEntryManager(Context context) {
+        super(context);
+    }
+
+    /**
+     * Returns the
+     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+     * be triggered when a notification card is long-pressed.
+     */
+    @Override
+    public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        // For the automative use case, we do not want to the user to be able to interact with
+        // a notification other than a regular click. As a result, just return null for the
+        // long click listener.
+        return null;
+    }
+
+    @Override
+    public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+        // Because space is usually constrained in the auto use-case, there should not be a
+        // pinned notification when the shade has been expanded. Ensure this by not pinning any
+        // notification if the shade is already opened.
+        if (!mPresenter.isPresenterFullyCollapsed()) {
+            return false;
+        }
+
+        return super.shouldPeek(entry, sbn);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 5a19e7d..174584d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -18,9 +18,22 @@
 import android.content.Context;
 import android.util.ArrayMap;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
 import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.volume.car.CarVolumeDialogController;
 
 /**
@@ -32,5 +45,7 @@
             Context context) {
         super.injectDependencies(providers, context);
         providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
+        providers.put(NotificationEntryManager.class,
+                () -> new CarNotificationEntryManager(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index cc2244a..8515bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -40,12 +40,16 @@
     private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
     private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
     private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400;
 
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
     static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
     static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+    static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout";
+    static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration";
 
     /**
      * Integer array to map ambient brightness type to real screen brightness.
@@ -89,6 +93,24 @@
      */
     public long proxCooldownPeriodMs;
 
+    /**
+     * For how long(ms) the wallpaper should still be visible
+     * after entering AoD.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_WALLPAPER_VISIBILITY_MS
+     */
+    public long wallpaperVisibilityDuration;
+
+    /**
+     * Duration(ms) of the fade out animation after
+     * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_WALLPAPER_FADE_OUT_MS
+     */
+    public long wallpaperFadeOutDuration;
+
     private final KeyValueListParser mParser;
     private final Context mContext;
     private SettingsObserver mSettingsObserver;
@@ -138,6 +160,10 @@
                         DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
                 proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
                         DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS,
+                        DEFAULT_WALLPAPER_FADE_OUT_MS);
+                wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS,
+                        DEFAULT_WALLPAPER_VISIBILITY_MS);
                 screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
                         resources.getIntArray(
                                 R.array.config_doze_brightness_sensor_to_brightness));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index a409fcb..0f0402d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -66,6 +66,7 @@
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
                 new DozeScreenState(wrappedService, handler),
                 createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
+                new DozeWallpaperState()
         });
 
         return machine;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 6650cc6..34d3928 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -24,9 +24,10 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.DozeServicePlugin;
-import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
new file mode 100644
index 0000000..ee41001
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import android.app.IWallpaperManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+/**
+ * Propagates doze state to wallpaper engine.
+ */
+public class DozeWallpaperState implements DozeMachine.Part {
+
+    private static final String TAG = "DozeWallpaperState";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @VisibleForTesting
+    final IWallpaperManager mWallpaperManagerService;
+    private boolean mIsAmbientMode;
+
+    public DozeWallpaperState() {
+        this(IWallpaperManager.Stub.asInterface(
+                ServiceManager.getService(Context.WALLPAPER_SERVICE)));
+    }
+
+    @VisibleForTesting
+    DozeWallpaperState(IWallpaperManager wallpaperManagerService) {
+        mWallpaperManagerService = wallpaperManagerService;
+    }
+
+    @Override
+    public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+        final boolean isAmbientMode;
+        switch (newState) {
+            case DOZE_AOD:
+            case DOZE_AOD_PAUSING:
+            case DOZE_AOD_PAUSED:
+            case DOZE_REQUEST_PULSE:
+            case DOZE_PULSING:
+            case DOZE_PULSE_DONE:
+                isAmbientMode = true;
+                break;
+            default:
+                isAmbientMode = false;
+        }
+
+        if (isAmbientMode != mIsAmbientMode) {
+            mIsAmbientMode = isAmbientMode;
+            try {
+                Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode);
+                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode);
+            } catch (RemoteException e) {
+                // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+                Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
+            }
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("DozeWallpaperState:");
+        pw.println(" isAmbientMode: " + mIsAmbientMode);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8fa66e0f..91ae448 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -613,6 +613,13 @@
         }
 
         @Override
+        public void onBouncerVisiblityChanged(boolean shown) {
+            synchronized (KeyguardViewMediator.this) {
+                adjustStatusBarLocked(shown);
+            }
+        }
+
+        @Override
         public void playTrustedSound() {
             KeyguardViewMediator.this.playTrustedSound();
         }
@@ -865,7 +872,7 @@
 
         // From DevicePolicyAdmin
         final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
-                .getMaximumTimeToLockForUserAndProfiles(userId);
+                .getMaximumTimeToLock(null, userId);
 
         long timeout;
 
@@ -1862,6 +1869,10 @@
     }
 
     private void adjustStatusBarLocked() {
+        adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */);
+    }
+
+    private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) {
         if (mStatusBarManager == null) {
             mStatusBarManager = (StatusBarManager)
                     mContext.getSystemService(Context.STATUS_BAR_SERVICE);
@@ -1872,19 +1883,14 @@
             // Disable aspects of the system/status/navigation bars that must not be re-enabled by
             // windows that appear on top, ever
             int flags = StatusBarManager.DISABLE_NONE;
-            if (mShowing) {
-                // Permanently disable components not available when keyguard is enabled
-                // (like recents). Temporary enable/disable (e.g. the "back" button) are
-                // done in KeyguardHostView.
-                flags |= StatusBarManager.DISABLE_RECENT;
-            }
-            if (isShowingAndNotOccluded()) {
-                flags |= StatusBarManager.DISABLE_HOME;
+            if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
+                flags |= StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT;
             }
 
             if (DEBUG) {
                 Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
-                        + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags));
+                        + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
+                        +  " --> flags=0x" + Integer.toHexString(flags));
             }
 
             mStatusBarManager.disable(flags);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 2963506..db999c4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -198,20 +198,6 @@
      * Expands the PIP.
      */
     public final void onBusEvent(ExpandPipEvent event) {
-        if (event.clearThumbnailWindows) {
-            try {
-                StackInfo stackInfo = mActivityManager.getStackInfo(
-                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-                if (stackInfo != null && stackInfo.taskIds != null) {
-                    ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
-                    for (int taskId : stackInfo.taskIds) {
-                        am.cancelThumbnailTransition(taskId);
-                    }
-                }
-            } catch (RemoteException e) {
-                // Do nothing
-            }
-        }
         mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 8d50d4b..0b7b6d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -72,30 +72,22 @@
 import com.android.systemui.tuner.TunerService;
 
 public class QSFooterImpl extends FrameLayout implements QSFooter,
-        NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
+        OnClickListener, OnUserInfoChangedListener, EmergencyListener,
         SignalCallback, CommandQueue.Callbacks {
     private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
 
     private ActivityStarter mActivityStarter;
-    private NextAlarmController mNextAlarmController;
     private UserInfoController mUserInfoController;
     private SettingsButton mSettingsButton;
     protected View mSettingsContainer;
 
-    private TextView mAlarmStatus;
-    private View mAlarmStatusCollapsed;
-    private View mDate;
-
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
 
     private boolean mExpanded;
-    private boolean mAlarmShowing;
-
     protected ExpandableIndicator mExpandIndicator;
 
     private boolean mListening;
-    private AlarmManager.AlarmClockInfo mNextAlarm;
 
     private boolean mShowEmergencyCallsOnly;
     protected MultiUserSwitch mMultiUserSwitch;
@@ -106,9 +98,6 @@
 
     protected View mEdit;
     private TouchAnimator mAnimator;
-    private View mDateTimeGroup;
-    private boolean mKeyguardShowing;
-    private TouchAnimator mAlarmAnimator;
 
     public QSFooterImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -124,18 +113,11 @@
                 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
                         mQsPanel.showEdit(view)));
 
-        mDateTimeGroup = findViewById(id.date_time_alarm_group);
-        mDate = findViewById(R.id.date);
-
         mExpandIndicator = findViewById(R.id.expand_indicator);
         mSettingsButton = findViewById(R.id.settings_button);
         mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
 
-        mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
-        mAlarmStatus = findViewById(R.id.alarm_status);
-        mDateTimeGroup.setOnClickListener(this);
-
         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
 
@@ -146,7 +128,6 @@
 
         updateResources();
 
-        mNextAlarmController = Dependency.get(NextAlarmController.class);
         mUserInfoController = Dependency.get(UserInfoController.class);
         mActivityStarter = Dependency.get(ActivityStarter.class);
         addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
@@ -165,28 +146,7 @@
                         isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
                 .addFloat(mSettingsButton, "rotation", -120, 0)
                 .build();
-        if (mAlarmShowing) {
-            int translate = isLayoutRtl() ? mDate.getWidth() : -mDate.getWidth();            
-            mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
-                    .addFloat(mDateTimeGroup, "translationX", 0, translate)
-                    .addFloat(mAlarmStatus, "alpha", 0, 1)
-                    .setListener(new ListenerAdapter() {
-                        @Override
-                        public void onAnimationAtStart() {
-                            mAlarmStatus.setVisibility(View.GONE);
-                        }
 
-                        @Override
-                        public void onAnimationStarted() {
-                            mAlarmStatus.setVisibility(View.VISIBLE);
-                        }
-                    }).build();
-        } else {
-            mAlarmAnimator = null;
-            mAlarmStatus.setVisibility(View.GONE);
-            mDate.setAlpha(1);
-            mDateTimeGroup.setTranslationX(0);
-        }
         setExpansion(mExpansionAmount);
     }
 
@@ -203,27 +163,11 @@
     }
 
     private void updateResources() {
-        FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
-
         updateSettingsAnimator();
     }
 
     private void updateSettingsAnimator() {
         mSettingsAlpha = createSettingsAlphaAnimator();
-
-        final boolean isRtl = isLayoutRtl();
-        if (isRtl && mDate.getWidth() == 0) {
-            mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    mDate.setPivotX(getWidth());
-                    mDate.removeOnLayoutChangeListener(this);
-                }
-            });
-        } else {
-            mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
-        }
     }
 
     @Nullable
@@ -236,7 +180,6 @@
 
     @Override
     public void setKeyguardShowing(boolean keyguardShowing) {
-        mKeyguardShowing = keyguardShowing;
         setExpansion(mExpansionAmount);
     }
 
@@ -248,36 +191,14 @@
     }
 
     @Override
-    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
-        mNextAlarm = nextAlarm;
-        if (nextAlarm != null) {
-            String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
-            mAlarmStatus.setText(alarmString);
-            mAlarmStatus.setContentDescription(mContext.getString(
-                    R.string.accessibility_quick_settings_alarm, alarmString));
-            mAlarmStatusCollapsed.setContentDescription(mContext.getString(
-                    R.string.accessibility_quick_settings_alarm, alarmString));
-        }
-        if (mAlarmShowing != (nextAlarm != null)) {
-            mAlarmShowing = nextAlarm != null;
-            updateAnimator(getWidth());
-            updateEverything();
-        }
-    }
-
-    @Override
     public void setExpansion(float headerExpansionFraction) {
         mExpansionAmount = headerExpansionFraction;
         if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
-        if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
-                mKeyguardShowing ? 0 : headerExpansionFraction);
 
         if (mSettingsAlpha != null) {
             mSettingsAlpha.setPosition(headerExpansionFraction);
         }
 
-        updateAlarmVisibilities();
-
         mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
     }
 
@@ -295,10 +216,6 @@
         super.onDetachedFromWindow();
     }
 
-    private void updateAlarmVisibilities() {
-        mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
-    }
-
     @Override
     public void setListening(boolean listening) {
         if (listening == mListening) {
@@ -329,8 +246,6 @@
     }
 
     private void updateVisibilities() {
-        updateAlarmVisibilities();
-
         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
@@ -349,14 +264,12 @@
 
     private void updateListeners() {
         if (mListening) {
-            mNextAlarmController.addCallback(this);
             mUserInfoController.addCallback(this);
             if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
                 Dependency.get(NetworkController.class).addEmergencyListener(this);
                 Dependency.get(NetworkController.class).addCallback(this);
             }
         } else {
-            mNextAlarmController.removeCallback(this);
             mUserInfoController.removeCallback(this);
             Dependency.get(NetworkController.class).removeEmergencyListener(this);
             Dependency.get(NetworkController.class).removeCallback(this);
@@ -400,16 +313,6 @@
             } else {
                 startSettingsActivity();
             }
-        } else if (v == mDateTimeGroup) {
-            Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
-                    mNextAlarm != null);
-            if (mNextAlarm != null) {
-                PendingIntent showIntent = mNextAlarm.getShowIntent();
-                mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
-            } else {
-                mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
-                        AlarmClock.ACTION_SHOW_ALARMS), 0);
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ca9a553..06dfd18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -709,7 +709,6 @@
                 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
             ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
             am.cancelWindowTransition(launchState.launchedToTaskId);
-            am.cancelThumbnailTransition(getTaskId());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 663f206..8359690 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -24,6 +24,8 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.KeyguardManager;
+import android.app.trust.TrustManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +34,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.os.AsyncTask.Status;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -225,6 +228,7 @@
         }
     };
 
+    private TrustManager mTrustManager;
     protected Context mContext;
     protected Handler mHandler;
     TaskStackListenerImpl mTaskStackListener;
@@ -271,6 +275,8 @@
         // Initialize the static configuration resources
         mDummyStackView = new TaskStackView(mContext);
         reloadResources();
+
+        mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
     }
 
     public void onBootCompleted() {
@@ -309,8 +315,7 @@
      * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
      */
     public void onStartScreenPinning(Context context, int taskId) {
-        SystemUIApplication app = (SystemUIApplication) context;
-        StatusBar statusBar = app.getComponent(StatusBar.class);
+        final StatusBar statusBar = getStatusBar();
         if (statusBar != null) {
             statusBar.showScreenPinningRequest(taskId, false);
         }
@@ -350,8 +355,8 @@
             if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
                 ActivityManager.RunningTaskInfo runningTask =
                         ActivityManagerWrapper.getInstance().getRunningTask();
-                startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
-                        growTarget);
+                startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
+                        isHomeStackVisible.value || fromHome, animate, growTarget);
             }
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to launch RecentsActivity", e);
@@ -442,8 +447,8 @@
                 // Otherwise, start the recents activity
                 ActivityManager.RunningTaskInfo runningTask =
                         ActivityManagerWrapper.getInstance().getRunningTask();
-                startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
-                        growTarget);
+                startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
+                        isHomeStackVisible.value, true /* animate */, growTarget);
 
                 // Only close the other system windows if we are actually showing recents
                 ActivityManagerWrapper.getInstance().closeSystemWindows(
@@ -462,6 +467,12 @@
             return;
         }
 
+        // Skip preloading recents when keyguard is showing
+        final StatusBar statusBar = getStatusBar();
+        if (statusBar != null && statusBar.isKeyguardShowing()) {
+            return;
+        }
+
         // Preload only the raw task list into a new load plan (which will be consumed by the
         // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
         // don't block the touch feedback on the nav bar button which triggers this.
@@ -942,9 +953,27 @@
     }
 
     /**
-     * Shows the recents activity
+     * Shows the recents activity after dismissing the keyguard if visible
      */
-    protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
+    protected void startRecentsActivityAndDismissKeyguardIfNeeded(
+            final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible,
+            final boolean animate, final int growTarget) {
+        // Preload only if device for current user is unlocked
+        final StatusBar statusBar = getStatusBar();
+        if (statusBar != null && statusBar.isKeyguardShowing()) {
+            statusBar.executeRunnableDismissingKeyguard(() -> {
+                    // Flush trustmanager before checking device locked per user when preloading
+                    mTrustManager.reportKeyguardShowingChanged();
+                    mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible,
+                            animate, growTarget));
+                }, null,  true /* dismissShade */, false /* afterKeyguardGone */,
+                true /* deferred */);
+        } else {
+            startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget);
+        }
+    }
+
+    private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
             boolean isHomeStackVisible, boolean animate, int growTarget) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
@@ -1033,6 +1062,10 @@
         return result;
     }
 
+    private StatusBar getStatusBar() {
+        return ((SystemUIApplication) mContext).getComponent(StatusBar.class);
+    }
+
     /**
      * Starts the recents activity.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
index 8fe4975..37266f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
@@ -22,5 +22,4 @@
  * This is sent when the PiP should be expanded due to being relaunched.
  */
 public class ExpandPipEvent extends EventBus.Event {
-    public final boolean clearThumbnailWindows = true;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 0000000..6bbd09f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+        .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+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.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+        ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+        VisualStabilityManager.Callback {
+    private static final String TAG = "NotificationEntryManager";
+    protected static final boolean DEBUG = false;
+    protected static final boolean ENABLE_HEADS_UP = true;
+    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+    protected final NotificationMessagingUtil mMessagingUtil;
+    protected final Context mContext;
+    protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+    protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+    protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+            new ArraySet<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final NotificationGutsManager mGutsManager =
+            Dependency.get(NotificationGutsManager.class);
+    protected final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+    protected final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+    protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    protected final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+    protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    protected final ForegroundServiceController mForegroundServiceController =
+            Dependency.get(ForegroundServiceController.class);
+    protected final NotificationListener mNotificationListener =
+            Dependency.get(NotificationListener.class);
+
+    protected IStatusBarService mBarService;
+    protected NotificationPresenter mPresenter;
+    protected Callback mCallback;
+    protected PowerManager mPowerManager;
+    protected SystemServicesProxy mSystemServicesProxy;
+    protected NotificationListenerService.RankingMap mLatestRankingMap;
+    protected HeadsUpManager mHeadsUpManager;
+    protected NotificationData mNotificationData;
+    protected ContentObserver mHeadsUpObserver;
+    protected boolean mUseHeadsUp = false;
+    protected boolean mDisableNotificationAlerts;
+    protected NotificationListContainer mListContainer;
+
+    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;
+            }
+
+            mPresenter.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 menu, if so slide notification back
+            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+                row.animateTranslateNotification(0);
+                return;
+            }
+
+            // Mark notification for one frame.
+            row.setJustClicked(true);
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+            mCallback.onNotificationClicked(sbn, row);
+        }
+
+        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);
+            }
+        }
+    }
+
+    private final DeviceProvisionedController.DeviceProvisionedListener
+            mDeviceProvisionedListener =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    updateNotifications();
+                }
+            };
+
+    public NotificationListenerService.RankingMap getLatestRankingMap() {
+        return mLatestRankingMap;
+    }
+
+    public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+        mLatestRankingMap = latestRankingMap;
+    }
+
+    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+        mDisableNotificationAlerts = disableNotificationAlerts;
+        mHeadsUpObserver.onChange(true);
+    }
+
+    public void destroy() {
+        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+    }
+
+    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            removeNotification(entry.key, getLatestRankingMap());
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+                setLatestRankingMap(null);
+            }
+        } else {
+            updateNotificationRanking(null);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationEntryManager state:");
+        pw.print("  mPendingNotifications=");
+        if (mPendingNotifications.size() == 0) {
+            pw.println("null");
+        } else {
+            for (NotificationData.Entry entry : mPendingNotifications.values()) {
+                pw.println(entry.notification);
+            }
+        }
+        pw.print("  mUseHeadsUp=");
+        pw.println(mUseHeadsUp);
+    }
+
+    public NotificationEntryManager(Context context) {
+        mContext = context;
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mMessagingUtil = new NotificationMessagingUtil(context);
+        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationListContainer listContainer, Callback callback,
+            HeadsUpManager headsUpManager) {
+        mPresenter = presenter;
+        mCallback = callback;
+        mNotificationData = new NotificationData(presenter);
+        mHeadsUpManager = headsUpManager;
+        mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mListContainer = listContainer;
+
+        mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                boolean wasUsing = mUseHeadsUp;
+                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                        Settings.Global.HEADS_UP_OFF);
+                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                if (wasUsing != mUseHeadsUp) {
+                    if (!mUseHeadsUp) {
+                        Log.d(TAG,
+                                "dismissing any existing heads up notification on disable event");
+                        mHeadsUpManager.releaseAllImmediately();
+                    }
+                }
+            }
+        };
+
+        if (ENABLE_HEADS_UP) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+                    true,
+                    mHeadsUpObserver);
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+                    mHeadsUpObserver);
+        }
+
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+        mHeadsUpObserver.onChange(true); // set up
+    }
+
+    public NotificationData getNotificationData() {
+        return mNotificationData;
+    }
+
+    public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return mGutsManager::openGuts;
+    }
+
+    @Override
+    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+        });
+    }
+
+    @Override
+    public void onReorderingAllowed() {
+        updateNotifications();
+    }
+
+    private boolean shouldSuppressFullScreenIntent(String key) {
+        if (mPresenter.isDeviceInVrMode()) {
+            return true;
+        }
+
+        if (mPowerManager.isInteractive()) {
+            return mNotificationData.shouldSuppressScreenOn(key);
+        } else {
+            return mNotificationData.shouldSuppressScreenOff(key);
+        }
+    }
+
+    private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                entry.notification.getUser().getIdentifier());
+
+        final StatusBarNotification sbn = entry.notification;
+        if (entry.row != null) {
+            entry.reset();
+            updateNotification(entry, pmUser, sbn, entry.row);
+        } else {
+            new RowInflaterTask().inflate(mContext, parent, entry,
+                    row -> {
+                        bindRow(entry, pmUser, sbn, row);
+                        updateNotification(entry, pmUser, sbn, row);
+                    });
+        }
+    }
+
+    private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setExpansionLogger(this, entry.notification.getKey());
+        row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setOnExpandClickListener(mPresenter);
+        row.setInflationCallback(this);
+        row.setLongPressListener(getNotificationLongClicker());
+        mRemoteInputManager.bindRow(row);
+
+        // 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 (PackageManager.NameNotFoundException e) {
+            // Do nothing
+        }
+        row.setAppName(appname);
+        row.setOnDismissRunnable(() ->
+                performRemoveNotification(row.getStatusBarNotification()));
+        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mCallback.onBindRow(entry, pmUser, sbn, row);
+    }
+
+    public void performRemoveNotification(StatusBarNotification n) {
+        NotificationData.Entry entry = mNotificationData.get(n.getKey());
+        mRemoteInputManager.onPerformRemoveNotification(n, entry);
+        final String pkg = n.getPackageName();
+        final String tag = n.getTag();
+        final int id = n.getId();
+        final int userId = n.getUserId();
+        try {
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mListContainer.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+            removeNotification(n.getKey(), null);
+
+        } catch (RemoteException ex) {
+            // system process is dead if we're here.
+        }
+
+        mCallback.onPerformRemoveNotification(n);
+    }
+
+    /**
+     * 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.
+        }
+    }
+
+    private void abortExistingInflation(String key) {
+        if (mPendingNotifications.containsKey(key)) {
+            NotificationData.Entry entry = mPendingNotifications.get(key);
+            entry.abortTask();
+            mPendingNotifications.remove(key);
+        }
+        NotificationData.Entry addedEntry = mNotificationData.get(key);
+        if (addedEntry != null) {
+            addedEntry.abortTask();
+        }
+    }
+
+    @Override
+    public void handleInflationException(StatusBarNotification notification, Exception e) {
+        handleNotificationError(notification, e.getMessage());
+    }
+
+    private void addEntry(NotificationData.Entry shadeEntry) {
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (isHeadsUped) {
+            mHeadsUpManager.showNotification(shadeEntry);
+            // Mark as seen immediately
+            setNotificationShown(shadeEntry.notification);
+        }
+        addNotificationViews(shadeEntry);
+        mCallback.onNotificationAdded(shadeEntry);
+    }
+
+    @Override
+    public void onAsyncInflationFinished(NotificationData.Entry entry) {
+        mPendingNotifications.remove(entry.key);
+        // If there was an async task started after the removal, we don't want to add it back to
+        // the list, otherwise we might get leaks.
+        boolean isNew = mNotificationData.get(entry.key) == null;
+        if (isNew && !entry.row.isRemoved()) {
+            addEntry(entry);
+        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+            mVisualStabilityManager.onLowPriorityUpdated(entry);
+            mPresenter.updateNotificationViews();
+        }
+        entry.row.setLowPriorityStateUpdated(false);
+    }
+
+    @Override
+    public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+        boolean deferRemoval = false;
+        abortExistingInflation(key);
+        if (mHeadsUpManager.isHeadsUp(key)) {
+            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
+            // sending look longer than it takes.
+            // Also we should not defer the removal if reordering isn't allowed since otherwise
+            // some notifications can't disappear before the panel is closed.
+            boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+                    && !FORCE_REMOTE_INPUT_HISTORY
+                    || !mVisualStabilityManager.isReorderingAllowed();
+            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
+        }
+        mMediaManager.onNotificationRemoved(key);
+
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
+            StatusBarNotification sbn = entry.notification;
+
+            Notification.Builder b = Notification.Builder
+                    .recoverBuilder(mContext, sbn.getNotification().clone());
+            CharSequence[] oldHistory = sbn.getNotification().extras
+                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+            CharSequence[] newHistory;
+            if (oldHistory == null) {
+                newHistory = new CharSequence[1];
+            } else {
+                newHistory = new CharSequence[oldHistory.length + 1];
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+            }
+            newHistory[0] = String.valueOf(entry.remoteInputText);
+            b.setRemoteInputHistory(newHistory);
+
+            Notification newNotification = b.build();
+
+            // Undo any compatibility view inflation
+            newNotification.contentView = sbn.getNotification().contentView;
+            newNotification.bigContentView = sbn.getNotification().bigContentView;
+            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+                    sbn.getOpPkg(),
+                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+            boolean updated = false;
+            try {
+                updateNotificationInternal(newSbn, null);
+                updated = true;
+            } catch (InflationException e) {
+                deferRemoval = false;
+            }
+            if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+                mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+                return;
+            }
+        }
+        if (deferRemoval) {
+            mLatestRankingMap = ranking;
+            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+            return;
+        }
+
+        if (mRemoteInputManager.onRemoveNotification(entry)) {
+            mLatestRankingMap = ranking;
+            return;
+        }
+
+        if (entry != null && mGutsManager.getExposedGuts() != null
+                && mGutsManager.getExposedGuts() == entry.row.getGuts()
+                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+            mLatestRankingMap = ranking;
+            mGutsManager.setKeyToRemoveOnGutsClosed(key);
+            return;
+        }
+
+        if (entry != null) {
+            mForegroundServiceController.removeNotification(entry.notification);
+        }
+
+        if (entry != null && entry.row != null) {
+            entry.row.setRemoved();
+            mListContainer.cleanUpViewState(entry.row);
+        }
+        // Let's remove the children if this was a summary
+        handleGroupSummaryRemoved(key);
+        StatusBarNotification old = removeNotificationViews(key, ranking);
+
+        mCallback.onNotificationRemoved(key, old);
+    }
+
+    private StatusBarNotification removeNotificationViews(String key,
+            NotificationListenerService.RankingMap ranking) {
+        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+        if (entry == null) {
+            Log.w(TAG, "removeNotification for unknown key: " + key);
+            return null;
+        }
+        updateNotifications();
+        Dependency.get(LeakDetector.class).trackGarbage(entry);
+        return entry.notification;
+    }
+
+    /**
+     * Ensures that the group children are cancelled immediately when the group summary is cancelled
+     * instead of waiting for the notification manager to send all cancels. Otherwise this could
+     * lead to flickers.
+     *
+     * This also ensures that the animation looks nice and only consists of a single disappear
+     * animation instead of multiple.
+     *  @param key the key of the notification was removed
+     *
+     */
+    private void handleGroupSummaryRemoved(String key) {
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry != null && entry.row != null
+                && entry.row.isSummaryWithChildren()) {
+            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+                // We don't want to remove children for autobundled notifications as they are not
+                // always cancelled. We only remove them if they were dismissed by the user.
+                return;
+            }
+            List<ExpandableNotificationRow> notificationChildren =
+                    entry.row.getNotificationChildren();
+            for (int i = 0; i < notificationChildren.size(); i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                if ((row.getStatusBarNotification().getNotification().flags
+                        & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                    // the child is a foreground service notification which we can't remove!
+                    continue;
+                }
+                row.setKeepInParent(true);
+                // we need to set this state earlier as otherwise we might generate some weird
+                // animations
+                row.setRemoved();
+            }
+        }
+    }
+
+    public void updateNotificationsOnDensityOrFontScaleChanged() {
+        ArrayList<NotificationData.Entry> activeNotifications =
+                mNotificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            NotificationData.Entry entry = activeNotifications.get(i);
+            boolean exposedGuts = mGutsManager.getExposedGuts() != null
+                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
+            entry.row.onDensityOrFontScaleChanged();
+            if (exposedGuts) {
+                mGutsManager.setExposedGuts(entry.row.getGuts());
+                mGutsManager.bindGuts(entry.row);
+            }
+        }
+    }
+
+    private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+        boolean isUpdate = mNotificationData.get(entry.key) != null;
+        boolean wasLowPriority = row.isLowPriority();
+        row.setIsLowPriority(isLowPriority);
+        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+        // bind the click event to the content area
+        mNotificationClicker.register(row, sbn);
+
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+        entry.row = row;
+        entry.row.setOnActivatedListener(mPresenter);
+
+        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+                mNotificationData.getImportance(sbn.getKey()));
+        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+                && !mPresenter.isPresenterFullyCollapsed();
+        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        row.updateNotification(entry);
+    }
+
+
+    protected void addNotificationViews(NotificationData.Entry entry) {
+        if (entry == null) {
+            return;
+        }
+        // Add the expanded view and icon.
+        mNotificationData.add(entry);
+        updateNotifications();
+    }
+
+    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+            throws InflationException {
+        if (DEBUG) {
+            Log.d(TAG, "createNotificationViews(notification=" + sbn);
+        }
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        Dependency.get(LeakDetector.class).trackInstance(entry);
+        entry.createIcons(mContext, sbn);
+        // Construct the expanded view.
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+        return entry;
+    }
+
+    private void addNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        String key = notification.getKey();
+        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+        mNotificationData.updateRanking(ranking);
+        NotificationData.Entry shadeEntry = createNotificationViews(notification);
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+            if (shouldSuppressFullScreenIntent(key)) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
+                }
+            } else if (mNotificationData.getImportance(key)
+                    < NotificationManager.IMPORTANCE_HIGH) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: not important enough: "
+                            + key);
+                }
+            } else {
+                // Stop screensaver if the notification has a fullscreen intent.
+                // (like an incoming phone call)
+                SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+                // not immersive & a fullscreen alert should be shown
+                if (DEBUG)
+                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+                try {
+                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+                            key);
+                    notification.getNotification().fullScreenIntent.send();
+                    shadeEntry.notifyFullScreenIntentLaunched();
+                    mMetricsLogger.count("note_fullscreen", 1);
+                } catch (PendingIntent.CanceledException e) {
+                }
+            }
+        }
+        abortExistingInflation(key);
+
+        mForegroundServiceController.addNotification(notification,
+                mNotificationData.getImportance(key));
+
+        mPendingNotifications.put(key, shadeEntry);
+    }
+
+    @Override
+    public void addNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            addNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+        return oldEntry == null || !oldEntry.hasInterrupted()
+                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+    }
+
+    private void updateNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+        final String key = notification.getKey();
+        abortExistingInflation(key);
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry == null) {
+            return;
+        }
+        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+        mRemoteInputManager.onUpdateNotification(entry);
+
+        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+            mGutsManager.setKeyToRemoveOnGutsClosed(null);
+            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+        }
+
+        Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
+
+        final StatusBarNotification oldNotification = entry.notification;
+        entry.notification = notification;
+        mGroupManager.onEntryUpdated(entry, oldNotification);
+
+        entry.updateIcons(mContext, notification);
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+
+        mForegroundServiceController.updateNotification(notification,
+                mNotificationData.getImportance(key));
+
+        boolean shouldPeek = shouldPeek(entry, notification);
+        boolean alertAgain = alertAgain(entry, n);
+
+        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.
+            mListContainer.snapViewIfNeeded(entry.row);
+        }
+
+        if (DEBUG) {
+            // Is this for you?
+            boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+        }
+
+        mCallback.onNotificationUpdated(notification);
+    }
+
+    @Override
+    public void updateNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            updateNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    public void updateNotifications() {
+        mNotificationData.filterAndSort();
+
+        mPresenter.updateNotificationViews();
+    }
+
+    public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+        mNotificationData.updateRanking(ranking);
+        updateNotifications();
+    }
+
+    protected boolean shouldPeek(NotificationData.Entry entry) {
+        return shouldPeek(entry, entry.notification);
+    }
+
+    public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+            return false;
+        }
+
+        if (mNotificationData.shouldFilterOut(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+            return false;
+        }
+
+        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+        if (!inUse && !mPresenter.isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(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;
+        }
+
+        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+        int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+                : NotificationManager.IMPORTANCE_HIGH;
+        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+            return false;
+        }
+
+        // Don't peek notifications that are suppressed due to group alert behavior
+        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+            return false;
+        }
+
+        if (!mCallback.shouldPeek(entry, sbn)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    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 isSnoozedPackage(StatusBarNotification sbn) {
+        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+    }
+
+    protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+            boolean alertAgain) {
+        final boolean wasHeadsUp = isHeadsUp(key);
+        if (wasHeadsUp) {
+            if (!shouldPeek) {
+                // We don't want this to be interrupting anymore, lets remove it
+                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+            } else {
+                mHeadsUpManager.updateNotification(entry, alertAgain);
+            }
+        } else if (shouldPeek && alertAgain) {
+            // This notification was updated to be a heads-up, show it!
+            mHeadsUpManager.showNotification(entry);
+        }
+    }
+
+    protected boolean isHeadsUp(String key) {
+        return mHeadsUpManager.isHeadsUp(key);
+    }
+
+    /**
+     * Callback for NotificationEntryManager.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new entry is created.
+         *
+         * @param shadeEntry entry that was created
+         */
+        void onNotificationAdded(NotificationData.Entry shadeEntry);
+
+        /**
+         * Called when a notification was updated.
+         *
+         * @param notification notification that was updated
+         */
+        void onNotificationUpdated(StatusBarNotification notification);
+
+        /**
+         * Called when a notification was removed.
+         *
+         * @param key key of notification that was removed
+         * @param old StatusBarNotification of the notification before it was removed
+         */
+        void onNotificationRemoved(String key, StatusBarNotification old);
+
+
+        /**
+         * Called when a notification is clicked.
+         *
+         * @param sbn notification that was clicked
+         * @param row row for that notification
+         */
+        void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Called when a new notification and row is created.
+         *
+         * @param entry entry for the notification
+         * @param pmUser package manager for user
+         * @param sbn notification
+         * @param row row for the notification
+         */
+        void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+                StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Removes a notification immediately.
+         *
+         * @param statusBarNotification notification that is being removed
+         */
+        void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+        /**
+         * Returns true if NotificationEntryManager should peek this notification.
+         *
+         * @param entry entry of the notification that might be peeked
+         * @param sbn notification that might be peeked
+         * @return true if the notification should be peeked
+         */
+        boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 2e572e1..87ad6f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -42,7 +42,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.io.FileDescriptor;
@@ -67,24 +66,22 @@
     private final Set<String> mNonBlockablePkgs;
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+    // Dependencies:
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
 
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
-    private NotificationPresenter mPresenter;
-
-    // TODO: Create NotificationListContainer interface and use it instead of
-    // NotificationStackScrollLayout here
-    private NotificationStackScrollLayout mStackScroller;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
     private NotificationInfo.CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
-    public NotificationGutsManager(
-            NotificationLockscreenUserManager lockscreenUserManager,
-            Context context) {
-        mLockscreenUserManager = lockscreenUserManager;
+    public NotificationGutsManager(Context context) {
         mContext = context;
         Resources res = context.getResources();
 
@@ -96,12 +93,13 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
 
-    public void setUp(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller,
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer,
             NotificationInfo.CheckSaveListener checkSaveListener,
             OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
-        mStackScroller = stackScroller;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
         mCheckSaveListener = checkSaveListener;
         mOnSettingsClickListener = onSettingsClickListener;
     }
@@ -158,7 +156,7 @@
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
             if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(
+                mListContainer.onHeightChanged(
                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
             }
             if (mNotificationGutsExposed == g) {
@@ -168,18 +166,18 @@
             String key = sbn.getKey();
             if (key.equals(mKeyToRemoveOnGutsClosed)) {
                 mKeyToRemoveOnGutsClosed = null;
-                mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+                mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
             }
         });
 
         View gutsView = item.getGutsView();
         if (gutsView instanceof NotificationSnooze) {
             NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
             snoozeGuts.setStatusBarNotification(sbn);
             snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
             guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+                mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
             });
         }
 
@@ -257,7 +255,7 @@
             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
         }
         if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+            mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
         }
     }
 
@@ -350,7 +348,7 @@
                                 !mAccessibilityManager.isTouchExplorationEnabled());
                 guts.setExposed(true /* exposed */, needsFalsingProtection);
                 row.closeRemoteInput();
-                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mListContainer.onHeightChanged(row, true /* needsAnimation */);
                 mNotificationGutsExposed = guts;
                 mGutsMenuItem = item;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644
index 0000000..43be44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+    /**
+     * Called when a child is being transferred.
+     *
+     * @param childTransferInProgress whether child transfer is in progress
+     */
+    void setChildTransferInProgress(boolean childTransferInProgress);
+
+    /**
+     * Change the position of child to a new location
+     *
+     * @param child the view to change the position for
+     * @param newIndex the new index
+     */
+    void changeViewPosition(View child, int newIndex);
+
+    /**
+     * Called when a child was added to a group.
+     *
+     * @param row row of the group child that was added
+     */
+    void notifyGroupChildAdded(View row);
+
+    /**
+     * Called when a child was removed from a group.
+     *
+     * @param row row of the child that was removed
+     * @param childrenContainer ViewGroup of the group that the child was removed from
+     */
+    void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+    /**
+     * Generate an animation for an added child view.
+     *
+     * @param child The view to be added.
+     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+     */
+    void generateAddAnimation(View child, boolean fromMoreCard);
+
+    /**
+     * Generate a child order changed event.
+     */
+    void generateChildOrderChangedEvent();
+
+    /**
+     * Returns the number of children in the NotificationListContainer.
+     *
+     * @return the number of children in the NotificationListContainer
+     */
+    int getContainerChildCount();
+
+    /**
+     * Gets the ith child in the NotificationListContainer.
+     *
+     * @param i ith child to get
+     * @return the ith child in the list container
+     */
+    View getContainerChildAt(int i);
+
+    /**
+     * Remove a view from the container
+     *
+     * @param v view to remove
+     */
+    void removeContainerView(View v);
+
+    /**
+     * Add a view to the container
+     *
+     * @param v view to add
+     */
+    void addContainerView(View v);
+
+    /**
+     * Sets the maximum number of notifications to display.
+     *
+     * @param maxNotifications max number of notifications to display
+     */
+    void setMaxDisplayedNotifications(int maxNotifications);
+
+    /**
+     * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+     *
+     * @param row row to snap back
+     */
+    void snapViewIfNeeded(ExpandableNotificationRow row);
+
+    /**
+     * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+     *
+     * @param entry entry to get the view parent for
+     * @return the view parent for entry
+     */
+    ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+    /**
+     * Called when the height of an expandable view changes.
+     *
+     * @param view view whose height changed
+     * @param animate whether this change should be animated
+     */
+    void onHeightChanged(ExpandableView view, boolean animate);
+
+    /**
+     * Resets the currently exposed menu view.
+     *
+     * @param animate whether to animate the closing/change of menu view
+     * @param force reset the menu view even if it looks like it is already reset
+     */
+    void resetExposedMenuView(boolean animate, boolean force);
+
+    /**
+     * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+     *
+     * @return swipe action helper for the list container
+     */
+    NotificationSwipeActionHelper getSwipeActionHelper();
+
+    /**
+     * Called when a notification is removed from the shade. This cleans up the state for a
+     * given view.
+     *
+     * @param view view to clean up view state for
+     */
+    void cleanUpViewState(View view);
+
+    /**
+     * Returns whether an ExpandableNotificationRow is in a visible location or not.
+     *
+     * @param row
+     * @return true if row is in a visible location
+     */
+    boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+    /**
+     * Sets a listener to listen for changes in notification locations.
+     *
+     * @param listener listener to set
+     */
+    void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener);
+
+    /**
+     * Called when an update to the notification view hierarchy is completed.
+     */
+    default void onNotificationViewUpdateFinished() {}
+
+    /**
+     * Returns true if there are pulsing notifications.
+     *
+     * @return true if has pulsing notifications
+     */
+    boolean hasPulsingNotifications();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 4952da4..0144f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -27,6 +27,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 /**
@@ -36,14 +37,16 @@
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
 
-    private final NotificationPresenter mPresenter;
-    private final NotificationRemoteInputManager mRemoteInputManager;
+    // Dependencies:
+    private final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+
     private final Context mContext;
 
-    public NotificationListener(NotificationPresenter presenter,
-            NotificationRemoteInputManager remoteInputManager, Context context) {
-        mPresenter = presenter;
-        mRemoteInputManager = remoteInputManager;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+
+    public NotificationListener(Context context) {
         mContext = context;
     }
 
@@ -59,7 +62,7 @@
         final RankingMap currentRanking = getCurrentRanking();
         mPresenter.getHandler().post(() -> {
             for (StatusBarNotification sbn : notifications) {
-                mPresenter.addNotification(sbn, currentRanking);
+                mEntryManager.addNotification(sbn, currentRanking);
             }
         });
     }
@@ -73,7 +76,8 @@
                 processForRemoteInput(sbn.getNotification(), mContext);
                 String key = sbn.getKey();
                 mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
-                boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
+                boolean isUpdate =
+                        mEntryManager.getNotificationData().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,
@@ -86,16 +90,17 @@
 
                     // Remove existing notification to avoid stale data.
                     if (isUpdate) {
-                        mPresenter.removeNotification(key, rankingMap);
+                        mEntryManager.removeNotification(key, rankingMap);
                     } else {
-                        mPresenter.getNotificationData().updateRanking(rankingMap);
+                        mEntryManager.getNotificationData()
+                                .updateRanking(rankingMap);
                     }
                     return;
                 }
                 if (isUpdate) {
-                    mPresenter.updateNotification(sbn, rankingMap);
+                    mEntryManager.updateNotification(sbn, rankingMap);
                 } else {
-                    mPresenter.addNotification(sbn, rankingMap);
+                    mEntryManager.addNotification(sbn, rankingMap);
                 }
             });
         }
@@ -107,7 +112,9 @@
         if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
             final String key = sbn.getKey();
-            mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap));
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.removeNotification(key, rankingMap);
+            });
         }
     }
 
@@ -116,11 +123,17 @@
         if (DEBUG) Log.d(TAG, "onRankingUpdate");
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
-            mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.updateNotificationRanking(r);
+            });
         }
     }
 
-    public void register() {
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+
         try {
             registerAsSystemService(mContext,
                     new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 644d834..06e7f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -81,7 +81,7 @@
                     isCurrentProfile(getSendingUserId())) {
                 mUsersAllowingPrivateNotifications.clear();
                 updateLockscreenNotificationSetting();
-                mPresenter.updateNotifications();
+                mEntryManager.updateNotifications();
             } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
                 if (userId != mCurrentUserId && isCurrentProfile(userId)) {
                     mPresenter.onWorkChallengeChanged();
@@ -157,6 +157,7 @@
 
     protected int mCurrentUserId = 0;
     protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     protected ContentObserver mLockscreenSettingsObserver;
     protected ContentObserver mSettingsObserver;
 
@@ -170,8 +171,10 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
         mPresenter = presenter;
+        mEntryManager = entryManager;
 
         mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
             @Override
@@ -182,7 +185,7 @@
                 mUsersAllowingNotifications.clear();
                 // ... and refresh all the notifications
                 updateLockscreenNotificationSetting();
-                mPresenter.updateNotifications();
+                mEntryManager.updateNotifications();
             }
         };
 
@@ -191,7 +194,7 @@
             public void onChange(boolean selfChange) {
                 updateLockscreenNotificationSetting();
                 if (mDeviceProvisionedController.isDeviceProvisioned()) {
-                    mPresenter.updateNotifications();
+                    mEntryManager.updateNotifications();
                 }
             }
         };
@@ -271,13 +274,13 @@
      */
     public boolean shouldHideNotifications(String key) {
         return isLockscreenPublicMode(mCurrentUserId)
-                && mPresenter.getNotificationData().getVisibilityOverride(key) ==
+                && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
                         Notification.VISIBILITY_SECRET;
     }
 
     public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
         return mShowLockscreenNotifications
-                && !mPresenter.getNotificationData().isAmbient(sbn.getKey());
+                && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
     }
 
     private void setShowLockscreenNotifications(boolean show) {
@@ -395,7 +398,7 @@
     }
 
     private boolean packageHasVisibilityOverride(String key) {
-        return mPresenter.getNotificationData().getVisibilityOverride(key) ==
+        return mEntryManager.getNotificationData().getVisibilityOverride(key) ==
                 Notification.VISIBILITY_PRIVATE;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
new file mode 100644
index 0000000..4225f83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+public class NotificationLogger {
+    private static final String TAG = "NotificationLogger";
+
+    /** The minimum delay in ms between reports of notification visibility. */
+    private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
+
+    /** Keys of notifications currently visible to the user. */
+    private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
+            new ArraySet<>();
+
+    // Dependencies:
+    private final NotificationListenerService mNotificationListener =
+            Dependency.get(NotificationListener.class);
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+
+    protected NotificationEntryManager mEntryManager;
+    protected Handler mHandler = new Handler();
+    protected IStatusBarService mBarService;
+    private long mLastVisibilityReportUptimeMs;
+    private NotificationListContainer mListContainer;
+
+    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+            new OnChildLocationsChangedListener() {
+                @Override
+                public void onChildLocationsChanged() {
+                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
+                        // Visibilities will be reported when the existing
+                        // callback is executed.
+                        return;
+                    }
+                    // Calculate when we're allowed to run the visibility
+                    // reporter. Note that this timestamp might already have
+                    // passed. That's OK, the callback will just be executed
+                    // ASAP.
+                    long nextReportUptimeMs =
+                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
+                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
+                }
+            };
+
+    // Tracks notifications currently visible in mNotificationStackScroller and
+    // emits visibility events via NoMan on changes.
+    protected final Runnable mVisibilityReporter = new Runnable() {
+        private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
+                new ArraySet<>();
+        private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
+                new ArraySet<>();
+        private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
+                new ArraySet<>();
+
+        @Override
+        public void run() {
+            mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
+
+            // 1. Loop over mNotificationData entries:
+            //   A. Keep list of visible notifications.
+            //   B. Keep list of previously hidden, now visible notifications.
+            // 2. Compute no-longer visible notifications by removing currently
+            //    visible notifications from the set of previously visible
+            //    notifications.
+            // 3. Report newly visible and no-longer visible notifications.
+            // 4. Keep currently visible notifications for next report.
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+                    .getNotificationData().getActiveNotifications();
+            int N = activeNotifications.size();
+            for (int i = 0; i < N; i++) {
+                NotificationData.Entry entry = activeNotifications.get(i);
+                String key = entry.notification.getKey();
+                boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
+                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
+                boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
+                if (isVisible) {
+                    // Build new set of visible notifications.
+                    mTmpCurrentlyVisibleNotifications.add(visObj);
+                    if (!previouslyVisible) {
+                        mTmpNewlyVisibleNotifications.add(visObj);
+                    }
+                } else {
+                    // release object
+                    visObj.recycle();
+                }
+            }
+            mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
+            mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
+
+            logNotificationVisibilityChanges(
+                    mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
+
+            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+            mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+
+            recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
+            mTmpCurrentlyVisibleNotifications.clear();
+            mTmpNewlyVisibleNotifications.clear();
+            mTmpNoLongerVisibleNotifications.clear();
+        }
+    };
+
+    public NotificationLogger() {
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    }
+
+    public void setUpWithEntryManager(NotificationEntryManager entryManager,
+            NotificationListContainer listContainer) {
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    public void stopNotificationLogging() {
+        // Report all notifications as invisible and turn down the
+        // reporter.
+        if (!mCurrentlyVisibleNotifications.isEmpty()) {
+            logNotificationVisibilityChanges(
+                    Collections.emptyList(), mCurrentlyVisibleNotifications);
+            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+        }
+        mHandler.removeCallbacks(mVisibilityReporter);
+        mListContainer.setChildLocationsChangedListener(null);
+    }
+
+    public void startNotificationLogging() {
+        mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+        // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
+        // cause the scroller to emit child location events. Hence generate
+        // one ourselves to guarantee that we're reporting visible
+        // notifications.
+        // (Note that in cases where the scroller does emit events, this
+        // additional event doesn't break anything.)
+        mNotificationLocationsChangedListener.onChildLocationsChanged();
+    }
+
+    private void logNotificationVisibilityChanges(
+            Collection<NotificationVisibility> newlyVisible,
+            Collection<NotificationVisibility> noLongerVisible) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return;
+        }
+        NotificationVisibility[] newlyVisibleAr =
+                newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
+        NotificationVisibility[] noLongerVisibleAr =
+                noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+
+            final int N = newlyVisible.size();
+            if (N > 0) {
+                String[] newlyVisibleKeyAr = new String[N];
+                for (int i = 0; i < N; i++) {
+                    newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+                }
+
+                // TODO: Call NotificationEntryManager to do this, once it exists.
+                // TODO: Consider not catching all runtime exceptions here.
+                try {
+                    mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
+                } catch (RuntimeException e) {
+                    Log.d(TAG, "failed setNotificationsShown: ", e);
+                }
+            }
+        });
+    }
+
+    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
+        final int N = array.size();
+        for (int i = 0 ; i < N; i++) {
+            array.valueAt(i).recycle();
+        }
+        array.clear();
+    }
+
+    @VisibleForTesting
+    public Runnable getVisibilityReporter() {
+        return mVisibilityReporter;
+    }
+
+    /**
+     * A listener that is notified when some child locations might have changed.
+     */
+    public interface OnChildLocationsChangedListener {
+        void onChildLocationsChanged();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 283a6e3..852239a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,9 +40,11 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final NotificationPresenter mPresenter;
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
+
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
@@ -73,8 +75,7 @@
         }
     };
 
-    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
-        mPresenter = presenter;
+    public NotificationMediaManager(Context context) {
         mContext = context;
         mMediaSessionManager
                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -82,6 +83,12 @@
         // in session state
     }
 
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+    }
+
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
@@ -100,8 +107,8 @@
     public void findAndUpdateMediaNotifications() {
         boolean metaDataChanged = false;
 
-        synchronized (mPresenter.getNotificationData()) {
-            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+        synchronized (mEntryManager.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
                     .getNotificationData().getActiveNotifications();
             final int N = activeNotifications.size();
 
@@ -188,7 +195,7 @@
         }
 
         if (metaDataChanged) {
-            mPresenter.updateNotifications();
+            mEntryManager.updateNotifications();
         }
         mPresenter.updateMediaMetaData(metaDataChanged, true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 33c7253..12641a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,12 +16,11 @@
 package com.android.systemui.statusbar;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
 import android.view.View;
 
-import java.util.Set;
-
 /**
  * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
  * for both querying the state of the system (some modularised piece of functionality may
@@ -29,9 +28,11 @@
  * for affecting the state of the system (e.g. starting an intent, given that the presenter may
  * want to perform some action before doing so).
  */
-public interface NotificationPresenter extends NotificationUpdateHandler,
-        NotificationData.Environment, NotificationRemoteInputManager.Callback {
-
+public interface NotificationPresenter extends NotificationData.Environment,
+        NotificationRemoteInputManager.Callback,
+        ExpandableNotificationRow.OnExpandClickListener,
+        ActivatableNotificationView.OnActivatedListener,
+        NotificationEntryManager.Callback {
     /**
      * Returns true if the presenter is not visible. For example, it may not be necessary to do
      * animations if this returns true.
@@ -50,32 +51,15 @@
     void startNotificationGutsIntent(Intent intent, int appUid);
 
     /**
-     * Returns NotificationData.
-     */
-    NotificationData getNotificationData();
-
-    /**
      * Returns the Handler for NotificationPresenter.
      */
     Handler getHandler();
 
-    // TODO: Create NotificationEntryManager and move this method to there.
-    /**
-     * Signals that some notifications have changed, and NotificationPresenter should update itself.
-     */
-    void updateNotifications();
-
     /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
 
-    // TODO: Create NotificationEntryManager and move this method to there.
-    /**
-     * Gets the latest ranking map.
-     */
-    NotificationListenerService.RankingMap getLatestRankingMap();
-
     /**
      * Called when the locked status of the device is changed for a work profile.
      */
@@ -107,4 +91,32 @@
      * @return true iff the device is locked
      */
     boolean isDeviceLocked(int userId);
+
+    /**
+     * @return true iff the device is in vr mode
+     */
+    boolean isDeviceInVrMode();
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    void updateNotificationViews();
+
+    /**
+     * @return true iff the device is dozing
+     */
+    boolean isDozing();
+
+    /**
+     * Returns the maximum number of notifications to show while locked.
+     *
+     * @param recompute whether something has changed that means we should recompute this value
+     * @return the maximum number of notifications to show while locked
+     */
+    int getMaxNotificationsWhileLocked(boolean recompute);
+
+    /**
+     * Called when the row states are updated by NotificationViewHierarchyManager.
+     */
+    void onUpdateRowStates();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7827f62..f25379a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
@@ -70,7 +71,10 @@
 
     protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
             new ArraySet<>();
-    protected final NotificationLockscreenUserManager mLockscreenUserManager;
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
 
     /**
      * Notifications with keys in this set are not actually around anymore. We kept them around
@@ -83,6 +87,7 @@
 
     protected RemoteInputController mRemoteInputController;
     protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     protected IStatusBarService mBarService;
     protected Callback mCallback;
 
@@ -263,9 +268,7 @@
         }
     };
 
-    public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager,
-            Context context) {
-        mLockscreenUserManager = lockscreenUserManager;
+    public NotificationRemoteInputManager(Context context) {
         mContext = context;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -273,16 +276,18 @@
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager,
             Callback callback,
             RemoteInputController.Delegate delegate) {
         mPresenter = presenter;
+        mEntryManager = entryManager;
         mCallback = callback;
         mRemoteInputController = new RemoteInputController(delegate);
         mRemoteInputController.addCallback(new RemoteInputController.Callback() {
             @Override
             public void onRemoteInputSent(NotificationData.Entry entry) {
                 if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
-                    mPresenter.removeNotification(entry.key, null);
+                    mEntryManager.removeNotification(entry.key, null);
                 } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
                     // We're currently holding onto this notification, but from the apps point of
                     // view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -290,7 +295,7 @@
                     // bit.
                     mPresenter.getHandler().postDelayed(() -> {
                         if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
-                            mPresenter.removeNotification(entry.key, null);
+                            mEntryManager.removeNotification(entry.key, null);
                         }
                     }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
                 }
@@ -336,7 +341,7 @@
         for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
             NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
             mRemoteInputController.removeRemoteInput(entry, null);
-            mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap());
+            mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap());
         }
         mRemoteInputEntriesToRemoveOnCollapse.clear();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 75321fd..8325df7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -56,6 +57,7 @@
     private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
+    private static final String TAG = "NotificationShelf";
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
@@ -98,7 +100,7 @@
         setClipToActualHeight(false);
         setClipChildren(false);
         setClipToPadding(false);
-        mShelfIcons.setShowAllIcons(false);
+        mShelfIcons.setIsStaticLayout(false);
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
@@ -294,10 +296,15 @@
             if (notGoneIndex == 0) {
                 StatusBarIconView icon = row.getEntry().expandedIcon;
                 NotificationIconContainer.IconState iconState = getIconState(icon);
-                if (iconState.clampedAppearAmount == 1.0f) {
+                if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
                     // only if the first icon is fully in the shelf we want to clip to it!
                     backgroundTop = (int) (row.getTranslationY() - getTranslationY());
                     firstElementRoundness = row.getCurrentTopRoundness();
+                } else if (iconState == null) {
+                    Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
+                            + (row.getEntry().expandedIcon != null
+                            ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
+                            + " \n number of notifications: " + mHostLayout.getChildCount() );
                 }
             }
             notGoneIndex++;
@@ -681,7 +688,8 @@
         if (isLayoutRtl()) {
             start = getWidth() - start - mCollapsedIcons.getWidth();
         }
-        int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+        int width = (int) NotificationUtils.interpolate(
+                start + mCollapsedIcons.getFinalTranslationX(),
                 mShelfIcons.getWidth(),
                 openedAmount);
         mShelfIcons.setActualLayoutWidth(width);
@@ -691,6 +699,9 @@
             // we have to ensure that adding the low priority notification won't lead to an
             // overflow
             collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+        } else {
+            // Partial overflow padding will fill enough space to add extra dots
+            collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
         }
         float padding = NotificationUtils.interpolate(collapsedPadding,
                 mShelfIcons.getPaddingEnd(),
@@ -700,7 +711,6 @@
                 mShelfIcons.getPaddingStart(), openedAmount);
         mShelfIcons.setActualPaddingStart(paddingStart);
         mShelfIcons.setOpenedAmount(openedAmount);
-        mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
     }
 
     public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644
index 0000000..266c09b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+    private static final String TAG = "NotificationViewHierarchyManager";
+
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+
+    /**
+     * {@code true} if notifications not part of a group should by default be rendered in their
+     * expanded state. If {@code false}, then only the first notification will be expanded if
+     * possible.
+     */
+    private final boolean mAlwaysExpandNonGroupedNotification;
+
+    private NotificationPresenter mPresenter;
+    private NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
+
+    public NotificationViewHierarchyManager(Context context) {
+        Resources res = context.getResources();
+        mAlwaysExpandNonGroupedNotification =
+                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    public void updateNotificationViews() {
+        ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
+        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+        final int N = activeNotifications.size();
+        for (int i = 0; i < N; i++) {
+            NotificationData.Entry ent = activeNotifications.get(i);
+            if (ent.row.isDismissed() || ent.row.isRemoved()) {
+                // we don't want to update removed notifications because they could
+                // temporarily become children if they were isolated before.
+                continue;
+            }
+            int userId = ent.notification.getUserId();
+
+            // Display public version of the notification if we need to redact.
+            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+            // We can probably move some of this code there.
+            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+                    mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+            boolean sensitive = userPublic && needsRedaction;
+            boolean deviceSensitive = devicePublic
+                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                    mLockscreenUserManager.getCurrentUserId());
+            ent.row.setSensitive(sensitive, deviceSensitive);
+            ent.row.setNeedsRedaction(needsRedaction);
+            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+                        ent.row.getStatusBarNotification());
+                List<ExpandableNotificationRow> orderedChildren =
+                        mTmpChildOrderMap.get(summary);
+                if (orderedChildren == null) {
+                    orderedChildren = new ArrayList<>();
+                    mTmpChildOrderMap.put(summary, orderedChildren);
+                }
+                orderedChildren.add(ent.row);
+            } else {
+                toShow.add(ent.row);
+            }
+
+        }
+
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+                toRemove.add((ExpandableNotificationRow) child);
+            }
+        }
+
+        for (ExpandableNotificationRow remove : toRemove) {
+            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
+                mListContainer.setChildTransferInProgress(true);
+            }
+            if (remove.isSummaryWithChildren()) {
+                remove.removeAllChildren();
+            }
+            mListContainer.removeContainerView(remove);
+            mListContainer.setChildTransferInProgress(false);
+        }
+
+        removeNotificationChildren();
+
+        for (int i = 0; i < toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mVisualStabilityManager.notifyViewAddition(v);
+                mListContainer.addContainerView(v);
+            }
+        }
+
+        addNotificationChildrenAndSort();
+
+        // So after all this work notifications still aren't sorted correctly.
+        // Let's do that now by advancing through toShow and mListContainer in
+        // lock-step, making sure mListContainer matches what we see in toShow.
+        int j = 0;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow targetChild = toShow.get(j);
+            if (child != targetChild) {
+                // Oops, wrong notification at this position. Put the right one
+                // here and advance both lists.
+                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+                    mListContainer.changeViewPosition(targetChild, i);
+                } else {
+                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+                }
+            }
+            j++;
+
+        }
+
+        mVisualStabilityManager.onReorderingFinished();
+        // clear the map again for the next usage
+        mTmpChildOrderMap.clear();
+
+        updateRowStates();
+
+        mListContainer.onNotificationViewUpdateFinished();
+    }
+
+    private void addNotificationChildrenAndSort() {
+        // Let's now add all notification children which are missing
+        boolean orderChanged = false;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+                    childIndex++) {
+                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+                if (children == null || !children.contains(childView)) {
+                    if (childView.getParent() != null) {
+                        Log.wtf(TAG, "trying to add a notification child that already has " +
+                                "a parent. class:" + childView.getParent().getClass() +
+                                "\n child: " + childView);
+                        // This shouldn't happen. We can recover by removing it though.
+                        ((ViewGroup) childView.getParent()).removeView(childView);
+                    }
+                    mVisualStabilityManager.notifyViewAddition(childView);
+                    parent.addChildNotification(childView, childIndex);
+                    mListContainer.notifyGroupChildAdded(childView);
+                }
+            }
+
+            // Finally after removing and adding has been performed we can apply the order.
+            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+                    mEntryManager);
+        }
+        if (orderChanged) {
+            mListContainer.generateChildOrderChangedEvent();
+        }
+    }
+
+    private void removeNotificationChildren() {
+        // First let's remove all children which don't belong in the parents
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            if (children != null) {
+                toRemove.clear();
+                for (ExpandableNotificationRow childRow : children) {
+                    if ((orderedChildren == null
+                            || !orderedChildren.contains(childRow))
+                            && !childRow.keepInParent()) {
+                        toRemove.add(childRow);
+                    }
+                }
+                for (ExpandableNotificationRow remove : toRemove) {
+                    parent.removeChildNotification(remove);
+                    if (mEntryManager.getNotificationData().get(
+                            remove.getStatusBarNotification().getKey()) == null) {
+                        // We only want to add an animation if the view is completely removed
+                        // otherwise it's just a transfer
+                        mListContainer.notifyGroupChildRemoved(remove,
+                                parent.getChildrenContainer());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    public void updateRowStates() {
+        final int N = mListContainer.getContainerChildCount();
+
+        int visibleNotifications = 0;
+        boolean isLocked = mPresenter.isPresenterLocked();
+        int maxNotifications = -1;
+        if (isLocked) {
+            maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+        }
+        mListContainer.setMaxDisplayedNotifications(maxNotifications);
+        Stack<ExpandableNotificationRow> stack = new Stack<>();
+        for (int i = N - 1; i >= 0; i--) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            stack.push((ExpandableNotificationRow) child);
+        }
+        while(!stack.isEmpty()) {
+            ExpandableNotificationRow row = stack.pop();
+            NotificationData.Entry entry = row.getEntry();
+            boolean isChildNotification =
+                    mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+            row.setOnKeyguard(isLocked);
+
+            if (!isLocked) {
+                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+                // very first notification and if it's not a child of grouped notifications.
+                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+                        || (visibleNotifications == 0 && !isChildNotification
+                        && !row.isLowPriority()));
+            }
+
+            entry.row.setShowAmbient(mPresenter.isDozing());
+            int userId = entry.notification.getUserId();
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    entry.notification) && !entry.row.isRemoved();
+            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+                    .notification);
+            if (suppressedSummary
+                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+                    && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+                    || (isLocked && !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+            } else {
+                boolean wasGone = entry.row.getVisibility() == View.GONE;
+                if (wasGone) {
+                    entry.row.setVisibility(View.VISIBLE);
+                }
+                if (!isChildNotification && !entry.row.isRemoved()) {
+                    if (wasGone) {
+                        // notify the scroller of a child addition
+                        mListContainer.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));
+                }
+            }
+        }
+
+        mPresenter.onUpdateRowStates();
+    }
+}
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 5ba6f6a..3ebeb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -247,19 +247,6 @@
         return null;
     }
 
-    /**
-     * Returns the
-     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
-     * be triggered when a notification card is long-pressed.
-     */
-    @Override
-    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        // For the automative use case, we do not want to the user to be able to interact with
-        // a notification other than a regular click. As a result, just return null for the
-        // long click listener.
-        return null;
-    }
-
     @Override
     public void showBatteryView() {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -388,18 +375,6 @@
     }
 
     @Override
-    protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
-        // Because space is usually constrained in the auto use-case, there should not be a
-        // pinned notification when the shade has been expanded. Ensure this by not pinning any
-        // notification if the shade is already opened.
-        if (mPanelExpanded) {
-            return false;
-        }
-
-        return super.shouldPeek(entry, sbn);
-    }
-
-    @Override
     public void animateExpandNotificationsPanel() {
         // Because space is usually constrained in the auto use-case, there should not be a
         // pinned notification when the shade has been expanded. Ensure this by removing all heads-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 61f3130..61cb61c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -55,6 +55,7 @@
     private KeyguardMonitor mKeyguardMonitor;
     private NetworkController mNetworkController;
     private LinearLayout mSystemIconArea;
+    private View mClockView;
     private View mNotificationIconAreaInner;
     private int mDisabled1;
     private StatusBar mStatusBarComponent;
@@ -93,6 +94,7 @@
         mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
         Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+        mClockView = mStatusBar.findViewById(R.id.clock);
         mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
         // Default to showing until we know otherwise.
@@ -197,10 +199,12 @@
 
     public void hideSystemIconArea(boolean animate) {
         animateHide(mSystemIconArea, animate);
+        animateHide(mClockView, animate);
     }
 
     public void showSystemIconArea(boolean animate) {
         animateShow(mSystemIconArea, animate);
+        animateShow(mClockView, animate);
     }
 
     public void hideNotificationIconArea(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 50cbd69..6d85fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.R;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 
 import java.io.PrintWriter;
 
@@ -37,10 +38,12 @@
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
+    private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
 
     public DozeParameters(Context context) {
         mContext = context;
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+        mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
     }
 
     public void dump(PrintWriter pw) {
@@ -83,6 +86,11 @@
         return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
     }
 
+    public float getScreenBrightnessDoze() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+    }
+
     public int getPulseInDuration() {
         return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
     }
@@ -115,6 +123,26 @@
         return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
     }
 
+    /**
+     * For how long a wallpaper can be visible in AoD before it fades aways.
+     * @return duration in millis.
+     */
+    public long getWallpaperAodDuration() {
+        return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+    }
+
+    /**
+     * How long it takes for the wallpaper fade away (Animation duration.)
+     * @return duration in millis.
+     */
+    public long getWallpaperFadeOutDuration() {
+        return mAlwaysOnPolicy.wallpaperFadeOutDuration;
+    }
+
+    /**
+     * Checks if always on is available and enabled for the current user.
+     * @return {@code true} if enabled and available.
+     */
     public boolean getAlwaysOn() {
         return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
     }
@@ -123,7 +151,7 @@
      * Some screens need to be completely black before changing the display power mode,
      * unexpected behavior might happen if this parameter isn't respected.
      *
-     * @return true if screen needs to be completely black before a power transition.
+     * @return {@code true} if screen needs to be completely black before a power transition.
      */
     public boolean getDisplayNeedsBlanking() {
         return mContext.getResources().getBoolean(
@@ -134,7 +162,7 @@
      * Whether we can implement our own screen off animation or if we need
      * to rely on DisplayPowerManager to dim the display.
      *
-     * @return true if SystemUI can control the screen off animation.
+     * @return {@code true} if SystemUI can control the screen off animation.
      */
     public boolean getCanControlScreenOffAnimation() {
         return !mContext.getResources().getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index c281379..b71ebfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -122,6 +121,8 @@
 
         // Split up the work over multiple frames.
         DejankUtils.postAfterTraversal(mShowRunnable);
+
+        mCallback.onBouncerVisiblityChanged(true /* shown */);
     }
 
     private final Runnable mShowRunnable = new Runnable() {
@@ -182,6 +183,7 @@
             mDismissCallbackRegistry.notifyDismissCancelled();
         }
         mFalsingManager.onBouncerHidden();
+        mCallback.onBouncerVisiblityChanged(false /* shown */);
         cancelShowRunnable();
         if (mKeyguardView != null) {
             mKeyguardView.cancelDismissAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5b..7f4deb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@
     public void setWorkModeEnabled(boolean enableWorkMode) {
         synchronized (mProfiles) {
             for (UserInfo ui : mProfiles) {
-                if (enableWorkMode) {
-                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
-                        StatusBarManager statusBarManager = (StatusBarManager) mContext
-                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
-                        statusBarManager.collapsePanels();
-                    }
-                } else {
-                    mUserManager.setQuietModeEnabled(ui.id, true);
+                if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+                    StatusBarManager statusBarManager = (StatusBarManager) mContext
+                            .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+                    statusBarManager.collapsePanels();
                 }
             }
         }
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 61b007f..695168e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -69,6 +69,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
@@ -125,6 +126,8 @@
     private int mSystemUiVisibility;
     private LightBarController mLightBarController;
 
+    private OverviewProxyService mOverviewProxyService;
+
     public boolean mHomeBlockedThisTouch;
 
     // ----- Fragment Lifecycle Callbacks -----
@@ -152,6 +155,7 @@
             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
         }
         mAssistManager = Dependency.get(AssistManager.class);
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
 
         try {
             WindowManagerGlobal.getWindowManagerService()
@@ -364,7 +368,8 @@
 
     private boolean shouldDisableNavbarGestures() {
         return !mStatusBar.isDeviceProvisioned()
-                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
+                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0
+                || mOverviewProxyService.getProxy() != null;
     }
 
     private void repositionNavigationBar() {
@@ -449,6 +454,7 @@
         MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
         mAssistManager.startAssist(new Bundle() /* args */);
         mStatusBar.awakenDreams();
+
         if (mNavigationBarView != null) {
             mNavigationBarView.abortCurrentGesture();
         }
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 4e7f205..2796f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -98,6 +98,7 @@
     private GestureHelper mGestureHelper;
     private DeadZone mDeadZone;
     private final NavigationBarTransitions mBarTransitions;
+    private final OverviewProxyService mOverviewProxyService;
 
     // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
     final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -226,6 +227,7 @@
         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
         mButtonDispatchers.put(R.id.accessibility_button,
                 new ButtonDispatcher(R.id.accessibility_button));
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
     }
 
     public BarTransitions getBarTransitions() {
@@ -464,6 +466,10 @@
             disableBack = false;
             disableRecent = false;
         }
+        if (mOverviewProxyService.getProxy() != null) {
+            // When overview is connected to the launcher service, disable the recents button
+            disableRecent = true;
+        }
 
         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
@@ -779,7 +785,7 @@
         onPluginDisconnected(null); // Create default gesture helper
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
-        Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
+        mOverviewProxyService.addCallback(mOverviewProxyListener);
     }
 
     @Override
@@ -789,7 +795,7 @@
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
         }
-        Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
+        mOverviewProxyService.removeCallback(mOverviewProxyListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a1b49c1..91cae0af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -100,8 +100,10 @@
     }.setDuration(200).setDelay(50);
 
     public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+    public static final int MAX_STATIC_ICONS = 4;
+    private static final int MAX_DOTS = 3;
 
-    private boolean mShowAllIcons = true;
+    private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
     private int mStaticDotRadius;
@@ -115,11 +117,13 @@
     private int mSpeedBumpIndex = -1;
     private int mIconSize;
     private float mOpenedAmount = 0.0f;
-    private float mVisualOverflowAdaption;
     private boolean mDisallowNextAnimation;
     private boolean mAnimationsEnabled = true;
     private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
     private int mDarkOffsetX;
+    // Keep track of the last visible icon so collapsed container can report on its location
+    private IconState mLastVisibleIconState;
+
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -163,7 +167,7 @@
                 mIconSize = child.getWidth();
             }
         }
-        if (mShowAllIcons) {
+        if (mIsStaticLayout) {
             resetViewStates();
             calculateIconTranslations();
             applyIconStates();
@@ -287,7 +291,8 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+                    mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
         float layoutEnd = getLayoutEnd();
         float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
         boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -320,23 +325,6 @@
                     visualOverflowStart += (translationX - overflowStart) / mIconSize
                             * (mStaticDotRadius * 2 + mDotPadding);
                 }
-                if (mShowAllIcons) {
-                    // We want to perfectly position the overflow in the static state, such that
-                    // it's perfectly centered instead of measuring it from the end.
-                    mVisualOverflowAdaption = 0;
-                    if (firstOverflowIndex != -1) {
-                        View firstOverflowView = getChildAt(i);
-                        IconState overflowState = mIconStates.get(firstOverflowView);
-                        float totalAmount = layoutEnd - overflowState.xTranslation;
-                        float newPosition = overflowState.xTranslation + totalAmount / 2
-                                - totalDotLength / 2
-                                - mIconSize * 0.5f + mStaticDotRadius;
-                        mVisualOverflowAdaption = newPosition - visualOverflowStart;
-                        visualOverflowStart = newPosition;
-                    }
-                } else {
-                    visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
-                }
             }
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
@@ -348,20 +336,24 @@
                 IconState iconState = mIconStates.get(view);
                 int dotWidth = mStaticDotRadius * 2 + mDotPadding;
                 iconState.xTranslation = translationX;
-                if (numDots <= 3) {
+                if (numDots <= MAX_DOTS) {
                     if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
                         iconState.visibleState = StatusBarIconView.STATE_ICON;
                         numDots--;
                     } else {
                         iconState.visibleState = StatusBarIconView.STATE_DOT;
                     }
-                    translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+                    translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
                             * iconState.iconAppearAmount;
+                    mLastVisibleIconState = iconState;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
                 }
                 numDots++;
             }
+        } else if (childCount > 0) {
+            View lastChild = getChildAt(childCount - 1);
+            mLastVisibleIconState = mIconStates.get(lastChild);
         }
         boolean center = mDark;
         if (center && translationX < getLayoutEnd()) {
@@ -415,13 +407,13 @@
     }
 
     /**
-     * Sets whether the layout should always show all icons.
+     * Sets whether the layout should always show the same number of icons.
      * If this is true, the icon positions will be updated on layout.
      * If this if false, the layout is managed from the outside and layouting won't trigger a
      * repositioning of the icons.
      */
-    public void setShowAllIcons(boolean showAllIcons) {
-        mShowAllIcons = showAllIcons;
+    public void setIsStaticLayout(boolean isStaticLayout) {
+        mIsStaticLayout = isStaticLayout;
     }
 
     public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -452,6 +444,14 @@
         return mActualLayoutWidth;
     }
 
+    public int getFinalTranslationX() {
+        if (mLastVisibleIconState == null) {
+            return 0;
+        }
+
+        return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+    }
+
     public void setChangingViewPositions(boolean changingViewPositions) {
         mChangingViewPositions = changingViewPositions;
     }
@@ -479,19 +479,43 @@
         mOpenedAmount = expandAmount;
     }
 
-    public float getVisualOverflowAdaption() {
-        return mVisualOverflowAdaption;
-    }
-
-    public void setVisualOverflowAdaption(float visualOverflowAdaption) {
-        mVisualOverflowAdaption = visualOverflowAdaption;
-    }
-
     public boolean hasOverflow() {
+        if (mIsStaticLayout) {
+            return getChildCount() > MAX_STATIC_ICONS;
+        }
+
         float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
         return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
     }
 
+    /**
+     * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+     * extra padding will have to be accounted for
+     *
+     * This method has no meaning for non-static containers
+     */
+    public boolean hasPartialOverflow() {
+        if (mIsStaticLayout) {
+            int count = getChildCount();
+            return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get padding that can account for extra dots up to the max. The only valid values for
+     * this method are for 1 or 2 dots.
+     * @return only extraDotPadding or extraDotPadding * 2
+     */
+    public int getPartialOverflowExtraPadding() {
+        if (!hasPartialOverflow()) {
+            return 0;
+        }
+
+        return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
+    }
+
     public int getIconSize() {
         return mIconSize;
     }
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 61dd22f..f0bd1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -455,7 +455,7 @@
             mTopPaddingAdjustment = 0;
         } else {
             mClockPositionAlgorithm.setup(
-                    mStatusBar.getMaxKeyguardNotifications(),
+                    mStatusBar.getMaxNotificationsWhileLocked(),
                     getMaxPanelHeight(),
                     getExpandedHeight(),
                     mNotificationStackScroller.getNotGoneChildCount(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 168758f..14329b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.app.AlarmManager;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Color;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -73,6 +75,19 @@
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
+
+    /**
+     * When both scrims have 0 alpha.
+     */
+    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    /**
+     * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
+     */
+    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    /**
+     * When at least 1 scrim is fully opaque (alpha set to 1.)
+     */
+    public static final int VISIBILITY_FULLY_OPAQUE = 2;
     /**
      * Default alpha value for most scrims.
      */
@@ -111,6 +126,7 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
+    private final AlarmTimeout mTimeTicker;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -138,23 +154,25 @@
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
     private int mCurrentInFrontTint;
     private int mCurrentBehindTint;
+    private boolean mWallpaperVisibilityTimedOut;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
     private boolean mKeyguardFadingOutInProgress;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    private boolean mScrimsVisible;
-    private final Consumer<Boolean> mScrimVisibleListener;
+    private int mScrimsVisibility;
+    private final Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
     private Callback mCallback;
+    private boolean mWallpaperSupportsAmbientMode;
 
     private final WakeLock mWakeLock;
     private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
-            DozeParameters dozeParameters) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener,
+            DozeParameters dozeParameters, AlarmManager alarmManager) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
@@ -164,6 +182,8 @@
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
+                "hide_aod_wallpaper", new Handler());
         mWakeLock = createWakeLock();
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
@@ -235,14 +255,24 @@
             mKeyguardFadeoutAnimation.cancel();
         }
 
-        // Do not let the device sleep until we're done with all animations
-        if (!mWakeLockHeld) {
-            if (mWakeLock != null) {
-                mWakeLockHeld = true;
-                mWakeLock.acquire();
-            } else {
-                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+        // The device might sleep if it's entering AOD, we need to make sure that
+        // the animation plays properly until the last frame.
+        // It's important to avoid holding the wakelock unless necessary because
+        // WakeLock#aqcuire will trigger an IPC and will cause jank.
+        if (mState == ScrimState.AOD) {
+            holdWakeLock();
+        }
+
+        // AOD wallpapers should fade away after a while
+        if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
+                && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) {
+            if (!mWallpaperVisibilityTimedOut) {
+                mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
             }
+        } else {
+            mTimeTicker.cancel();
+            mWallpaperVisibilityTimedOut = false;
         }
 
         if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
@@ -279,6 +309,30 @@
         mTracking = false;
     }
 
+    @VisibleForTesting
+    protected void onHideWallpaperTimeout() {
+        if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
+            return;
+        }
+
+        holdWakeLock();
+        mWallpaperVisibilityTimedOut = true;
+        mAnimateChange = true;
+        mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
+        scheduleUpdate();
+    }
+
+    private void holdWakeLock() {
+        if (!mWakeLockHeld) {
+            if (mWakeLock != null) {
+                mWakeLockHeld = true;
+                mWakeLock.acquire();
+            } else {
+                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+            }
+        }
+    }
+
     /**
      * Current state of the shade expansion when pulling it from the top.
      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
@@ -391,6 +445,14 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
+        // We want to override the back scrim opacity for AOD and PULSING
+        // when it's time to fade the wallpaper away.
+        boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+                && mWallpaperVisibilityTimedOut;
+        if (overrideBackScrimAlpha) {
+            mCurrentBehindAlpha = 1;
+        }
+
         setScrimInFrontAlpha(mCurrentInFrontAlpha);
         setScrimBehindAlpha(mCurrentBehindAlpha);
 
@@ -398,12 +460,18 @@
     }
 
     private void dispatchScrimsVisible() {
-        boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
+        final int currentScrimVisibility;
+        if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+        } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+        } else {
+            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+        }
 
-        if (mScrimsVisible != scrimsVisible) {
-            mScrimsVisible = scrimsVisible;
-
-            mScrimVisibleListener.accept(scrimsVisible);
+        if (mScrimsVisibility != currentScrimVisibility) {
+            mScrimsVisibility = currentScrimVisibility;
+            mScrimVisibleListener.accept(currentScrimVisibility);
         }
     }
 
@@ -811,6 +879,14 @@
         pw.print("   mTracking="); pw.println(mTracking);
     }
 
+    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+        mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+        }
+    }
+
     public interface Callback {
         default void onStart() {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index c33cc50..fa2c1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,6 +19,7 @@
 import android.graphics.Color;
 import android.os.Trace;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -89,8 +90,10 @@
                 updateScrimColor(mScrimInFront, 1, Color.BLACK);
             }
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
-            mBlankScreen = previousState == ScrimState.PULSING;
-            mCurrentBehindAlpha = 1;
+            final boolean wasPulsing = previousState == ScrimState.PULSING;
+            mBlankScreen = wasPulsing && !mCanControlScreenOff;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
             mCurrentInFrontTint = Color.BLACK;
             mCurrentBehindTint = Color.BLACK;
@@ -106,9 +109,10 @@
     PULSING {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 1;
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
             if (mDisplayRequiresBlanking) {
@@ -158,6 +162,8 @@
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mCanControlScreenOff;
+    boolean mWallpaperSupportsAmbientMode;
+    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
@@ -165,6 +171,7 @@
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
         mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation();
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
     }
 
     public void prepare(ScrimState previousState) {
@@ -218,4 +225,8 @@
     public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
     }
+
+    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+        mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d162448..2da1e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -28,10 +28,6 @@
         .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager
-        .FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -46,6 +42,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlarmManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -53,6 +50,7 @@
 import android.app.StatusBarManager;
 import android.app.TaskStackBuilder;
 import android.app.WallpaperColors;
+import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -64,14 +62,12 @@
 import android.content.IntentSender;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -86,7 +82,6 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -101,12 +96,9 @@
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -136,9 +128,7 @@
 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.NotificationVisibility;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
@@ -148,11 +138,9 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -199,21 +187,22 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -236,10 +225,7 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
-        .OnChildLocationsChangedListener;
 import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -247,19 +233,12 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
-import java.util.function.Function;
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
-        ActivatableNotificationView.OnActivatedListener,
-        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
-        ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
+        OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
@@ -272,10 +251,6 @@
     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";
-
-
     // Should match the values in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -311,15 +286,12 @@
     // Time after we abort the launch transition.
     private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
 
-    private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+    protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
     private static final long AUTOHIDE_TIMEOUT_MS = 2250;
 
-    /** The minimum delay in ms between reports of notification visibility. */
-    private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
-
     /**
      * The delay to reset the hint text when the hint animation is finished running.
      */
@@ -397,13 +369,6 @@
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     private TextView mNotificationPanelDebugText;
 
-    /**
-     * {@code true} if notifications not part of a group should by default be rendered in their
-     * expanded state. If {@code false}, then only the first notification will be expanded if
-     * possible.
-     */
-    private boolean mAlwaysExpandNonGroupedNotification;
-
     // settings
     private QSPanel mQSPanel;
 
@@ -431,6 +396,9 @@
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     private NotificationGutsManager mGutsManager;
+    protected NotificationLogger mNotificationLogger;
+    protected NotificationEntryManager mEntryManager;
+    protected NotificationViewHierarchyManager mViewHierarchyManager;
 
     // for disabling the status bar
     private int mDisabled1 = 0;
@@ -482,23 +450,6 @@
     };
 
     protected final H mHandler = createHandler();
-    final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            boolean wasUsing = mUseHeadsUp;
-            mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                    && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                    mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                    Settings.Global.HEADS_UP_OFF);
-            Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-            if (wasUsing != mUseHeadsUp) {
-                if (!mUseHeadsUp) {
-                    Log.d(TAG, "dismissing any existing heads up notification on disable event");
-                    mHeadsUpManager.releaseAllImmediately();
-                }
-            }
-        }
-    };
 
     private int mInteractingWindows;
     private boolean mAutohideSuspended;
@@ -531,10 +482,22 @@
     protected NotificationLockscreenUserManager mLockscreenUserManager;
     protected NotificationRemoteInputManager mRemoteInputManager;
 
-    /** Keys of notifications currently visible to the user. */
-    private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
-            new ArraySet<>();
-    private long mLastVisibilityReportUptimeMs;
+    private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+            if (wallpaperManager == null) {
+                Log.w(TAG, "WallpaperManager not available");
+                return;
+            }
+            WallpaperInfo info = wallpaperManager.getWallpaperInfo();
+            final boolean supportsAmbientMode = info != null &&
+                    info.getSupportsAmbientMode();
+
+            mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+            mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+        }
+    };
 
     private Runnable mLaunchTransitionEndRunnable;
     protected boolean mLaunchTransitionFadingAway;
@@ -557,83 +520,6 @@
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
 
-    private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-                @Override
-                public void onChildLocationsChanged(
-                        NotificationStackScrollLayout stackScrollLayout) {
-                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
-                        // Visibilities will be reported when the existing
-                        // callback is executed.
-                        return;
-                    }
-                    // Calculate when we're allowed to run the visibility
-                    // reporter. Note that this timestamp might already have
-                    // passed. That's OK, the callback will just be executed
-                    // ASAP.
-                    long nextReportUptimeMs =
-                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
-                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
-                }
-            };
-
-    // Tracks notifications currently visible in mNotificationStackScroller and
-    // emits visibility events via NoMan on changes.
-    protected final Runnable mVisibilityReporter = new Runnable() {
-        private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
-                new ArraySet<>();
-        private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
-                new ArraySet<>();
-        private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
-                new ArraySet<>();
-
-        @Override
-        public void run() {
-            mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
-
-            // 1. Loop over mNotificationData entries:
-            //   A. Keep list of visible notifications.
-            //   B. Keep list of previously hidden, now visible notifications.
-            // 2. Compute no-longer visible notifications by removing currently
-            //    visible notifications from the set of previously visible
-            //    notifications.
-            // 3. Report newly visible and no-longer visible notifications.
-            // 4. Keep currently visible notifications for next report.
-            ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-            int N = activeNotifications.size();
-            for (int i = 0; i < N; i++) {
-                Entry entry = activeNotifications.get(i);
-                String key = entry.notification.getKey();
-                boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
-                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
-                boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
-                if (isVisible) {
-                    // Build new set of visible notifications.
-                    mTmpCurrentlyVisibleNotifications.add(visObj);
-                    if (!previouslyVisible) {
-                        mTmpNewlyVisibleNotifications.add(visObj);
-                    }
-                } else {
-                    // release object
-                    visObj.recycle();
-                }
-            }
-            mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
-            mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
-
-            logNotificationVisibilityChanges(
-                    mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
-
-            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
-            mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
-
-            recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
-            mTmpCurrentlyVisibleNotifications.clear();
-            mTmpNewlyVisibleNotifications.clear();
-            mTmpNoLongerVisibleNotifications.clear();
-        }
-    };
-
     // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
     // this animation is tied to the scrim for historic reasons.
     // TODO: notify when keyguard has faded away instead of the scrim.
@@ -657,7 +543,6 @@
         }
     };
 
-    private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
     private NetworkController mNetworkController;
@@ -672,31 +557,18 @@
     private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private SysuiColorExtractor mColorExtractor;
-    private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
     @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
 
-    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
-        final int N = array.size();
-        for (int i = 0 ; i < N; i++) {
-            array.valueAt(i).recycle();
-        }
-        array.clear();
-    }
-
     private final View.OnClickListener mGoToLockedShadeListener = v -> {
         if (mState == StatusBarState.KEYGUARD) {
             wakeUpIfDozing(SystemClock.uptimeMillis(), v);
             goToLockedShade(null);
         }
     };
-    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
-            mTmpChildOrderMap = new HashMap<>();
-    private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
@@ -715,7 +587,12 @@
 
     @Override
     public void start() {
+        mGroupManager = Dependency.get(NotificationGroupManager.class);
+        mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
+        mNotificationLogger = Dependency.get(NotificationLogger.class);
         mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+        mNotificationListener =  Dependency.get(NotificationListener.class);
+        mGroupManager = Dependency.get(NotificationGroupManager.class);
         mNetworkController = Dependency.get(NetworkController.class);
         mUserSwitcherController = Dependency.get(UserSwitcherController.class);
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -724,27 +601,25 @@
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mBatteryController = Dependency.get(BatteryController.class);
         mAssistManager = Dependency.get(AssistManager.class);
-        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
         mGutsManager = Dependency.get(NotificationGutsManager.class);
+        mMediaManager = Dependency.get(NotificationMediaManager.class);
+        mEntryManager = Dependency.get(NotificationEntryManager.class);
+        mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
-        mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
         mDisplay = mWindowManager.getDefaultDisplay();
         updateDisplaySize();
 
         Resources res = mContext.getResources();
         mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
-        mAlwaysExpandNonGroupedNotification =
-                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
         putComponent(StatusBar.class, this);
@@ -754,16 +629,12 @@
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        mNotificationData = new NotificationData(this);
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
-
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -773,8 +644,7 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
 
-        mMediaManager = new NotificationMediaManager(this, mContext);
-        mLockscreenUserManager.setUpWithPresenter(this);
+        mMediaManager.setUpWithPresenter(this, mEntryManager);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -795,6 +665,12 @@
 
         createAndAddWindows();
 
+        // Make sure we always have the most current wallpaper info.
+        IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+        mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
+        mWallpaperChangedReceiver.onReceive(mContext, null);
+
+        mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
         mCommandQueue.disable(switches[0], switches[6], false /* animate */);
         setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
                 fullscreenStackBounds, dockedStackBounds);
@@ -809,8 +685,7 @@
         }
 
         // Set up the initial notification state.
-        mNotificationListener = new NotificationListener(this, mRemoteInputManager, mContext);
-        mNotificationListener.register();
+        mNotificationListener.setUpWithPresenter(this, mEntryManager);
 
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -844,15 +719,6 @@
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
 
-        mHeadsUpObserver.onChange(true); // set up
-        if (ENABLE_HEADS_UP) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
-                    mHeadsUpObserver);
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
-                    mHeadsUpObserver);
-        }
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mUnlockMethodCache.addListener(this);
         startKeyguard();
@@ -887,7 +753,7 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
-        mGutsManager.setUp(this, mStackScroller, mCheckSaveListener,
+        mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
                 key -> {
                     try {
                         mBarService.onNotificationSettingsViewed(key);
@@ -895,6 +761,7 @@
                         // if we're here we're dead
                     }
                 });
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -933,11 +800,13 @@
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
-        mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
+        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
+
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
@@ -953,7 +822,7 @@
             // no window manager? good luck with that
         }
 
-        mStackScroller.setLongPressListener(getNotificationLongClicker());
+        mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1012,9 +881,9 @@
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
                 scrimsVisible -> {
                     if (mStatusBarWindowManager != null) {
-                        mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
+                        mStatusBarWindowManager.setScrimsVisibility(scrimsVisible);
                     }
-                }, new DozeParameters(mContext));
+                }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1176,7 +1045,7 @@
         MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
         } else {
             mReinflateNotificationsOnUserSwitched = true;
         }
@@ -1240,20 +1109,6 @@
         }
     }
 
-    private void updateNotificationsOnDensityOrFontScaleChanged() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        for (int i = 0; i < activeNotifications.size(); i++) {
-            Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = mGutsManager.getExposedGuts() != null
-                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
-            entry.row.onDensityOrFontScaleChanged();
-            if (exposedGuts) {
-                mGutsManager.setExposedGuts(entry.row.getGuts());
-                mGutsManager.bindGuts(entry.row);
-            }
-        }
-    }
-
     private void inflateSignalClusters() {
         if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
     }
@@ -1367,7 +1222,7 @@
             mStackScroller.setDismissAllInProgress(false);
             for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
                 if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                    removeNotification(rowToRemove.getEntry().key, null);
+                    mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
                 } else {
                     rowToRemove.resetTranslation();
                 }
@@ -1475,213 +1330,53 @@
         return true;
     }
 
-    void awakenDreams() {
-        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+    @Override
+    public void onPerformRemoveNotification(StatusBarNotification n) {
+        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+            // Finish the pulse.
+            mDozeScrimController.pulseOutNow();
+        }
     }
 
     @Override
-    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
-        String key = notification.getKey();
-        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+    public void updateNotificationViews() {
+        // The function updateRowStates depends on both of these being non-null, so check them here.
+        // We may be called before they are set from DeviceProvisionedController's callback.
+        if (mStackScroller == null || mScrimController == null) return;
 
-        mNotificationData.updateRanking(ranking);
-        Entry shadeEntry = null;
-        try {
-            shadeEntry = createNotificationViews(notification);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
+        // Do not modify the notifications during collapse.
+        if (isCollapsing()) {
+            addPostCollapseAction(this::updateNotificationViews);
             return;
         }
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
-            if (shouldSuppressFullScreenIntent(key)) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
-                }
-            } else if (mNotificationData.getImportance(key)
-                    < NotificationManager.IMPORTANCE_HIGH) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: not important enough: "
-                            + key);
-                }
-            } else {
-                // Stop screensaver if the notification has a fullscreen intent.
-                // (like an incoming phone call)
-                awakenDreams();
 
-                // not immersive & a fullscreen alert should be shown
-                if (DEBUG)
-                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
-                try {
-                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
-                            key);
-                    notification.getNotification().fullScreenIntent.send();
-                    shadeEntry.notifyFullScreenIntentLaunched();
-                    mMetricsLogger.count("note_fullscreen", 1);
-                } catch (PendingIntent.CanceledException e) {
-                }
-            }
-        }
-        abortExistingInflation(key);
+        mViewHierarchyManager.updateNotificationViews();
 
-        mForegroundServiceController.addNotification(notification,
-                mNotificationData.getImportance(key));
+        updateSpeedBumpIndex();
+        updateClearAll();
+        updateEmptyShadeView();
 
-        mPendingNotifications.put(key, shadeEntry);
+        updateQsExpansionEnabled();
+
+        // Let's also update the icons
+        mNotificationIconAreaController.updateNotificationIcons(
+                mEntryManager.getNotificationData());
     }
 
-    private void abortExistingInflation(String key) {
-        if (mPendingNotifications.containsKey(key)) {
-            Entry entry = mPendingNotifications.get(key);
-            entry.abortTask();
-            mPendingNotifications.remove(key);
-        }
-        Entry addedEntry = mNotificationData.get(key);
-        if (addedEntry != null) {
-            addedEntry.abortTask();
-        }
-    }
-
-    private void addEntry(Entry shadeEntry) {
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (isHeadsUped) {
-            mHeadsUpManager.showNotification(shadeEntry);
-            // Mark as seen immediately
-            setNotificationShown(shadeEntry.notification);
-        }
-        addNotificationViews(shadeEntry);
+    @Override
+    public void onNotificationAdded(Entry shadeEntry) {
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
     }
 
     @Override
-    public void handleInflationException(StatusBarNotification notification, Exception e) {
-        handleNotificationError(notification, e.getMessage());
+    public void onNotificationUpdated(StatusBarNotification notification) {
+        setAreThereNotifications();
     }
 
     @Override
-    public void onAsyncInflationFinished(Entry entry) {
-        mPendingNotifications.remove(entry.key);
-        // If there was an async task started after the removal, we don't want to add it back to
-        // the list, otherwise we might get leaks.
-        boolean isNew = mNotificationData.get(entry.key) == null;
-        if (isNew && !entry.row.isRemoved()) {
-            addEntry(entry);
-        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
-            mVisualStabilityManager.onLowPriorityUpdated(entry);
-            updateNotificationShade();
-        }
-        entry.row.setLowPriorityStateUpdated(false);
-    }
-
-    private boolean shouldSuppressFullScreenIntent(String key) {
-        if (isDeviceInVrMode()) {
-            return true;
-        }
-
-        if (mPowerManager.isInteractive()) {
-            return mNotificationData.shouldSuppressScreenOn(key);
-        } else {
-            return mNotificationData.shouldSuppressScreenOff(key);
-        }
-    }
-
-    @Override
-    public void updateNotificationRanking(RankingMap ranking) {
-        mNotificationData.updateRanking(ranking);
-        updateNotifications();
-    }
-
-    @Override
-    public void removeNotification(String key, RankingMap ranking) {
-        boolean deferRemoval = false;
-        abortExistingInflation(key);
-        if (mHeadsUpManager.isHeadsUp(key)) {
-            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
-            // sending look longer than it takes.
-            // Also we should not defer the removal if reordering isn't allowed since otherwise
-            // some notifications can't disappear before the panel is closed.
-            boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
-                    && !FORCE_REMOTE_INPUT_HISTORY
-                    || !mVisualStabilityManager.isReorderingAllowed();
-            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
-        }
-        mMediaManager.onNotificationRemoved(key);
-
-        Entry entry = mNotificationData.get(key);
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
-                && entry.row != null && !entry.row.isDismissed()) {
-            StatusBarNotification sbn = entry.notification;
-
-            Notification.Builder b = Notification.Builder
-                    .recoverBuilder(mContext, sbn.getNotification().clone());
-            CharSequence[] oldHistory = sbn.getNotification().extras
-                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
-            CharSequence[] newHistory;
-            if (oldHistory == null) {
-                newHistory = new CharSequence[1];
-            } else {
-                newHistory = new CharSequence[oldHistory.length + 1];
-                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
-            }
-            newHistory[0] = String.valueOf(entry.remoteInputText);
-            b.setRemoteInputHistory(newHistory);
-
-            Notification newNotification = b.build();
-
-            // Undo any compatibility view inflation
-            newNotification.contentView = sbn.getNotification().contentView;
-            newNotification.bigContentView = sbn.getNotification().bigContentView;
-            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
-            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
-                    sbn.getOpPkg(),
-                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
-            boolean updated = false;
-            try {
-                updateNotificationInternal(newSbn, null);
-                updated = true;
-            } catch (InflationException e) {
-                deferRemoval = false;
-            }
-            if (updated) {
-                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
-                mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
-                return;
-            }
-        }
-        if (deferRemoval) {
-            mLatestRankingMap = ranking;
-            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
-            return;
-        }
-
-        if (mRemoteInputManager.onRemoveNotification(entry)) {
-            mLatestRankingMap = ranking;
-            return;
-        }
-
-        if (entry != null && mGutsManager.getExposedGuts() != null
-                && mGutsManager.getExposedGuts() == entry.row.getGuts()
-                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
-            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
-            mLatestRankingMap = ranking;
-            mGutsManager.setKeyToRemoveOnGutsClosed(key);
-            return;
-        }
-
-        if (entry != null) {
-            mForegroundServiceController.removeNotification(entry.notification);
-        }
-
-        if (entry != null && entry.row != null) {
-            entry.row.setRemoved();
-            mStackScroller.cleanUpViewState(entry.row);
-        }
-        // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key);
-        StatusBarNotification old = removeNotificationViews(key, ranking);
+    public void onNotificationRemoved(String key, StatusBarNotification old) {
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
@@ -1698,195 +1393,6 @@
     }
 
     /**
-     * Ensures that the group children are cancelled immediately when the group summary is cancelled
-     * instead of waiting for the notification manager to send all cancels. Otherwise this could
-     * lead to flickers.
-     *
-     * This also ensures that the animation looks nice and only consists of a single disappear
-     * animation instead of multiple.
-     *  @param key the key of the notification was removed
-     *
-     */
-    private void handleGroupSummaryRemoved(String key) {
-        Entry entry = mNotificationData.get(key);
-        if (entry != null && entry.row != null
-                && entry.row.isSummaryWithChildren()) {
-            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
-                // We don't want to remove children for autobundled notifications as they are not
-                // always cancelled. We only remove them if they were dismissed by the user.
-                return;
-            }
-            List<ExpandableNotificationRow> notificationChildren =
-                    entry.row.getNotificationChildren();
-            for (int i = 0; i < notificationChildren.size(); i++) {
-                ExpandableNotificationRow row = notificationChildren.get(i);
-                if ((row.getStatusBarNotification().getNotification().flags
-                        & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    // the child is a foreground service notification which we can't remove!
-                    continue;
-                }
-                row.setKeepInParent(true);
-                // we need to set this state earlier as otherwise we might generate some weird
-                // animations
-                row.setRemoved();
-            }
-        }
-    }
-
-    protected void performRemoveNotification(StatusBarNotification n) {
-        Entry entry = mNotificationData.get(n.getKey());
-        mRemoteInputManager.onPerformRemoveNotification(n, entry);
-        // 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 {
-            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
-            if (isHeadsUp(n.getKey())) {
-                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
-            } else if (mStackScroller.hasPulsingNotifications()) {
-                dismissalSurface = NotificationStats.DISMISSAL_AOD;
-            }
-            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
-            removeNotification(n.getKey(), null);
-
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
-        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
-            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
-            // Finish the pulse.
-            mDozeScrimController.pulseOutNow();
-        }
-        // end old BaseStatusBar.performRemoveNotification.
-    }
-
-    private void updateNotificationShade() {
-        if (mStackScroller == null) return;
-
-        // Do not modify the notifications during collapse.
-        if (isCollapsing()) {
-            addPostCollapseAction(this::updateNotificationShade);
-            return;
-        }
-
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
-        final int N = activeNotifications.size();
-        for (int i = 0; i < N; i++) {
-            Entry ent = activeNotifications.get(i);
-            if (ent.row.isDismissed() || ent.row.isRemoved()) {
-                // we don't want to update removed notifications because they could
-                // temporarily become children if they were isolated before.
-                continue;
-            }
-            int userId = ent.notification.getUserId();
-
-            // Display public version of the notification if we need to redact.
-            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
-            // We can probably move some of this code there.
-            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
-                    mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
-            boolean sensitive = userPublic && needsRedaction;
-            boolean deviceSensitive = devicePublic
-                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
-                    mLockscreenUserManager.getCurrentUserId());
-            ent.row.setSensitive(sensitive, deviceSensitive);
-            ent.row.setNeedsRedaction(needsRedaction);
-            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
-                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
-                        ent.row.getStatusBarNotification());
-                List<ExpandableNotificationRow> orderedChildren =
-                        mTmpChildOrderMap.get(summary);
-                if (orderedChildren == null) {
-                    orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(summary, orderedChildren);
-                }
-                orderedChildren.add(ent.row);
-            } else {
-                toShow.add(ent.row);
-            }
-
-        }
-
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i=0; i< mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                toRemove.add((ExpandableNotificationRow) child);
-            }
-        }
-
-        for (ExpandableNotificationRow remove : toRemove) {
-            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transferring this notification to its parent, don't generate an
-                // animation
-                mStackScroller.setChildTransferInProgress(true);
-            }
-            if (remove.isSummaryWithChildren()) {
-                remove.removeAllChildren();
-            }
-            mStackScroller.removeView(remove);
-            mStackScroller.setChildTransferInProgress(false);
-        }
-
-        removeNotificationChildren();
-
-        for (int i = 0; i < toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mVisualStabilityManager.notifyViewAddition(v);
-                mStackScroller.addView(v);
-            }
-        }
-
-        addNotificationChildrenAndSort();
-
-        // So after all this work notifications still aren't sorted correctly.
-        // Let's do that now by advancing through toShow and mStackScroller in
-        // lock-step, making sure mStackScroller matches what we see in toShow.
-        int j = 0;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow targetChild = toShow.get(j);
-            if (child != targetChild) {
-                // Oops, wrong notification at this position. Put the right one
-                // here and advance both lists.
-                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
-                    mStackScroller.changeViewPosition(targetChild, i);
-                } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(this);
-                }
-            }
-            j++;
-
-        }
-
-        mVisualStabilityManager.onReorderingFinished();
-        // clear the map again for the next usage
-        mTmpChildOrderMap.clear();
-
-        updateRowStates();
-        updateSpeedBumpIndex();
-        updateClearAll();
-        updateEmptyShadeView();
-
-        updateQsExpansionEnabled();
-
-        // Let's also update the icons
-        mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
-    }
-
-    /**
      * Disable QS if device not provisioned.
      * If the user switcher is simple then disable QS during setup because
      * the user intends to use the lock screen user switcher, QS in not needed.
@@ -1901,81 +1407,6 @@
                 && !ONLY_CORE_APPS);
     }
 
-    private void addNotificationChildrenAndSort() {
-        // Let's now add all notification children which are missing
-        boolean orderChanged = false;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
-                    childIndex++) {
-                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
-                if (children == null || !children.contains(childView)) {
-                    if (childView.getParent() != null) {
-                        Log.wtf(TAG, "trying to add a notification child that already has " +
-                                "a parent. class:" + childView.getParent().getClass() +
-                                "\n child: " + childView);
-                        // This shouldn't happen. We can recover by removing it though.
-                        ((ViewGroup) childView.getParent()).removeView(childView);
-                    }
-                    mVisualStabilityManager.notifyViewAddition(childView);
-                    parent.addChildNotification(childView, childIndex);
-                    mStackScroller.notifyGroupChildAdded(childView);
-                }
-            }
-
-            // Finally after removing and adding has been performed we can apply the order.
-            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
-        }
-        if (orderChanged) {
-            mStackScroller.generateChildOrderChangedEvent();
-        }
-    }
-
-    private void removeNotificationChildren() {
-        // First let's remove all children which don't belong in the parents
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            if (children != null) {
-                toRemove.clear();
-                for (ExpandableNotificationRow childRow : children) {
-                    if ((orderedChildren == null
-                            || !orderedChildren.contains(childRow))
-                            && !childRow.keepInParent()) {
-                        toRemove.add(childRow);
-                    }
-                }
-                for (ExpandableNotificationRow remove : toRemove) {
-                    parent.removeChildNotification(remove);
-                    if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
-                        // We only want to add an animation if the view is completely removed
-                        // otherwise it's just a transfer
-                        mStackScroller.notifyGroupChildRemoved(remove,
-                                parent.getChildrenContainer());
-                    }
-                }
-            }
-        }
-    }
-
     public void addQsTile(ComponentName tile) {
         mQSPanel.getHost().addTile(tile);
     }
@@ -2018,7 +1449,7 @@
     private void updateEmptyShadeView() {
         boolean showEmptyShadeView =
                 mState != StatusBarState.KEYGUARD &&
-                        mNotificationData.getActiveNotifications().size() == 0;
+                        mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
         mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
     }
 
@@ -2033,7 +1464,8 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             currentIndex++;
-            if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+            if (!mEntryManager.getNotificationData().isAmbient(
+                    row.getStatusBarNotification().getKey())) {
                 speedBumpIndex = currentIndex;
             }
         }
@@ -2045,15 +1477,9 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    @Override
-    public void updateNotifications() {
-        mNotificationData.filterAndSort();
-
-        updateNotificationShade();
-    }
 
     public void requestNotificationUpdate() {
-        updateNotifications();
+        mEntryManager.updateNotifications();
     }
 
     protected void setAreThereNotifications() {
@@ -2062,7 +1488,7 @@
             final boolean clearable = hasActiveNotifications() &&
                     hasActiveClearableNotifications();
             Log.d(TAG, "setAreThereNotifications: N=" +
-                    mNotificationData.getActiveNotifications().size() + " any=" +
+                    mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
                     hasActiveNotifications() + " clearable=" + clearable);
         }
 
@@ -2347,9 +1773,8 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            mDisableNotificationAlerts =
-                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-            mHeadsUpObserver.onChange(true);
+            mEntryManager.setDisableNotificationAlerts(
+                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
         }
 
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -2412,10 +1837,40 @@
         return getBarState() == StatusBarState.KEYGUARD;
     }
 
+    @Override
     public boolean isDozing() {
         return mDozing;
     }
 
+    @Override
+    public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+        if (mIsOccluded && !isDozing()) {
+            boolean devicePublic = mLockscreenUserManager.
+                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                return false;
+            }
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else if (isDozing()) {
+                // We never want heads up when we are dozing.
+                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;
+    }
+
     @Override  // NotificationData.Environment
     public String getCurrentMediaNotificationKey() {
         return mMediaManager.getMediaNotificationKey();
@@ -2484,34 +1939,10 @@
 
     @Override
     public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
-        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
-            removeNotification(entry.key, mLatestRankingMap);
-            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
-                mLatestRankingMap = null;
-            }
-        } else {
-            updateNotificationRanking(null);
-            if (isHeadsUp) {
-                mDozeServiceHost.fireNotificationHeadsUp();
-            }
-        }
+        mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
 
-    }
-
-    protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
-            boolean alertAgain) {
-        final boolean wasHeadsUp = isHeadsUp(key);
-        if (wasHeadsUp) {
-            if (!shouldPeek) {
-                // We don't want this to be interrupting anymore, lets remove it
-                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
-            } else {
-                mHeadsUpManager.updateNotification(entry, alertAgain);
-            }
-        } else if (shouldPeek && alertAgain) {
-            // This notification was updated to be a heads-up, show it!
-            mHeadsUpManager.showNotification(entry);
+        if (isHeadsUp) {
+            mDozeServiceHost.fireNotificationHeadsUp();
         }
     }
 
@@ -2521,14 +1952,6 @@
         }
     }
 
-    public boolean isHeadsUp(String key) {
-        return mHeadsUpManager.isHeadsUp(key);
-    }
-
-    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
-        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
-    }
-
     public boolean isKeyguardCurrentlySecure() {
         return !mUnlockMethodCache.canSkipBouncer();
     }
@@ -2558,11 +1981,6 @@
         return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
-    @Override
-    public void onReorderingAllowed() {
-        updateNotifications();
-    }
-
     public boolean isLaunchTransitionFadingAway() {
         return mLaunchTransitionFadingAway;
     }
@@ -3196,14 +2614,6 @@
                     + " scroll " + mStackScroller.getScrollX()
                     + "," + mStackScroller.getScrollY());
         }
-        pw.print("  mPendingNotifications=");
-        if (mPendingNotifications.size() == 0) {
-            pw.println("null");
-        } else {
-            for (Entry entry : mPendingNotifications.values()) {
-                pw.println(entry.notification);
-            }
-        }
 
         pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
         pw.print("  mStatusBarWindowState=");
@@ -3215,8 +2625,7 @@
         pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
                 mContext.getContentResolver(), Settings.Global.ZEN_MODE,
                 Settings.Global.ZEN_MODE_OFF)));
-        pw.print("  mUseHeadsUp=");
-        pw.println(mUseHeadsUp);
+
         if (mStatusBarView != null) {
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
@@ -3258,8 +2667,8 @@
         }
 
         if (DUMPTRUCK) {
-            synchronized (mNotificationData) {
-                mNotificationData.dump(pw, "  ");
+            synchronized (mEntryManager.getNotificationData()) {
+                mEntryManager.getNotificationData().dump(pw, "  ");
             }
 
             if (false) {
@@ -3319,19 +2728,20 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() {
-            public void setRemoteInputActive(NotificationData.Entry entry,
-                    boolean remoteInputActive) {
-                mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
-            }
-            public void lockScrollTo(NotificationData.Entry entry) {
-                mStackScroller.lockScrollTo(entry.row);
-            }
-            public void requestDisallowLongPressAndDismiss() {
-                mStackScroller.requestDisallowLongPress();
-                mStackScroller.requestDisallowDismiss();
-            }
-        });
+        mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
+                new RemoteInputController.Delegate() {
+                    public void setRemoteInputActive(NotificationData.Entry entry,
+                            boolean remoteInputActive) {
+                        mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+                    }
+                    public void lockScrollTo(NotificationData.Entry entry) {
+                        mStackScroller.lockScrollTo(entry.row);
+                    }
+                    public void requestDisallowLongPressAndDismiss() {
+                        mStackScroller.requestDisallowLongPress();
+                        mStackScroller.requestDisallowDismiss();
+                    }
+                });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -3498,7 +2908,8 @@
     };
 
     public void resetUserExpandedStates() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+        ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
         final int notificationCount = activeNotifications.size();
         for (int i = 0; i < notificationCount; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
@@ -3541,7 +2952,7 @@
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
         }
 
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         mScreenPinningRequest.onConfigurationChanged();
     }
 
@@ -3553,12 +2964,12 @@
         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
         animateCollapsePanels();
         updatePublicMode();
-        mNotificationData.filterAndSort();
+        mEntryManager.getNotificationData().filterAndSort();
         if (mReinflateNotificationsOnUserSwitched) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
             mReinflateNotificationsOnUserSwitched = false;
         }
-        updateNotificationShade();
+        updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
     }
@@ -3568,6 +2979,13 @@
         return mLockscreenUserManager;
     }
 
+    @Override
+    public void onBindRow(Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setAboveShelfChangedListener(mAboveShelfObserver);
+        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+    }
+
     protected void setLockscreenUser(int newUserId) {
         mLockscreenWallpaper.setCurrentUser(newUserId);
         mScrimController.setCurrentUser(newUserId);
@@ -3617,9 +3035,9 @@
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
             handleVisibleToUserChangedImpl(visibleToUser);
-            startNotificationLogging();
+            mNotificationLogger.startNotificationLogging();
         } else {
-            stopNotificationLogging();
+            mNotificationLogger.stopNotificationLogging();
             handleVisibleToUserChangedImpl(visibleToUser);
         }
     }
@@ -3628,7 +3046,8 @@
         try {
             // consider the transition from peek to expanded to be a panel open,
             // but not one that clears notification effects.
-            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            int notificationLoad = mEntryManager.getNotificationData()
+                    .getActiveNotifications().size();
             mBarService.onPanelRevealed(false, notificationLoad);
         } catch (RemoteException ex) {
             // Won't fail unless the world has ended.
@@ -3646,7 +3065,8 @@
                     !isPresenterFullyCollapsed() &&
                             (mState == StatusBarState.SHADE
                                     || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+                    .size();
             if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
                 notificationLoad = 1;
             }
@@ -3671,60 +3091,6 @@
 
     }
 
-    private void stopNotificationLogging() {
-        // Report all notifications as invisible and turn down the
-        // reporter.
-        if (!mCurrentlyVisibleNotifications.isEmpty()) {
-            logNotificationVisibilityChanges(
-                    Collections.emptyList(), mCurrentlyVisibleNotifications);
-            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
-        }
-        mHandler.removeCallbacks(mVisibilityReporter);
-        mStackScroller.setChildLocationsChangedListener(null);
-    }
-
-    private void startNotificationLogging() {
-        mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
-        // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
-        // cause the scroller to emit child location events. Hence generate
-        // one ourselves to guarantee that we're reporting visible
-        // notifications.
-        // (Note that in cases where the scroller does emit events, this
-        // additional event doesn't break anything.)
-        mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
-    }
-
-    private void logNotificationVisibilityChanges(
-            Collection<NotificationVisibility> newlyVisible,
-            Collection<NotificationVisibility> noLongerVisible) {
-        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
-            return;
-        }
-        NotificationVisibility[] newlyVisibleAr =
-                newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
-        NotificationVisibility[] noLongerVisibleAr =
-                noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
-        mUiOffloadThread.submit(() -> {
-            try {
-                mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
-            } catch (RemoteException e) {
-                // Ignore.
-            }
-
-            final int N = newlyVisible.size();
-            if (N > 0) {
-                String[] newlyVisibleKeyAr = new String[N];
-                for (int i = 0; i < N; i++) {
-                    newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
-                }
-
-                setNotificationsShown(newlyVisibleKeyAr);
-            }
-        });
-    }
-
-    // State logging
-
     private void logStateToEventlog() {
         boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
         boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
@@ -3835,7 +3201,7 @@
         } catch (RemoteException e) {
             // Ignore.
         }
-        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+        mEntryManager.destroy();
         // End old BaseStatusBar.destroy().
         if (mStatusBarWindow != null) {
             mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4288,7 +3654,7 @@
         updateDozingState();
         updatePublicMode();
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
-        updateNotifications();
+        mEntryManager.updateNotifications();
         checkBarModes();
         updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
@@ -4359,7 +3725,7 @@
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         Trace.endSection();
     }
 
@@ -4563,7 +3929,8 @@
         }
     }
 
-    protected int getMaxKeyguardNotifications(boolean recompute) {
+    @Override
+    public int getMaxNotificationsWhileLocked(boolean recompute) {
         if (recompute) {
             mMaxKeyguardNotifications = Math.max(1,
                     mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4573,8 +3940,8 @@
         return mMaxKeyguardNotifications;
     }
 
-    public int getMaxKeyguardNotifications() {
-        return getMaxKeyguardNotifications(false /* recompute */);
+    public int getMaxNotificationsWhileLocked() {
+        return getMaxNotificationsWhileLocked(false /* recompute */);
     }
 
     // TODO: Figure out way to remove these.
@@ -4802,7 +4169,7 @@
     @Override
     public void onWorkChallengeChanged() {
         updatePublicMode();
-        updateNotifications();
+        mEntryManager.updateNotifications();
         if (mPendingWorkRemoteInputView != null
                 && !mLockscreenUserManager.isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
@@ -5012,7 +4379,7 @@
     }
 
     public boolean hasActiveNotifications() {
-        return !mNotificationData.getActiveNotifications().isEmpty();
+        return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
     }
 
     @Override
@@ -5391,10 +4758,9 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
-    protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
+    protected NotificationGroupManager mGroupManager;
 
 
     // for heads up notifications
@@ -5403,22 +4769,17 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
+    protected VisualStabilityManager mVisualStabilityManager;
 
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    protected boolean mUseHeadsUp = false;
-    protected boolean mDisableNotificationAlerts = false;
-
     protected DevicePolicyManager mDevicePolicyManager;
     protected PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -5427,7 +4788,6 @@
     private LockPatternUtils mLockPatternUtils;
     private DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
-    protected SystemServicesProxy mSystemServicesProxy;
 
     // UI-specific methods
 
@@ -5442,8 +4802,6 @@
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private final NotificationClicker mNotificationClicker = new NotificationClicker();
-
     protected AssistManager mAssistManager;
 
     protected boolean mVrMode;
@@ -5468,14 +4826,6 @@
         return mVrMode;
     }
 
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-        @Override
-        public void onDeviceProvisionedChanged() {
-            updateNotifications();
-        }
-    };
-
     private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -5500,6 +4850,121 @@
         }
     };
 
+    @Override
+    public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+        Notification notification = sbn.getNotification();
+        final PendingIntent intent = notification.contentIntent != null
+                ? notification.contentIntent
+                : notification.fullScreenIntent;
+        final String notificationKey = sbn.getKey();
+
+        final boolean afterKeyguardGone = intent.isActivity()
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mLockscreenUserManager.getCurrentUserId());
+        dismissKeyguardThenExecute(() -> {
+            // TODO: Some of this code may be able to move to NotificationEntryManager.
+            if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                // Release the HUN notification to the shade.
+
+                if (isPresenterFullyCollapsed()) {
+                    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;
+            final Runnable runnable = () -> {
+                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)) {
+                            // TODO(b/28935539): should allow certain activities to
+                            // bypass work challenge
+                            if (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(() -> {
+                        Runnable removeRunnable =
+                                () -> mEntryManager.performRemoveNotification(parentToCancelFinal);
+                        if (isCollapsing()) {
+                            // To avoid lags we're only performing the remove
+                            // after the shade was collapsed
+                            addPostCollapseAction(removeRunnable);
+                        } else {
+                            removeRunnable.run();
+                        }
+                    });
+                }
+            };
+
+            if (mStatusBarKeyguardViewManager.isShowing()
+                    && mStatusBarKeyguardViewManager.isOccluded()) {
+                mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+            } else {
+                new Thread(runnable).start();
+            }
+
+            if (!mNotificationPanel.isFullyCollapsed()) {
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                        true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            } else {
+                return false;
+            }
+        }, afterKeyguardGone);
+    }
+
     protected NotificationListener mNotificationListener;
 
     protected void notifyUserAboutHiddenNotifications() {
@@ -5561,18 +5026,6 @@
         return mLockscreenUserManager.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);
-        }
-    }
-
     @Override
     public NotificationGroupManager getGroupManager() {
         return mGroupManager;
@@ -5602,15 +5055,15 @@
         }
     }
 
-    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
-    }
-
     @Override
     public void toggleSplitScreen() {
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
     }
 
+    void awakenDreams() {
+        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+    }
+
     @Override
     public void preloadRecentApps() {
         int msg = MSG_PRELOAD_RECENT_APPS;
@@ -5688,104 +5141,14 @@
         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 */);
+            int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+            int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
             if (maxBefore != maxNotifications) {
-                updateRowStates();
+                mViewHierarchyManager.updateRowStates();
             }
         }
     }
 
-    protected void inflateViews(Entry entry, ViewGroup parent) {
-        PackageManager pmUser = getPackageManagerForUser(mContext,
-                entry.notification.getUser().getIdentifier());
-
-        final StatusBarNotification sbn = entry.notification;
-        if (entry.row != null) {
-            entry.reset();
-            updateNotification(entry, pmUser, sbn, entry.row);
-        } else {
-            new RowInflaterTask().inflate(mContext, parent, entry,
-                    row -> {
-                        bindRow(entry, pmUser, sbn, row);
-                        updateNotification(entry, pmUser, sbn, row);
-                    });
-        }
-
-    }
-
-    private void bindRow(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setExpansionLogger(this, entry.notification.getKey());
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setAboveShelfChangedListener(mAboveShelfObserver);
-        row.setOnExpandClickListener(this);
-        row.setInflationCallback(this);
-        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
-        row.setLongPressListener(getNotificationLongClicker());
-        mRemoteInputManager.bindRow(row);
-
-        // 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);
-        row.setOnDismissRunnable(() ->
-                performRemoveNotification(row.getStatusBarNotification()));
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-    }
-
-    private void updateNotification(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
-        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
-        boolean isUpdate = mNotificationData.get(entry.key) != null;
-        boolean wasLowPriority = row.isLowPriority();
-        row.setIsLowPriority(isLowPriority);
-        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
-        // bind the click event to the content area
-        mNotificationClicker.register(row, sbn);
-
-        // 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);
-        }
-        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
-                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
-        entry.row = row;
-        entry.row.setOnActivatedListener(this);
-
-        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
-                mNotificationData.getImportance(sbn.getKey()));
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.updateNotification(entry);
-    }
-
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
@@ -5829,166 +5192,15 @@
         }, 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 menu, if so slide notification back
-            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
-                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(() -> row.setJustClicked(false));
-
-            final boolean afterKeyguardGone = intent.isActivity()
-                    && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                            mLockscreenUserManager.getCurrentUserId());
-            dismissKeyguardThenExecute(() -> {
-                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                    // Release the HUN notification to the shade.
-
-                    if (isPresenterFullyCollapsed()) {
-                        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;
-                final Runnable runnable = () -> {
-                    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)) {
-                                // TODO(b/28935539): should allow certain activities to
-                                // bypass work challenge
-                                if (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(() -> {
-                            Runnable removeRunnable =
-                                    () -> performRemoveNotification(parentToCancelFinal);
-                            if (isCollapsing()) {
-                                // To avoid lags we're only performing the remove
-                                // after the shade was collapsed
-                                addPostCollapseAction(removeRunnable);
-                            } else {
-                                removeRunnable.run();
-                            }
-                        });
-                    }
-                };
-
-                if (mStatusBarKeyguardViewManager.isShowing()
-                        && mStatusBarKeyguardViewManager.isOccluded()) {
-                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                } else {
-                    new Thread(runnable).start();
-                }
-
-                if (!mNotificationPanel.isFullyCollapsed()) {
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                            true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                } else {
-                    return false;
-                }
-            }, afterKeyguardGone);
+    private boolean shouldAutoCancel(StatusBarNotification sbn) {
+        int flags = sbn.getNotification().flags;
+        if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+            return false;
         }
-
-        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;
+        if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+            return false;
         }
-
-        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);
-            }
-        }
+        return true;
     }
 
     protected Bundle getActivityOptions() {
@@ -6031,127 +5243,10 @@
     }
 
     /**
-     * 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();
-        Dependency.get(LeakDetector.class).trackGarbage(entry);
-        return entry.notification;
-    }
-
-    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
-            throws InflationException {
-        if (DEBUG) {
-            Log.d(TAG, "createNotificationViews(notification=" + sbn);
-        }
-        NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        Dependency.get(LeakDetector.class).trackInstance(entry);
-        entry.createIcons(mContext, sbn);
-        // Construct the expanded view.
-        inflateViews(entry, mStackScroller);
-        return entry;
-    }
-
-    protected void addNotificationViews(Entry entry) {
-        if (entry == null) {
-            return;
-        }
-        // Add the expanded view and icon.
-        mNotificationData.add(entry);
-        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 isChildNotification =
-                    mGroupManager.isChildInGroupWithSummary(entry.notification);
-
-            row.setOnKeyguard(onKeyguard);
-
-            if (!onKeyguard) {
-                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-                // very first notification and if it's not a child of grouped notifications.
-                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
-                        || (visibleNotifications == 0 && !isChildNotification
-                        && !row.isLowPriority()));
-            }
-
-            entry.row.setShowAmbient(isDozing());
-            int userId = entry.notification.getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.notification) && !entry.row.isRemoved();
-            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
-                    .notification);
-            if (suppressedSummary
-                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
-                            && !mLockscreenUserManager.shouldShowLockscreenNotifications())
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.row.setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.row.getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.row.setVisibility(View.VISIBLE);
-                }
-                if (!isChildNotification && !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));
-                }
-            }
-        }
-        mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+    @Override
+    public void onUpdateRowStates() {
         // The following views will be moved to the end of mStackScroller. This counter represents
         // the offset from the last child. Initialized to 1 for the very last position. It is post-
         // incremented in the following "changeViewPosition" calls so that its value is correct for
@@ -6174,163 +5269,10 @@
         mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
     }
 
-    // TODO: Move this to NotificationEntryManager once it is created.
-    private void updateNotificationInternal(StatusBarNotification notification,
-            RankingMap ranking) throws InflationException {
-        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
-        final String key = notification.getKey();
-        abortExistingInflation(key);
-        Entry entry = mNotificationData.get(key);
-        if (entry == null) {
-            return;
-        }
-        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-        mRemoteInputManager.onUpdateNotification(entry);
-
-        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
-            mGutsManager.setKeyToRemoveOnGutsClosed(null);
-            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
-        }
-
-        Notification n = notification.getNotification();
-        mNotificationData.updateRanking(ranking);
-
-        final StatusBarNotification oldNotification = entry.notification;
-        entry.notification = notification;
-        mGroupManager.onEntryUpdated(entry, oldNotification);
-
-        entry.updateIcons(mContext, notification);
-        inflateViews(entry, mStackScroller);
-
-        mForegroundServiceController.updateNotification(notification,
-                mNotificationData.getImportance(key));
-
-        boolean shouldPeek = shouldPeek(entry, notification);
-        boolean alertAgain = alertAgain(entry, n);
-
-        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();
-    }
-
-    @Override
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
-        try {
-            updateNotificationInternal(notification, ranking);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
-        }
-    }
-
     protected void notifyHeadsUpGoingToSleep() {
         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()) {
-            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
-            return false;
-        }
-
-        if (mNotificationData.shouldFilterOut(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
-            return false;
-        }
-
-        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
-        if (!inUse && !isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (isDozing() && mNotificationData.shouldSuppressScreenOff(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;
-        }
-
-        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
-        int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
-                : NotificationManager.IMPORTANCE_HIGH;
-        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
-            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
-            return false;
-        }
-
-        if (mIsOccluded && !isDozing()) {
-            boolean devicePublic = mLockscreenUserManager.
-                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
-            if (userPublic && needsRedaction) {
-                return false;
-            }
-        }
-
-        if (sbn.getNotification().fullScreenIntent != null) {
-            if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
-                return false;
-            } else if (mDozing) {
-                // We never want heads up when we are dozing.
-                return false;
-            } else {
-                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
-                return !mStatusBarKeyguardViewManager.isShowing()
-                        || mStatusBarKeyguardViewManager.isOccluded();
-            }
-        }
-
-        // Don't peek notifications that are suppressed due to group alert behavior
-        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
-            return false;
-        }
-
-        return true;
-    }
-
     /**
      * @return Whether the security bouncer from Keyguard is showing.
      */
@@ -6360,17 +5302,6 @@
         return contextForUser.getPackageManager();
     }
 
-    @Override
-    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mUiOffloadThread.submit(() -> {
-            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.
@@ -6418,20 +5349,10 @@
     }
 
     @Override
-    public NotificationData getNotificationData() {
-        return mNotificationData;
-    }
-
-    @Override
     public Handler getHandler() {
         return mHandler;
     }
 
-    @Override
-    public RankingMap getLatestRankingMap() {
-        return mLatestRankingMap;
-    }
-
     private final NotificationInfo.CheckSaveListener mCheckSaveListener =
             (Runnable saveImportance, StatusBarNotification sbn) -> {
                 // If the user has security enabled, show challenge if the setting is changed.
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 b0b5b8e..c30f633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -54,6 +54,7 @@
     private final Context mContext;
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
+    private final DozeParameters mDozeParameters;
     private View mStatusBarView;
     private WindowManager.LayoutParams mLp;
     private WindowManager.LayoutParams mLpChanged;
@@ -70,8 +71,8 @@
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mActivityManager = ActivityManager.getService();
         mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
-        mScreenBrightnessDoze = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+        mDozeParameters = new DozeParameters(mContext);
+        mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
     }
 
     private boolean shouldEnableKeyguardScreenRotation() {
@@ -136,7 +137,11 @@
             mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
 
-        if (state.keyguardShowing && !state.backdropShowing && !state.dozing) {
+        final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() &&
+                state.wallpaperSupportsAmbientMode &&
+                state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE;
+        if (state.keyguardShowing && !state.backdropShowing &&
+                (!state.dozing || showWallpaperOnAod)) {
             mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
         } else {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -188,7 +193,8 @@
     private boolean isExpanded(State state) {
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
-                || state.headsUpShowing || state.scrimsVisible);
+                || state.headsUpShowing
+                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -336,8 +342,8 @@
         apply(mCurrentState);
     }
 
-    public void setScrimsVisible(boolean scrimsVisible) {
-        mCurrentState.scrimsVisible = scrimsVisible;
+    public void setScrimsVisibility(int scrimsVisibility) {
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
     }
 
@@ -346,6 +352,11 @@
         apply(mCurrentState);
     }
 
+    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
+        apply(mCurrentState);
+    }
+
     /**
      * @param state The {@link StatusBarState} of the status bar.
      */
@@ -433,6 +444,7 @@
         boolean forceDozeBrightness;
         boolean forceUserActivity;
         boolean backdropShowing;
+        boolean wallpaperSupportsAmbientMode;
 
         /**
          * The {@link StatusBar} state from the status bar.
@@ -442,7 +454,7 @@
         boolean remoteInputActive;
         boolean forcePluginOpen;
         boolean dozing;
-        boolean scrimsVisible;
+        int scrimsVisibility;
 
         private boolean isKeyguardShowingAndNotOccluded() {
             return keyguardShowing && !keyguardOccluded;
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 fe39a89..369e7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -80,6 +80,8 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -112,7 +114,8 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+        NotificationListContainer {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -207,7 +210,7 @@
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
-    private OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -447,6 +450,7 @@
         }
     }
 
+    @Override
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -614,7 +618,9 @@
         mNoAmbient = noAmbient;
     }
 
-    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+    @Override
+    public void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
@@ -1325,6 +1331,7 @@
                 true /* isDismissAll */);
     }
 
+    @Override
     public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
         // If the child is showing the notification menu snap to that
@@ -1333,6 +1340,11 @@
     }
 
     @Override
+    public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+        return this;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -2053,6 +2065,7 @@
         return mAmbientState.isPulsing(entry);
     }
 
+    @Override
     public boolean hasPulsingNotifications() {
         return mPulsing != null;
     }
@@ -2610,10 +2623,7 @@
         }
     }
 
-    /**
-     * Called when a notification is removed from the shade. This cleans up the state for a given
-     * view.
-     */
+    @Override
     public void cleanUpViewState(View child) {
         if (child == mTranslatingParentView) {
             mTranslatingParentView = null;
@@ -2922,10 +2932,12 @@
         }
     }
 
+    @Override
     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
+    @Override
     public void notifyGroupChildAdded(View row) {
         onViewAddedInternal(row);
     }
@@ -2963,12 +2975,8 @@
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
-    /**
-     * Generate an animation for an added child view.
-     *
-     * @param child The view to be added.
-     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
-     */
+
+    @Override
     public void generateAddAnimation(View child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
             // Generate Animations
@@ -2984,12 +2992,7 @@
         }
     }
 
-    /**
-     * Change the position of child to a new location
-     *
-     * @param child the view to change the position for
-     * @param newIndex the new index
-     */
+    @Override
     public void changeViewPosition(View child, int newIndex) {
         int currentIndex = indexOfChild(child);
         if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -3705,7 +3708,7 @@
     private void applyCurrentState() {
         mCurrentStackScrollState.apply();
         if (mListener != null) {
-            mListener.onChildLocationsChanged(this);
+            mListener.onChildLocationsChanged();
         }
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
@@ -4189,6 +4192,26 @@
         }
     }
 
+    @Override
+    public int getContainerChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getContainerChildAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public void removeContainerView(View v) {
+        removeView(v);
+    }
+
+    @Override
+    public void addContainerView(View v) {
+        addView(v);
+    }
+
     public void runAfterAnimationFinished(Runnable runnable) {
         mAnimationFinishedRunnables.add(runnable);
     }
@@ -4445,13 +4468,6 @@
     }
 
     /**
-     * A listener that is notified when some child locations might have changed.
-     */
-    public interface OnChildLocationsChangedListener {
-        void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
-    }
-
-    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     public interface OnEmptySpaceClickListener {
@@ -4706,6 +4722,7 @@
         }
     }
 
+    @Override
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index fa82e33..f8843a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.volume;
 
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
+
 import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
 
 import android.bluetooth.BluetoothClass;
@@ -27,7 +31,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
 import android.util.Log;
 import android.util.Pair;
 
@@ -38,8 +50,13 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 
 public class OutputChooserDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
@@ -47,15 +64,33 @@
     private static final String TAG = Util.logTag(OutputChooserDialog.class);
     private static final int MAX_DEVICES = 10;
 
+    private static final long UPDATE_DELAY_MS = 300L;
+    static final int MSG_UPDATE_ITEMS = 1;
+
     private final Context mContext;
     private final BluetoothController mController;
+    private final WifiManager mWifiManager;
     private OutputChooserLayout mView;
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mRouterCallback;
+    private long mLastUpdateTime;
 
+    private final MediaRouteSelector mRouteSelector;
+    private Drawable mDefaultIcon;
+    private Drawable mTvIcon;
+    private Drawable mSpeakerIcon;
+    private Drawable mSpeakerGroupIcon;
 
     public OutputChooserDialog(Context context) {
         super(context);
         mContext = context;
         mController = Dependency.get(BluetoothController.class);
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mRouter = MediaRouter.getInstance(context);
+        mRouterCallback = new MediaRouterCallback();
+        mRouteSelector = new MediaRouteSelector.Builder()
+                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+                .build();
 
         final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mReceiver, filter);
@@ -67,10 +102,21 @@
         setContentView(R.layout.output_chooser);
         setCanceledOnTouchOutside(true);
         setOnDismissListener(this::onDismiss);
+        setTitle(R.string.output_title);
+
         mView = findViewById(R.id.output_chooser);
         mView.setCallback(this);
-        updateItems();
-        mController.addCallback(mCallback);
+
+        mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
+        mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
+        mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
+        mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
+
+        final boolean wifiOff = !mWifiManager.isWifiEnabled();
+        final boolean btOff = !mController.isBluetoothEnabled();
+        if (wifiOff || btOff) {
+            mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
+        }
     }
 
     protected void cleanUp() {}
@@ -82,43 +128,97 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mRouter.addCallback(mRouteSelector, mRouterCallback,
+                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+        mController.addCallback(mCallback);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mRouterCallback);
+        mController.removeCallback(mCallback);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
     public void onDismiss(DialogInterface unused) {
         mContext.unregisterReceiver(mReceiver);
-        mController.removeCallback(mCallback);
         cleanUp();
     }
 
     @Override
     public void onDetailItemClick(OutputChooserLayout.Item item) {
         if (item == null || item.tag == null) return;
-        final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-        if (device != null && device.getMaxConnectionState()
-                == BluetoothProfile.STATE_DISCONNECTED) {
-            mController.connect(device);
+        if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            if (device != null && device.getMaxConnectionState()
+                    == BluetoothProfile.STATE_DISCONNECTED) {
+                mController.connect(device);
+            }
+        } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+            final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
+            if (route.isEnabled()) {
+                route.select();
+            }
         }
     }
 
     @Override
     public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
         if (item == null || item.tag == null) return;
-        final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-        if (device != null) {
-            mController.disconnect(device);
+        if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            if (device != null) {
+                mController.disconnect(device);
+            }
+        } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+            mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
         }
     }
 
     private void updateItems() {
-        if (mView == null) return;
-        if (mController.isBluetoothEnabled()) {
-            mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                    R.string.quick_settings_bluetooth_detail_empty_text);
-            mView.setItemsVisible(true);
-        } else {
-            mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                    R.string.bt_is_off);
-            mView.setItemsVisible(false);
+        if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
+            mHandler.removeMessages(MSG_UPDATE_ITEMS);
+            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS),
+                    mLastUpdateTime + UPDATE_DELAY_MS);
+            return;
         }
+        mLastUpdateTime = SystemClock.uptimeMillis();
+        if (mView == null) return;
         ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
+
+        // Add bluetooth devices
+        addBluetoothDevices(items);
+
+        // Add remote displays
+        addRemoteDisplayRoutes(items);
+
+        Collections.sort(items, ItemComparator.sInstance);
+
+        if (items.size() == 0) {
+            String emptyMessage = mContext.getString(R.string.output_none_found);
+            final boolean wifiOff = !mWifiManager.isWifiEnabled();
+            final boolean btOff = !mController.isBluetoothEnabled();
+            if (wifiOff || btOff) {
+                emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
+            }
+            mView.setEmptyState(emptyMessage);
+        }
+
+        mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+    }
+
+    private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
+        return mContext.getString(R.string.output_none_found_service_off,
+                wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
+                        : wifiOff ? mContext.getString(R.string.output_service_wifi)
+                                : mContext.getString(R.string.output_service_bt));
+    }
+
+    private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
         final Collection<CachedBluetoothDevice> devices = mController.getDevices();
         if (devices != null) {
             int connectedDevices = 0;
@@ -134,6 +234,7 @@
                 item.iconResId = R.drawable.ic_qs_bluetooth_on;
                 item.line1 = device.getName();
                 item.tag = device;
+                item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
                 int state = device.getMaxConnectionState();
                 if (state == BluetoothProfile.STATE_CONNECTED) {
                     item.iconResId = R.drawable.ic_qs_bluetooth_connected;
@@ -163,7 +264,87 @@
                 }
             }
         }
-        mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+    }
+
+    private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
+        List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
+        for(MediaRouter.RouteInfo route : routes) {
+            if (route.isDefaultOrBluetooth() || !route.isEnabled()
+                    || !route.matchesSelector(mRouteSelector)) {
+                continue;
+            }
+            final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+            item.icon = getIconDrawable(route);
+            item.line1 = route.getName();
+            item.tag = route;
+            item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
+            if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
+                mContext.getString(R.string.quick_settings_connecting);
+            } else {
+                item.line2 = route.getDescription();
+            }
+
+            if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
+                item.canDisconnect = true;
+            }
+            items.add(item);
+        }
+    }
+
+    private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+        Uri iconUri = route.getIconUri();
+        if (iconUri != null) {
+            try {
+                InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+                Drawable drawable = Drawable.createFromStream(is, null);
+                if (drawable != null) {
+                    return drawable;
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to load " + iconUri, e);
+                // Falls back.
+            }
+        }
+        return getDefaultIconDrawable(route);
+    }
+
+    private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+        // If the type of the receiver device is specified, use it.
+        switch (route.getDeviceType()) {
+            case  MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+                return mTvIcon;
+            case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+                return mSpeakerIcon;
+        }
+
+        // Otherwise, make the best guess based on other route information.
+        if (route instanceof MediaRouter.RouteGroup) {
+            // Only speakers can be grouped for now.
+            return mSpeakerGroupIcon;
+        }
+        return mDefaultIcon;
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.Callback {
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+            dismiss();
+        }
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -188,4 +369,33 @@
             updateItems();
         }
     };
+
+    static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
+        public static final ItemComparator sInstance = new ItemComparator();
+
+        @Override
+        public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
+            // Connected item(s) first
+            if (lhs.canDisconnect != rhs.canDisconnect) {
+                return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
+            }
+            // Bluetooth items before media routes
+            if (lhs.deviceType != rhs.deviceType) {
+                return Integer.compare(lhs.deviceType, rhs.deviceType);
+            }
+            // then by name
+            return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
+        }
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_UPDATE_ITEMS:
+                    updateItems();
+                    break;
+            }
+        }
+    };
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
index e8be4fd..22ced60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
@@ -55,7 +55,6 @@
     private AutoSizingList mItemList;
     private View mEmpty;
     private TextView mEmptyText;
-    private ImageView mEmptyIcon;
 
     private Item[] mItems;
 
@@ -76,7 +75,6 @@
         mEmpty = findViewById(android.R.id.empty);
         mEmpty.setVisibility(GONE);
         mEmptyText = mEmpty.findViewById(android.R.id.title);
-        mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
     }
 
     @Override
@@ -93,9 +91,8 @@
         }
     }
 
-    public void setEmptyState(int icon, int text) {
+    public void setEmptyState(String text) {
         mEmpty.post(() -> {
-            mEmptyIcon.setImageResource(icon);
             mEmptyText.setText(text);
         });
     }
@@ -241,6 +238,8 @@
     }
 
     public static class Item {
+        public static int DEVICE_TYPE_BT = 1;
+        public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
         public int iconResId;
         public Drawable icon;
         public Drawable overlay;
@@ -249,6 +248,7 @@
         public Object tag;
         public boolean canDisconnect;
         public int icon2 = -1;
+        public int deviceType = 0;
     }
 
     public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 2129790..4464f75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -168,7 +168,7 @@
     }
 
     protected int getAudioManagerStreamMinVolume(int stream) {
-        return mAudio.getStreamMinVolume(stream);
+        return mAudio.getStreamMinVolumeInt(stream);
     }
 
     public void register() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
new file mode 100644
index 0000000..8e7f83d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.IWallpaperManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class DozeWallpaperStateTest extends SysuiTestCase {
+
+    @Test
+    public void testDreamNotification() throws RemoteException {
+        IWallpaperManager wallpaperManagerService = mock(IWallpaperManager.class);
+        DozeWallpaperState dozeWallpaperState = new DozeWallpaperState(wallpaperManagerService);
+        dozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
+                DozeMachine.State.DOZE_AOD);
+        verify(wallpaperManagerService).setInAmbientMode(eq(true));
+        dozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
+        verify(wallpaperManagerService).setInAmbientMode(eq(false));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
new file mode 100644
index 0000000..0a68389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.FrameLayout;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationEntryManagerTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private ExpandableNotificationRow mRow;
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private NotificationEntryManager.Callback mCallback;
+    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private NotificationListenerService.RankingMap mRankingMap;
+    @Mock private RemoteInputController mRemoteInputController;
+    @Mock private IStatusBarService mBarService;
+
+    // Dependency mocks:
+    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGutsManager mGutsManager;
+    @Mock private NotificationRemoteInputManager mRemoteInputManager;
+    @Mock private NotificationMediaManager mMediaManager;
+    @Mock private NotificationListener mNotificationListener;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private MetricsLogger mMetricsLogger;
+
+    private NotificationData.Entry mEntry;
+    private StatusBarNotification mSbn;
+    private Handler mHandler;
+    private TestableNotificationEntryManager mEntryManager;
+    private CountDownLatch mCountDownLatch;
+
+    private class TestableNotificationEntryManager extends NotificationEntryManager {
+        private final CountDownLatch mCountDownLatch;
+
+        public TestableNotificationEntryManager(Context context, IStatusBarService barService) {
+            super(context);
+            mBarService = barService;
+            mCountDownLatch = new CountDownLatch(1);
+            mUseHeadsUp = true;
+        }
+
+        @Override
+        public void onAsyncInflationFinished(NotificationData.Entry entry) {
+            super.onAsyncInflationFinished(entry);
+
+            mCountDownLatch.countDown();
+        }
+
+        public CountDownLatch getCountDownLatch() {
+            return mCountDownLatch;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(ForegroundServiceController.class,
+                mForegroundServiceController);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
+        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
+        mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
+        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
+        mDependency.injectTestDependency(DeviceProvisionedController.class,
+                mDeviceProvisionedController);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+
+        mHandler = new Handler(Looper.getMainLooper());
+        mCountDownLatch = new CountDownLatch(1);
+
+        when(mPresenter.getHandler()).thenReturn(mHandler);
+        when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
+        when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
+        when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+        when(mListContainer.getViewParentForNotification(any())).thenReturn(
+                new FrameLayout(mContext));
+
+        Notification.Builder n = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+                0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+        mEntry = new NotificationData.Entry(mSbn);
+        mEntry.expandedIcon = mock(StatusBarIconView.class);
+
+        mEntryManager = new TestableNotificationEntryManager(mContext, mBarService);
+        mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
+    }
+
+    @Test
+    public void testAddNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        doAnswer(invocation -> {
+            mCountDownLatch.countDown();
+            return null;
+        }).when(mCallback).onBindRow(any(), any(), any(), any());
+
+        // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
+        // callback forever, since it won't execute until the tests ends.
+        mHandler.post(() -> {
+            mEntryManager.addNotification(mSbn, mRankingMap);
+        });
+        assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES));
+        assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES));
+        waitForIdleSync(mHandler);
+
+        // Check that no inflation error occurred.
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+        verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
+
+        // Row inflation:
+        ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
+                NotificationData.Entry.class);
+        verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
+        NotificationData.Entry entry = entryCaptor.getValue();
+        verify(mRemoteInputManager).bindRow(entry.row);
+
+        // Row content inflation:
+        verify(mCallback).onNotificationAdded(entry);
+        verify(mPresenter).updateNotificationViews();
+
+        assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
+        assertNotNull(entry.row);
+    }
+
+    @Test
+    public void testUpdateNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        mEntryManager.getNotificationData().add(mEntry);
+
+        mHandler.post(() -> {
+            mEntryManager.updateNotification(mSbn, mRankingMap);
+        });
+        // Wait for content update.
+        mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES);
+        waitForIdleSync(mHandler);
+
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+
+        verify(mRemoteInputManager).onUpdateNotification(mEntry);
+        verify(mPresenter).updateNotificationViews();
+        verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
+        verify(mCallback).onNotificationUpdated(mSbn);
+        assertNotNull(mEntry.row);
+    }
+
+    @Test
+    public void testRemoveNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        mEntry.row = mRow;
+        mEntryManager.getNotificationData().add(mEntry);
+
+        mHandler.post(() -> {
+            mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
+        });
+        waitForIdleSync(mHandler);
+
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+
+        verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
+        verify(mRemoteInputManager).onRemoveNotification(mEntry);
+        verify(mForegroundServiceController).removeNotification(mSbn);
+        verify(mListContainer).cleanUpViewState(mRow);
+        verify(mPresenter).updateNotificationViews();
+        verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
+        verify(mRow).setRemoved();
+
+        assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index f562340..ef5f071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -19,7 +19,6 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +37,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -49,38 +50,45 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    private NotificationPresenter mPresenter;
-    private Handler mHandler;
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationListenerService.RankingMap mRanking;
+    @Mock private NotificationData mNotificationData;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationRemoteInputManager mRemoteInputManager;
+
     private NotificationListener mListener;
+    private Handler mHandler;
     private StatusBarNotification mSbn;
-    private NotificationListenerService.RankingMap mRanking;
     private Set<String> mKeysKeptForRemoteInput;
-    private NotificationData mNotificationData;
-    private NotificationRemoteInputManager mRemoteInputManager;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+                mRemoteInputManager);
+
         mHandler = new Handler(Looper.getMainLooper());
-        mPresenter = mock(NotificationPresenter.class);
-        mNotificationData = mock(NotificationData.class);
-        mRanking = mock(NotificationListenerService.RankingMap.class);
-        mRemoteInputManager = mock(NotificationRemoteInputManager.class);
         mKeysKeptForRemoteInput = new HashSet<>();
 
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
         when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
 
-        mListener = new NotificationListener(mPresenter, mRemoteInputManager, mContext);
+        mListener = new NotificationListener(mContext);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
+
+        mListener.setUpWithPresenter(mPresenter, mEntryManager);
     }
 
     @Test
     public void testNotificationAddCallsAddNotification() {
         mListener.onNotificationPosted(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).addNotification(mSbn, mRanking);
+        verify(mEntryManager).addNotification(mSbn, mRanking);
     }
 
     @Test
@@ -96,14 +104,14 @@
         when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
         mListener.onNotificationPosted(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).updateNotification(mSbn, mRanking);
+        verify(mEntryManager).updateNotification(mSbn, mRanking);
     }
 
     @Test
     public void testNotificationRemovalCallsRemoveNotification() {
         mListener.onNotificationRemoved(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).removeNotification(mSbn.getKey(), mRanking);
+        verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking);
     }
 
     @Test
@@ -111,6 +119,6 @@
         mListener.onNotificationRankingUpdate(mRanking);
         waitForIdleSync(mHandler);
         // RankingMap may be modified by plugins.
-        verify(mPresenter).updateNotificationRanking(any());
+        verify(mEntryManager).updateNotificationRanking(any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 4045995..cb8f7ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -22,7 +22,6 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -49,42 +48,47 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
-    private NotificationPresenter mPresenter;
-    private TestNotificationLockscreenUserManager mLockscreenUserManager;
-    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private UserManager mUserManager;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+
     private int mCurrentUserId;
     private Handler mHandler;
-    private UserManager mUserManager;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
 
     @Before
     public void setUp() {
-        mUserManager = mock(UserManager.class);
-        mContext.addMockSystemService(UserManager.class, mUserManager);
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(DeviceProvisionedController.class,
+                mDeviceProvisionedController);
+
         mHandler = new Handler(Looper.getMainLooper());
-        mDependency.injectMockDependency(DeviceProvisionedController.class);
-        mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class);
-        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+        mCurrentUserId = ActivityManager.getCurrentUser();
 
         when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList(
                 new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0)));
-
-        mPresenter = mock(NotificationPresenter.class);
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        mLockscreenUserManager.setUpWithPresenter(mPresenter);
-        mCurrentUserId = ActivityManager.getCurrentUser();
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter, mEntryManager);
     }
 
     @Test
     public void testLockScreenShowNotificationsChangeUpdatesNotifications() {
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-        verify(mPresenter, times(1)).updateNotifications();
+        verify(mEntryManager, times(1)).updateNotifications();
     }
 
     @Test
@@ -123,7 +127,7 @@
     public void testSettingsObserverUpdatesNotifications() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         mLockscreenUserManager.getSettingsObserverForTest().onChange(false);
-        verify(mPresenter, times(1)).updateNotifications();
+        verify(mEntryManager, times(1)).updateNotifications();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
new file mode 100644
index 0000000..726810e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLoggerTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private IStatusBarService mBarService;
+    @Mock private NotificationData mNotificationData;
+    @Mock private ExpandableNotificationRow mRow;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationListener mListener;
+
+    private NotificationData.Entry mEntry;
+    private StatusBarNotification mSbn;
+    private TestableNotificationLogger mLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationListener.class, mListener);
+
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+                0, new Notification(), UserHandle.CURRENT, null, 0);
+        mEntry = new NotificationData.Entry(mSbn);
+        mEntry.row = mRow;
+
+        mLogger = new TestableNotificationLogger(mBarService);
+        mLogger.setUpWithEntryManager(mEntryManager, mListContainer);
+    }
+
+    @Test
+    public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
+        when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        waitForIdleSync(mLogger.getHandlerForTest());
+        waitForUiOffloadThread();
+
+        NotificationVisibility[] newlyVisibleKeys = {
+                NotificationVisibility.obtain(mEntry.key, 0, true)
+        };
+        NotificationVisibility[] noLongerVisibleKeys = {};
+        verify(mBarService).onNotificationVisibilityChanged(newlyVisibleKeys, noLongerVisibleKeys);
+
+        // |mEntry| won't change visibility, so it shouldn't be reported again:
+        Mockito.reset(mBarService);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        waitForIdleSync(mLogger.getHandlerForTest());
+        waitForUiOffloadThread();
+
+        verify(mBarService, never()).onNotificationVisibilityChanged(any(), any());
+    }
+
+    @Test
+    public void testStoppingNotificationLoggingReportsCurrentNotifications()
+            throws Exception {
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
+        when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        waitForIdleSync(mLogger.getHandlerForTest());
+        waitForUiOffloadThread();
+        Mockito.reset(mBarService);
+
+        mLogger.stopNotificationLogging();
+        waitForUiOffloadThread();
+        // The visibility objects are recycled by NotificationLogger, so we can't use specific
+        // matchers here.
+        verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
+    }
+
+    private class TestableNotificationLogger extends NotificationLogger {
+
+        public TestableNotificationLogger(IStatusBarService barService) {
+            mBarService = barService;
+            // Make this on the main thread so we can wait for it during tests.
+            mHandler = new Handler(Looper.getMainLooper());
+        }
+
+        public OnChildLocationsChangedListener
+                getChildLocationsChangedListenerForTest() {
+            return mNotificationLocationsChangedListener;
+        }
+
+        public Handler getHandlerForTest() {
+            return mHandler;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index b881c09..4829cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -34,36 +34,41 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    private Handler mHandler;
-    private TestableNotificationRemoteInputManager mRemoteInputManager;
-    private StatusBarNotification mSbn;
-    private NotificationData.Entry mEntry;
-
     @Mock private NotificationPresenter mPresenter;
     @Mock private RemoteInputController.Delegate mDelegate;
-    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private RemoteInputController mController;
     @Mock private NotificationListenerService.RankingMap mRanking;
     @Mock private ExpandableNotificationRow mRow;
 
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+
+    private Handler mHandler;
+    private TestableNotificationRemoteInputManager mRemoteInputManager;
+    private StatusBarNotification mSbn;
+    private NotificationData.Entry mEntry;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
         mHandler = new Handler(Looper.getMainLooper());
 
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        when(mPresenter.getLatestRankingMap()).thenReturn(mRanking);
+        when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking);
 
-        mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager,
-                mContext);
+        mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                 0, new Notification(), UserHandle.CURRENT, null, 0);
         mEntry = new NotificationData.Entry(mSbn);
         mEntry.row = mRow;
 
-        mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate,
-                mController);
+        mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mEntryManager, mCallback,
+                mDelegate, mController);
     }
 
     @Test
@@ -97,21 +102,21 @@
 
         assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
         verify(mController).removeRemoteInput(mEntry, null);
-        verify(mPresenter).removeNotification(mEntry.key, mRanking);
+        verify(mEntryManager).removeNotification(mEntry.key, mRanking);
     }
 
     private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
 
-        public TestableNotificationRemoteInputManager(
-                NotificationLockscreenUserManager lockscreenUserManager, Context context) {
-            super(lockscreenUserManager, context);
+        public TestableNotificationRemoteInputManager(Context context) {
+            super(context);
         }
 
         public void setUpWithPresenterForTest(NotificationPresenter presenter,
+                NotificationEntryManager entryManager,
                 Callback callback,
                 RemoteInputController.Delegate delegate,
                 RemoteInputController controller) {
-            super.setUpWithPresenter(presenter, callback, delegate);
+            super.setUpWithPresenter(presenter, entryManager, callback, delegate);
             mRemoteInputController = controller;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
new file mode 100644
index 0000000..fbe730a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationData mNotificationData;
+    @Spy private FakeListContainer mListContainer = new FakeListContainer();
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+
+    private NotificationViewHierarchyManager mViewHierarchyManager;
+    private NotificationTestHelper mHelper = new NotificationTestHelper(mContext);;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
+        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
+        mViewHierarchyManager = new NotificationViewHierarchyManager(mContext);
+        mViewHierarchyManager.setUpWithPresenter(mPresenter, mEntryManager, mListContainer);
+    }
+
+    private NotificationData.Entry createEntry() throws Exception {
+        ExpandableNotificationRow row = mHelper.createRow();
+        NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification());
+        entry.row = row;
+        return entry;
+    }
+
+    @Test
+    public void testNotificationsBecomingBundled() throws Exception {
+        // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
+        // the summary.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+
+        // Set up the prior state to look like three top level notifications.
+        mListContainer.addContainerView(entry0.row);
+        mListContainer.addContainerView(entry1.row);
+        mListContainer.addContainerView(entry2.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true);
+        when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0.row);
+        when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0.row);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildAdded(entry1.row);
+        verify(mListContainer).notifyGroupChildAdded(entry2.row);
+        assertTrue(Lists.newArrayList(entry0.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingUnbundled() throws Exception {
+        // Tests a bundled notification becoming three top level notifications.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+        entry0.row.addChildNotification(entry2.row);
+
+        // Set up the prior state to look like one top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should not be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry2.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row, entry2.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingSuppressed() throws Exception {
+        // Tests two top level notifications becoming a suppressed summary and a child.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+
+        // Set up the prior state to look like a top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1));
+
+        // Set up group manager to report a suppressed summary now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row).equals(mListContainer.mRows));
+        assertEquals(View.GONE, entry0.row.getVisibility());
+        assertEquals(View.VISIBLE, entry1.row.getVisibility());
+    }
+
+    private class FakeListContainer implements NotificationListContainer {
+        final LinearLayout mLayout = new LinearLayout(mContext);
+        final List<View> mRows = Lists.newArrayList();
+
+        @Override
+        public void setChildTransferInProgress(boolean childTransferInProgress) {}
+
+        @Override
+        public void changeViewPosition(View child, int newIndex) {
+            mRows.remove(child);
+            mRows.add(newIndex, child);
+        }
+
+        @Override
+        public void notifyGroupChildAdded(View row) {}
+
+        @Override
+        public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {}
+
+        @Override
+        public void generateAddAnimation(View child, boolean fromMoreCard) {}
+
+        @Override
+        public void generateChildOrderChangedEvent() {}
+
+        @Override
+        public int getContainerChildCount() {
+            return mRows.size();
+        }
+
+        @Override
+        public View getContainerChildAt(int i) {
+            return mRows.get(i);
+        }
+
+        @Override
+        public void removeContainerView(View v) {
+            mLayout.removeView(v);
+            mRows.remove(v);
+        }
+
+        @Override
+        public void addContainerView(View v) {
+            mLayout.addView(v);
+            mRows.add(v);
+        }
+
+        @Override
+        public void setMaxDisplayedNotifications(int maxNotifications) {}
+
+        @Override
+        public void snapViewIfNeeded(ExpandableNotificationRow row) {}
+
+        @Override
+        public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+            return null;
+        }
+
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean animate) {}
+
+        @Override
+        public void resetExposedMenuView(boolean animate, boolean force) {}
+
+        @Override
+        public NotificationSwipeActionHelper getSwipeActionHelper() {
+            return null;
+        }
+
+        @Override
+        public void cleanUpViewState(View view) {}
+
+        @Override
+        public boolean isInVisibleLocation(ExpandableNotificationRow row) {
+            return true;
+        }
+
+        @Override
+        public void setChildLocationsChangedListener(
+                NotificationLogger.OnChildLocationsChangedListener listener) {}
+
+        @Override
+        public boolean hasPulsingNotifications() {
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a40ef64..6d2691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,13 +16,22 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
+import android.app.AlarmManager;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.Looper;
@@ -31,6 +40,7 @@
 import android.testing.TestableLooper;
 import android.view.View;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -52,12 +62,13 @@
     private ScrimView mScrimBehind;
     private ScrimView mScrimInFront;
     private View mHeadsUpScrim;
-    private Consumer<Boolean> mScrimVisibilityCallback;
-    private Boolean mScrimVisibile;
+    private Consumer<Integer> mScrimVisibilityCallback;
+    private int mScrimVisibility;
     private LightBarController mLightBarController;
     private DozeParameters mDozeParamenters;
     private WakeLock mWakeLock;
     private boolean mAlwaysOnEnabled;
+    private AlarmManager mAlarmManager;
 
     @Before
     public void setup() {
@@ -66,13 +77,15 @@
         mScrimInFront = new ScrimView(getContext());
         mHeadsUpScrim = mock(View.class);
         mWakeLock = mock(WakeLock.class);
+        mAlarmManager = mock(AlarmManager.class);
         mAlwaysOnEnabled = true;
-        mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+        mScrimVisibilityCallback = (Integer visible) -> mScrimVisibility = visible;
         mDozeParamenters = mock(DozeParameters.class);
         when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
         when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
         mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
-                mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+                mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters,
+                mAlarmManager);
     }
 
     @Test
@@ -87,29 +100,70 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
 
     @Test
-    public void transitionToAod() {
+    public void transitionToAod_withRegularWallpaper() {
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible with tint
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimTint(mScrimBehind, true /* tinted */);
+        assertScrimTint(mScrimInFront, true /* tinted */);
+    }
+
+    @Test
+    public void transitionToAod_withAodWallpaper() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be transparent
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        // Move on to PULSING and check if the back scrim is still transparent
+        mScrimController.transitionTo(ScrimState.PULSING);
+        mScrimController.finishAnimationsImmediately();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+    }
+
+    @Test
+    public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
+        ScrimState.AOD.mKeyguardUpdateMonitor = new KeyguardUpdateMonitor(getContext()) {
+            @Override
+            public boolean hasLockscreenWallpaper() {
+                return true;
+            }
+        };
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible with tint
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
         assertScrimTint(mScrimInFront, true /* tinted */);
     }
 
     @Test
     public void transitionToPulsing() {
+        // Pre-condition
+        // Need to go to AoD first because PULSING doesn't change
+        // the back scrim opacity - otherwise it would hide AoD wallpapers.
+        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible with tint
         // Pulse callback should have been invoked
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
     }
 
@@ -119,7 +173,7 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(true /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
 
@@ -129,13 +183,13 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be transparent
-        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
         assertScrimTint(mScrimInFront, false /* tinted */);
 
         // Back scrim should be visible after start dragging
         mScrimController.setPanelExpansion(0.5f);
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
     }
 
     @Test
@@ -151,7 +205,7 @@
         // Front scrim should be transparent
         // Back scrim should be transparent
         // Neither scrims should be tinted anymore after the animation.
-        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
         assertScrimTint(mScrimInFront, false /* tinted */);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
@@ -169,8 +223,8 @@
                         Assert.assertTrue("Scrim should be visible during transition. Alpha: "
                                 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
                         assertScrimTint(mScrimInFront, true /* tinted */);
-                        Assert.assertTrue("Scrim should be visible during transition.",
-                                mScrimVisibile);
+                        Assert.assertSame("Scrim should be visible during transition.",
+                                mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
                     }
                 });
         mScrimController.finishAnimationsImmediately();
@@ -222,12 +276,19 @@
     }
 
     @Test
-    public void testHoldsWakeLock() {
+    public void testHoldsWakeLock_whenAOD() {
         mScrimController.transitionTo(ScrimState.AOD);
-        verify(mWakeLock, times(1)).acquire();
+        verify(mWakeLock).acquire();
         verify(mWakeLock, never()).release();
         mScrimController.finishAnimationsImmediately();
-        verify(mWakeLock, times(1)).release();
+        verify(mWakeLock).release();
+    }
+
+    @Test
+    public void testDoesNotHoldWakeLock_whenUnlocking() {
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.finishAnimationsImmediately();
+        verifyZeroInteractions(mWakeLock);
     }
 
     @Test
@@ -236,7 +297,30 @@
         mScrimController.finishAnimationsImmediately();
         ScrimController.Callback callback = mock(ScrimController.Callback.class);
         mScrimController.transitionTo(ScrimState.UNLOCKED, callback);
-        verify(callback, times(1)).onFinished();
+        verify(callback).onFinished();
+    }
+
+    @Test
+    public void testHoldsAodWallpaperAnimationLock() {
+        // Pre-conditions
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        reset(mWakeLock);
+
+        mScrimController.onHideWallpaperTimeout();
+        verify(mWakeLock).acquire();
+        verify(mWakeLock, never()).release();
+        mScrimController.finishAnimationsImmediately();
+        verify(mWakeLock).release();
+    }
+
+    @Test
+    public void testWillHideAoDWallpaper() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.AOD);
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
     private void assertScrimTint(ScrimView scrimView, boolean tinted) {
@@ -247,12 +331,23 @@
                 tinted, viewIsTinted);
     }
 
-    private void assertScrimVisibility(boolean inFront, boolean behind) {
+    private void assertScrimVisibility(int inFront, int behind) {
+        boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+        boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
         Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
-                + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+                + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
         Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
-                + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
-        Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+                + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
+
+        final int visibility;
+        if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
+            visibility = VISIBILITY_FULLY_OPAQUE;
+        } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
+            visibility = VISIBILITY_SEMI_TRANSPARENT;
+        } else {
+            visibility = VISIBILITY_FULLY_TRANSPARENT;
+        }
+        Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
     }
 
     /**
@@ -264,9 +359,10 @@
 
         public SynchronousScrimController(LightBarController lightBarController,
                 ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-                Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+                Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
+                AlarmManager alarmManager) {
             super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                    scrimVisibleListener, dozeParameters);
+                    scrimVisibleListener, dozeParameters, alarmManager);
             mHandler = new FakeHandler(Looper.myLooper());
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index e4c33f1..99202f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -37,13 +37,13 @@
 import android.app.Notification;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
+import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IPowerManager;
-import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -52,9 +52,7 @@
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.DisplayMetrics;
 import android.util.SparseArray;
 import android.view.ViewGroup.LayoutParams;
 
@@ -63,8 +61,10 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -73,8 +73,18 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -84,6 +94,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
@@ -93,64 +105,71 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class StatusBarTest extends SysuiTestCase {
+    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private UnlockMethodCache mUnlockMethodCache;
+    @Mock private KeyguardIndicationController mKeyguardIndicationController;
+    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private SystemServicesProxy mSystemServicesProxy;
+    @Mock private NotificationPanelView mNotificationPanelView;
+    @Mock private IStatusBarService mBarService;
+    @Mock private ScrimController mScrimController;
+    @Mock private ArrayList<Entry> mNotificationList;
+    @Mock private FingerprintUnlockController mFingerprintUnlockController;
+    @Mock private NotificationData mNotificationData;
 
-    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    UnlockMethodCache mUnlockMethodCache;
-    KeyguardIndicationController mKeyguardIndicationController;
-    NotificationStackScrollLayout mStackScroller;
-    TestableStatusBar mStatusBar;
-    FakeMetricsLogger mMetricsLogger;
-    HeadsUpManager mHeadsUpManager;
-    NotificationData mNotificationData;
-    PowerManager mPowerManager;
-    SystemServicesProxy mSystemServicesProxy;
-    NotificationPanelView mNotificationPanelView;
-    ScrimController mScrimController;
-    IStatusBarService mBarService;
-    ArrayList<Entry> mNotificationList;
-    FingerprintUnlockController mFingerprintUnlockController;
-    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    // Mock dependencies:
+    @Mock private NotificationViewHierarchyManager mViewHierarchyManager;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private NotificationListener mNotificationListener;
+
+    private TestableStatusBar mStatusBar;
+    private FakeMetricsLogger mMetricsLogger;
+    private PowerManager mPowerManager;
+    private TestableNotificationEntryManager mEntryManager;
+    private NotificationLogger mNotificationLogger;
 
     @Before
     public void setup() throws Exception {
-        mContext.setTheme(R.style.Theme_SystemUI_Light);
+        MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(DeviceProvisionedController.class);
+        mDependency.injectMockDependency(NotificationGroupManager.class);
+        mDependency.injectMockDependency(NotificationGutsManager.class);
+        mDependency.injectMockDependency(NotificationRemoteInputManager.class);
+        mDependency.injectMockDependency(NotificationMediaManager.class);
+        mDependency.injectMockDependency(ForegroundServiceController.class);
+        mDependency.injectTestDependency(NotificationViewHierarchyManager.class,
+                mViewHierarchyManager);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
         mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
-        CommandQueue commandQueue = mock(CommandQueue.class);
-        when(commandQueue.asBinder()).thenReturn(new Binder());
-        mContext.putComponent(CommandQueue.class, commandQueue);
+
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
-        mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
-        mUnlockMethodCache = mock(UnlockMethodCache.class);
-        mKeyguardIndicationController = mock(KeyguardIndicationController.class);
-        mStackScroller = mock(NotificationStackScrollLayout.class);
-        when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+
         mMetricsLogger = new FakeMetricsLogger();
-        mHeadsUpManager = mock(HeadsUpManager.class);
-        mNotificationData = mock(NotificationData.class);
-        mSystemServicesProxy = mock(SystemServicesProxy.class);
-        mNotificationPanelView = mock(NotificationPanelView.class);
-        when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
-        mNotificationList = mock(ArrayList.class);
-        mScrimController = mock(ScrimController.class);
-        mFingerprintUnlockController = mock(FingerprintUnlockController.class);
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+        mNotificationLogger = new NotificationLogger();
+        mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
+
         IPowerManager powerManagerService = mock(IPowerManager.class);
         HandlerThread handlerThread = new HandlerThread("TestThread");
         handlerThread.start();
         mPowerManager = new PowerManager(mContext, powerManagerService,
                 new Handler(handlerThread.getLooper()));
-        when(powerManagerService.isInteractive()).thenReturn(true);
-        mBarService = mock(IStatusBarService.class);
 
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
-                mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
-                mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
-                mBarService, mScrimController, mFingerprintUnlockController);
-        mStatusBar.mContext = mContext;
-        mStatusBar.mComponents = mContext.getComponents();
+        CommandQueue commandQueue = mock(CommandQueue.class);
+        when(commandQueue.asBinder()).thenReturn(new Binder());
+        mContext.putComponent(CommandQueue.class, commandQueue);
+
+        mContext.setTheme(R.style.Theme_SystemUI_Light);
+
+        when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+        when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
+        when(powerManagerService.isInteractive()).thenReturn(true);
+        when(mStackScroller.getActivatedChild()).thenReturn(null);
+
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
             onDismissAction.onDismiss();
@@ -163,15 +182,24 @@
             return null;
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
-        when(mStackScroller.getActivatedChild()).thenReturn(null);
-        TestableLooper.get(this).setMessageHandler(new MessageHandler() {
-            @Override
-            public boolean onMessageHandled(Message m) {
-                if (m.getCallback() == mStatusBar.mVisibilityReporter) {
-                    return false;
-                }
-                return true;
+        mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager,
+                mContext);
+        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
+                mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
+                mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
+                mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
+                mEntryManager, mScrimController, mFingerprintUnlockController);
+        mStatusBar.mContext = mContext;
+        mStatusBar.mComponents = mContext.getComponents();
+        mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager,
+                mNotificationData);
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
+
+        TestableLooper.get(this).setMessageHandler(m -> {
+            if (m.getCallback() == mStatusBar.mNotificationLogger.getVisibilityReporter()) {
+                return false;
             }
+            return true;
         });
     }
 
@@ -328,7 +356,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -349,7 +377,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -369,7 +397,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -388,7 +416,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
     @Test
     public void testShouldPeek_suppressedScreenOff_dozing() {
@@ -406,7 +434,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -425,7 +453,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
 
@@ -558,22 +586,28 @@
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
-                NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
-                PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
-                IStatusBarService barService, ScrimController scrimController,
+                NotificationStackScrollLayout stack, HeadsUpManager hum,
+                PowerManager pm, NotificationPanelView panelView,
+                IStatusBarService barService, NotificationListener notificationListener,
+                NotificationLogger notificationLogger,
+                VisualStabilityManager visualStabilityManager,
+                NotificationViewHierarchyManager viewHierarchyManager,
+                TestableNotificationEntryManager entryManager, ScrimController scrimController,
                 FingerprintUnlockController fingerprintUnlockController) {
             mStatusBarKeyguardViewManager = man;
             mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
             mStackScroller = stack;
             mHeadsUpManager = hum;
-            mNotificationData = nd;
-            mUseHeadsUp = true;
             mPowerManager = pm;
-            mSystemServicesProxy = ssp;
             mNotificationPanel = panelView;
             mBarService = barService;
+            mNotificationListener = notificationListener;
+            mNotificationLogger = notificationLogger;
             mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+            mVisualStabilityManager = visualStabilityManager;
+            mViewHierarchyManager = viewHierarchyManager;
+            mEntryManager = entryManager;
             mScrimController = scrimController;
             mFingerprintUnlockController = fingerprintUnlockController;
         }
@@ -597,5 +631,26 @@
         public void setUserSetupForTest(boolean userSetup) {
             mUserSetup = userSetup;
         }
+
+    }
+
+    private class TestableNotificationEntryManager extends NotificationEntryManager {
+
+        public TestableNotificationEntryManager(SystemServicesProxy systemServicesProxy,
+                PowerManager powerManager, Context context) {
+            super(context);
+            mSystemServicesProxy = systemServicesProxy;
+            mPowerManager = powerManager;
+        }
+
+        public void setUpForTest(NotificationPresenter presenter,
+                NotificationListContainer listContainer,
+                Callback callback,
+                HeadsUpManager headsUpManager,
+                NotificationData notificationData) {
+            super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
+            mNotificationData = notificationData;
+            mUseHeadsUp = true;
+        }
     }
 }
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index c19ae52..5019a06 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -25,8 +25,8 @@
     <string name="data_received" msgid="4062776929376067820">"प्राप्त भयो:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> बाइटहरू / <xliff:g id="NUMBER_1">%2$s</xliff:g> प्याकेटहरू"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"सधैँ-सक्रिय रहने VPN सेवामा जडान गर्न सकिँदैन"</string>
-    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
-    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
+    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
+    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN सम्बन्धी सेटिङहरू परिवर्तन गर्नुहोस्"</string>
     <string name="configure" msgid="4905518375574791375">"कन्फिगर गर्नुहोस्"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
new file mode 100644
index 0000000..ed068b9
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -0,0 +1,1348 @@
+/*
+ ** 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.
+ */
+
+package com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.MagnificationSpec;
+import android.view.View;
+import android.view.accessibility.AccessibilityCache;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.DumpUtils;
+import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
+import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
+ * It is responsible for behavior common to both types of clients.
+ */
+abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
+        implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
+        FingerprintGestureDispatcher.FingerprintGestureClient {
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "AccessibilityClientConnection";
+
+    protected final Context mContext;
+    protected final SystemSupport mSystemSupport;
+    private final WindowManagerInternal mWindowManagerService;
+    private final GlobalActionPerformer mGlobalActionPerformer;
+
+    // Handler for scheduling method invocations on the main thread.
+    public final InvocationHandler mInvocationHandler;
+
+    final int mId;
+
+    final AccessibilityServiceInfo mAccessibilityServiceInfo;
+
+    // Lock must match the one used by AccessibilityManagerService
+    protected final Object mLock;
+
+    protected final SecurityPolicy mSecurityPolicy;
+
+    // The service that's bound to this instance. Whenever this value is non-null, this
+    // object is registered as a death recipient
+    IBinder mService;
+
+    IAccessibilityServiceClient mServiceInterface;
+
+    int mEventTypes;
+
+    int mFeedbackType;
+
+    Set<String> mPackageNames = new HashSet<>();
+
+    boolean mIsDefault;
+
+    boolean mRequestTouchExplorationMode;
+
+    boolean mRequestFilterKeyEvents;
+
+    boolean mRetrieveInteractiveWindows;
+
+    boolean mCaptureFingerprintGestures;
+
+    boolean mRequestAccessibilityButton;
+
+    boolean mReceivedAccessibilityButtonCallbackSinceBind;
+
+    boolean mLastAccessibilityButtonCallbackState;
+
+    int mFetchFlags;
+
+    long mNotificationTimeout;
+
+    final ComponentName mComponentName;
+
+    // the events pending events to be dispatched to this service
+    final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
+
+    /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+    boolean mUsesAccessibilityCache = false;
+
+    // Handler only for dispatching accessibility events since we use event
+    // types as message types allowing us to remove messages per event type.
+    public Handler mEventDispatchHandler;
+
+    final IBinder mOverlayWindowToken = new Binder();
+
+
+    public interface SystemSupport {
+        /**
+         * @return The current dispatcher for key events
+         */
+        @NonNull KeyEventDispatcher getKeyEventDispatcher();
+
+        /**
+         * @param windowId The id of the window of interest
+         * @return The magnification spec for the window, or {@code null} if none is available
+         */
+        @Nullable MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId);
+
+        /**
+         * @return The current injector of motion events, if one exists
+         */
+        @Nullable MotionEventInjector getMotionEventInjectorLocked();
+
+        /**
+         * @return The current dispatcher for fingerprint gestures, if one exists
+         */
+        @Nullable FingerprintGestureDispatcher getFingerprintGestureDispatcher();
+
+        /**
+         * @return The magnification controller
+         */
+        @NonNull MagnificationController getMagnificationController();
+
+        /**
+         * Resolve a connection wrapper for a window id
+         *
+         * @param windowId The id of the window of interest
+         *
+         * @return a connection to the window
+         */
+        RemoteAccessibilityConnection getConnectionLocked(int windowId);
+
+        /**
+         * Perform the specified accessibility action
+         *
+         * @param resolvedWindowId The window ID
+         * [Other parameters match the method on IAccessibilityServiceConnection]
+         *
+         * @return Whether or not the action could be sent to the app process
+         */
+        boolean performAccessibilityAction(int resolvedWindowId,
+                long accessibilityNodeId, int action, Bundle arguments, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
+                long interrogatingTid);
+
+        /**
+         * Replace the interaction callback if needed, for example if the window is in picture-
+         * in-picture mode and needs its nodes replaced.
+         *
+         * @param originalCallback The callback we were planning to use
+         * @param resolvedWindowId The ID of the window we're calling
+         * @param interactionId The id for the original callback
+         * @param interrogatingPid Process ID of requester
+         * @param interrogatingTid Thread ID of requester
+         *
+         * @return The callback to use, which may be the original one.
+         */
+        @NonNull IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
+                IAccessibilityInteractionConnectionCallback originalCallback,
+                int resolvedWindowId, int interactionId, int interrogatingPid,
+                long interrogatingTid);
+
+        /**
+         * Request that the system make sure windows are available to interrogate
+         */
+        void ensureWindowsAvailableTimed();
+
+        /**
+         * Called back to notify system that the client has changed
+         * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+         */
+        void onClientChange(boolean serviceInfoChanged);
+
+        int getCurrentUserIdLocked();
+
+        boolean isAccessibilityButtonShown();
+
+        /**
+         * Persists the component names in the specified setting in a
+         * colon separated fashion.
+         *
+         * @param settingName The setting name.
+         * @param componentNames The component names.
+         * @param userId The user id to persist the setting for.
+         */
+        void persistComponentNamesToSettingLocked(String settingName,
+                Set<ComponentName> componentNames, int userId);
+
+        /* This is exactly PendingIntent.getActivity, separated out for testability */
+        PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
+                int flags);
+    }
+
+    public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
+            AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
+            Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
+            WindowManagerInternal windowManagerInternal,
+            GlobalActionPerformer globalActionPerfomer) {
+        mContext = context;
+        mWindowManagerService = windowManagerInternal;
+        mId = id;
+        mComponentName = componentName;
+        mAccessibilityServiceInfo = accessibilityServiceInfo;
+        mLock = lock;
+        mSecurityPolicy = securityPolicy;
+        mGlobalActionPerformer = globalActionPerfomer;
+        mSystemSupport = systemSupport;
+        mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
+        mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
+            @Override
+            public void handleMessage(Message message) {
+                final int eventType =  message.what;
+                AccessibilityEvent event = (AccessibilityEvent) message.obj;
+                boolean serviceWantsEvent = message.arg1 != 0;
+                notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
+            }
+        };
+        setDynamicallyConfigurableProperties(accessibilityServiceInfo);
+    }
+
+    @Override
+    public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
+        if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+            return false;
+        }
+        if((mAccessibilityServiceInfo.getCapabilities()
+                & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
+            return false;
+        }
+        try {
+            mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+        } catch (RemoteException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+        mEventTypes = info.eventTypes;
+        mFeedbackType = info.feedbackType;
+        String[] packageNames = info.packageNames;
+        if (packageNames != null) {
+            mPackageNames.addAll(Arrays.asList(packageNames));
+        }
+        mNotificationTimeout = info.notificationTimeout;
+        mIsDefault = (info.flags & DEFAULT) != 0;
+
+        if (supportsFlagForNotImportantViews(info)) {
+            if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
+                mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            } else {
+                mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            }
+        }
+
+        if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+            mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+        } else {
+            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+        }
+
+        mRequestTouchExplorationMode = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+        mRequestFilterKeyEvents = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+        mRetrieveInteractiveWindows = (info.flags
+                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+        mCaptureFingerprintGestures = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+        mRequestAccessibilityButton = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+    }
+
+    protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
+        return info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.JELLY_BEAN;
+    }
+
+    public boolean canReceiveEventsLocked() {
+        return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
+    }
+
+    @Override
+    public void setOnKeyEventResult(boolean handled, int sequence) {
+        mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
+    }
+
+    @Override
+    public AccessibilityServiceInfo getServiceInfo() {
+        synchronized (mLock) {
+            return mAccessibilityServiceInfo;
+        }
+    }
+
+    int getRelevantEventTypes() {
+        return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK : 0)
+                | mEventTypes;
+    }
+
+    @Override
+    public void setServiceInfo(AccessibilityServiceInfo info) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                // If the XML manifest had data to configure the service its info
+                // should be already set. In such a case update only the dynamically
+                // configurable properties.
+                AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
+                if (oldInfo != null) {
+                    oldInfo.updateDynamicallyConfigurableProperties(info);
+                    setDynamicallyConfigurableProperties(oldInfo);
+                } else {
+                    setDynamicallyConfigurableProperties(info);
+                }
+                mSystemSupport.onClientChange(true);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    protected abstract boolean isCalledForCurrentUserLocked();
+
+    @Override
+    public List<AccessibilityWindowInfo> getWindows() {
+        mSystemSupport.ensureWindowsAvailableTimed();
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            final boolean permissionGranted =
+                    mSecurityPolicy.canRetrieveWindowsLocked(this);
+            if (!permissionGranted) {
+                return null;
+            }
+            if (mSecurityPolicy.mWindows == null) {
+                return null;
+            }
+            List<AccessibilityWindowInfo> windows = new ArrayList<>();
+            final int windowCount = mSecurityPolicy.mWindows.size();
+            for (int i = 0; i < windowCount; i++) {
+                AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(i);
+                AccessibilityWindowInfo windowClone =
+                        AccessibilityWindowInfo.obtain(window);
+                windowClone.setConnectionId(mId);
+                windows.add(windowClone);
+            }
+            return windows;
+        }
+    }
+
+    @Override
+    public AccessibilityWindowInfo getWindow(int windowId) {
+        mSystemSupport.ensureWindowsAvailableTimed();
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            final boolean permissionGranted =
+                    mSecurityPolicy.canRetrieveWindowsLocked(this);
+            if (!permissionGranted) {
+                return null;
+            }
+            AccessibilityWindowInfo window = mSecurityPolicy.findA11yWindowInfoById(windowId);
+            if (window != null) {
+                AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
+                windowClone.setConnectionId(mId);
+                return windowClone;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+            long accessibilityNodeId, String viewIdResName, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+            throws RemoteException {
+        final int resolvedWindowId;
+        RemoteAccessibilityConnection connection;
+        Region partialInteractiveRegion = Region.obtain();
+        MagnificationSpec spec;
+        synchronized (mLock) {
+            mUsesAccessibilityCache = true;
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+            final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+            if (!permissionGranted) {
+                return null;
+            } else {
+                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+                if (connection == null) {
+                    return null;
+                }
+            }
+            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+                    resolvedWindowId, partialInteractiveRegion)) {
+                partialInteractiveRegion.recycle();
+                partialInteractiveRegion = null;
+            }
+            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+        }
+        final int interrogatingPid = Binder.getCallingPid();
+        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+                interrogatingPid, interrogatingTid);
+        final int callingUid = Binder.getCallingUid();
+        final long identityToken = Binder.clearCallingIdentity();
+        try {
+            connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+                    viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
+                    interrogatingPid, interrogatingTid, spec);
+            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+                    connection.getPackageName(), connection.getUid());
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+            // Recycle if passed to another process.
+            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
+                partialInteractiveRegion.recycle();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
+            long accessibilityNodeId, String text, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+            throws RemoteException {
+        final int resolvedWindowId;
+        RemoteAccessibilityConnection connection;
+        Region partialInteractiveRegion = Region.obtain();
+        MagnificationSpec spec;
+        synchronized (mLock) {
+            mUsesAccessibilityCache = true;
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+            final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+            if (!permissionGranted) {
+                return null;
+            } else {
+                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+                if (connection == null) {
+                    return null;
+                }
+            }
+            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+                    resolvedWindowId, partialInteractiveRegion)) {
+                partialInteractiveRegion.recycle();
+                partialInteractiveRegion = null;
+            }
+            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+        }
+        final int interrogatingPid = Binder.getCallingPid();
+        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+                interrogatingPid, interrogatingTid);
+        final int callingUid = Binder.getCallingUid();
+        final long identityToken = Binder.clearCallingIdentity();
+        try {
+            connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
+                    text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
+                    interrogatingPid, interrogatingTid, spec);
+            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+                    connection.getPackageName(), connection.getUid());
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+            // Recycle if passed to another process.
+            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
+                partialInteractiveRegion.recycle();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfoByAccessibilityId(
+            int accessibilityWindowId, long accessibilityNodeId, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags,
+            long interrogatingTid, Bundle arguments) throws RemoteException {
+        final int resolvedWindowId;
+        RemoteAccessibilityConnection connection;
+        Region partialInteractiveRegion = Region.obtain();
+        MagnificationSpec spec;
+        synchronized (mLock) {
+            mUsesAccessibilityCache = true;
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+            final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+            if (!permissionGranted) {
+                return null;
+            } else {
+                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+                if (connection == null) {
+                    return null;
+                }
+            }
+            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+                    resolvedWindowId, partialInteractiveRegion)) {
+                partialInteractiveRegion.recycle();
+                partialInteractiveRegion = null;
+            }
+            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+        }
+        final int interrogatingPid = Binder.getCallingPid();
+        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+                interrogatingPid, interrogatingTid);
+        final int callingUid = Binder.getCallingUid();
+        final long identityToken = Binder.clearCallingIdentity();
+        try {
+            connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
+                    accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
+                    mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments);
+            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+                    connection.getPackageName(), connection.getUid());
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+            // Recycle if passed to another process.
+            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
+                partialInteractiveRegion.recycle();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId,
+            int focusType, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+            throws RemoteException {
+        final int resolvedWindowId;
+        RemoteAccessibilityConnection connection;
+        Region partialInteractiveRegion = Region.obtain();
+        MagnificationSpec spec;
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
+                    accessibilityWindowId, focusType);
+            final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+            if (!permissionGranted) {
+                return null;
+            } else {
+                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+                if (connection == null) {
+                    return null;
+                }
+            }
+            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+                    resolvedWindowId, partialInteractiveRegion)) {
+                partialInteractiveRegion.recycle();
+                partialInteractiveRegion = null;
+            }
+            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+        }
+        final int interrogatingPid = Binder.getCallingPid();
+        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+                interrogatingPid, interrogatingTid);
+        final int callingUid = Binder.getCallingUid();
+        final long identityToken = Binder.clearCallingIdentity();
+        try {
+            connection.getRemote().findFocus(accessibilityNodeId, focusType,
+                    partialInteractiveRegion, interactionId, callback, mFetchFlags,
+                    interrogatingPid, interrogatingTid, spec);
+            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+                    connection.getPackageName(), connection.getUid());
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Error calling findFocus()");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+            // Recycle if passed to another process.
+            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
+                partialInteractiveRegion.recycle();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId,
+            int direction, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+            throws RemoteException {
+        final int resolvedWindowId;
+        RemoteAccessibilityConnection connection;
+        Region partialInteractiveRegion = Region.obtain();
+        MagnificationSpec spec;
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return null;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+            final boolean permissionGranted =
+                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+            if (!permissionGranted) {
+                return null;
+            } else {
+                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+                if (connection == null) {
+                    return null;
+                }
+            }
+            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+                    resolvedWindowId, partialInteractiveRegion)) {
+                partialInteractiveRegion.recycle();
+                partialInteractiveRegion = null;
+            }
+            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+        }
+        final int interrogatingPid = Binder.getCallingPid();
+        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+                interrogatingPid, interrogatingTid);
+        final int callingUid = Binder.getCallingUid();
+        final long identityToken = Binder.clearCallingIdentity();
+        try {
+            connection.getRemote().focusSearch(accessibilityNodeId, direction,
+                    partialInteractiveRegion, interactionId, callback, mFetchFlags,
+                    interrogatingPid, interrogatingTid, spec);
+            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+                    connection.getPackageName(), connection.getUid());
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+            // Recycle if passed to another process.
+            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
+                partialInteractiveRegion.recycle();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int accessibilityWindowId,
+            long accessibilityNodeId, int action, Bundle arguments, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+            throws RemoteException {
+        final int resolvedWindowId;
+        IAccessibilityInteractionConnection connection = null;
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return false;
+            }
+            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+            if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId)) {
+                return false;
+            }
+        }
+        boolean returnValue =
+                mSystemSupport.performAccessibilityAction(resolvedWindowId, accessibilityNodeId,
+                action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
+        return returnValue;
+    }
+
+    @Override
+    public boolean performGlobalAction(int action) {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return false;
+            }
+        }
+        return mGlobalActionPerformer.performGlobalAction(action);
+    }
+
+    @Override
+    public boolean isFingerprintGestureDetectionAvailable() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return false;
+        }
+        if (isCapturingFingerprintGestures()) {
+            FingerprintGestureDispatcher dispatcher =
+                    mSystemSupport.getFingerprintGestureDispatcher();
+            return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
+        }
+        return false;
+    }
+
+    @Override
+    public float getMagnificationScale() {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return 1.0f;
+            }
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSystemSupport.getMagnificationController().getScale();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public Region getMagnificationRegion() {
+        synchronized (mLock) {
+            final Region region = Region.obtain();
+            if (!isCalledForCurrentUserLocked()) {
+                return region;
+            }
+            MagnificationController magnificationController =
+                    mSystemSupport.getMagnificationController();
+            boolean registeredJustForThisCall =
+                    registerMagnificationIfNeeded(magnificationController);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                magnificationController.getMagnificationRegion(region);
+                return region;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+                if (registeredJustForThisCall) {
+                    magnificationController.unregister();
+                }
+            }
+        }
+    }
+
+    @Override
+    public float getMagnificationCenterX() {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return 0.0f;
+            }
+            MagnificationController magnificationController =
+                    mSystemSupport.getMagnificationController();
+            boolean registeredJustForThisCall =
+                    registerMagnificationIfNeeded(magnificationController);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return magnificationController.getCenterX();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+                if (registeredJustForThisCall) {
+                    magnificationController.unregister();
+                }
+            }
+        }
+    }
+
+    @Override
+    public float getMagnificationCenterY() {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return 0.0f;
+            }
+            MagnificationController magnificationController =
+                    mSystemSupport.getMagnificationController();
+            boolean registeredJustForThisCall =
+                    registerMagnificationIfNeeded(magnificationController);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return magnificationController.getCenterY();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+                if (registeredJustForThisCall) {
+                    magnificationController.unregister();
+                }
+            }
+        }
+    }
+
+    private boolean registerMagnificationIfNeeded(
+            MagnificationController magnificationController) {
+        if (!magnificationController.isRegisteredLocked()
+                && mSecurityPolicy.canControlMagnification(this)) {
+            magnificationController.register();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean resetMagnification(boolean animate) {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return false;
+            }
+            if (!mSecurityPolicy.canControlMagnification(this)) {
+                return false;
+            }
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSystemSupport.getMagnificationController().reset(animate);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+            boolean animate) {
+        synchronized (mLock) {
+            if (!isCalledForCurrentUserLocked()) {
+                return false;
+            }
+            if (!mSecurityPolicy.canControlMagnification(this)) {
+                return false;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                MagnificationController magnificationController =
+                        mSystemSupport.getMagnificationController();
+                if (!magnificationController.isRegisteredLocked()) {
+                    magnificationController.register();
+                }
+                return magnificationController
+                        .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void setMagnificationCallbackEnabled(boolean enabled) {
+        mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+    }
+
+    public boolean isMagnificationCallbackEnabled() {
+        return mInvocationHandler.mIsMagnificationCallbackEnabled;
+    }
+
+    @Override
+    public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+        mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+        synchronized (mLock) {
+            pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo()
+                    .loadLabel(mContext.getPackageManager()));
+            pw.append(", feedbackType"
+                    + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
+            pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
+            pw.append(", eventTypes="
+                    + AccessibilityEvent.eventTypeToString(mEventTypes));
+            pw.append(", notificationTimeout=" + mNotificationTimeout);
+            pw.append("]");
+        }
+    }
+
+    public void onAdded() {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mWindowManagerService.addWindowToken(mOverlayWindowToken,
+                    TYPE_ACCESSIBILITY_OVERLAY, DEFAULT_DISPLAY);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    public void onRemoved() {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mWindowManagerService.removeWindowToken(mOverlayWindowToken, true, DEFAULT_DISPLAY);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    public void resetLocked() {
+        mSystemSupport.getKeyEventDispatcher().flush(this);
+        try {
+            // Clear the proxy in the other process so this
+            // IAccessibilityServiceConnection can be garbage collected.
+            if (mServiceInterface != null) {
+                mServiceInterface.init(null, mId, null);
+            }
+        } catch (RemoteException re) {
+                /* ignore */
+        }
+        if (mService != null) {
+            mService.unlinkToDeath(this, 0);
+            mService = null;
+        }
+
+        mServiceInterface = null;
+        mReceivedAccessibilityButtonCallbackSinceBind = false;
+    }
+
+    public boolean isConnectedLocked() {
+        return (mService != null);
+    }
+
+    public void notifyAccessibilityEvent(AccessibilityEvent event) {
+        synchronized (mLock) {
+            final int eventType = event.getEventType();
+
+            final boolean serviceWantsEvent = wantsEventLocked(event);
+            final boolean requiredForCacheConsistency = mUsesAccessibilityCache
+                    && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
+            if (!serviceWantsEvent && !requiredForCacheConsistency) {
+                return;
+            }
+
+            // Make a copy since during dispatch it is possible the event to
+            // be modified to remove its source if the receiving service does
+            // not have permission to access the window content.
+            AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
+            Message message;
+            if ((mNotificationTimeout > 0)
+                    && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
+                // Allow at most one pending event
+                final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+                mPendingEvents.put(eventType, newEvent);
+                if (oldEvent != null) {
+                    mEventDispatchHandler.removeMessages(eventType);
+                    oldEvent.recycle();
+                }
+                message = mEventDispatchHandler.obtainMessage(eventType);
+            } else {
+                // Send all messages, bypassing mPendingEvents
+                message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
+            }
+            message.arg1 = serviceWantsEvent ? 1 : 0;
+
+            mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
+        }
+    }
+
+    /**
+     * Determines if given event can be dispatched to a service based on the package of the
+     * event source. Specifically, a service is notified if it is interested in events from the
+     * package.
+     *
+     * @param event The event.
+     * @return True if the listener should be notified, false otherwise.
+     */
+    private boolean wantsEventLocked(AccessibilityEvent event) {
+
+        if (!canReceiveEventsLocked()) {
+            return false;
+        }
+
+        if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+                && !event.isImportantForAccessibility()
+                && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+            return false;
+        }
+
+        int eventType = event.getEventType();
+        if ((mEventTypes & eventType) != eventType) {
+            return false;
+        }
+
+        Set<String> packageNames = mPackageNames;
+        String packageName = (event.getPackageName() != null)
+                ? event.getPackageName().toString() : null;
+
+        return (packageNames.isEmpty() || packageNames.contains(packageName));
+    }
+
+    /**
+     * Notifies an accessibility service client for a scheduled event given the event type.
+     *
+     * @param eventType The type of the event to dispatch.
+     */
+    private void notifyAccessibilityEventInternal(
+            int eventType,
+            AccessibilityEvent event,
+            boolean serviceWantsEvent) {
+        IAccessibilityServiceClient listener;
+
+        synchronized (mLock) {
+            listener = mServiceInterface;
+
+            // If the service died/was disabled while the message for dispatching
+            // the accessibility event was propagating the listener may be null.
+            if (listener == null) {
+                return;
+            }
+
+            // There are two ways we notify for events, throttled AND non-throttled. If we
+            // are not throttling, then messages come with events, which we handle with
+            // minimal fuss.
+            if (event == null) {
+                // We are throttling events, so we'll send the event for this type in
+                // mPendingEvents as long as it it's null. It can only null due to a race
+                // condition:
+                //
+                //   1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+                //      which posts a message for dispatching an event and stores the event
+                //      in mPendingEvents.
+                //   2) The message is pulled from the queue by the handler on the service
+                //      thread and this method is just about to acquire the lock.
+                //   3) Another binder thread acquires the lock in notifyAccessibilityEvent
+                //   4) notifyAccessibilityEvent recycles the event that this method was about
+                //      to process, replaces it with a new one, and posts a second message
+                //   5) This method grabs the new event, processes it, and removes it from
+                //      mPendingEvents
+                //   6) The second message dispatched in (4) arrives, but the event has been
+                //      remvoved in (5).
+                event = mPendingEvents.get(eventType);
+                if (event == null) {
+                    return;
+                }
+                mPendingEvents.remove(eventType);
+            }
+            if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
+                event.setConnectionId(mId);
+            } else {
+                event.setSource((View) null);
+            }
+            event.setSealed(true);
+        }
+
+        try {
+            listener.onAccessibilityEvent(event, serviceWantsEvent);
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+            }
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+        } finally {
+            event.recycle();
+        }
+    }
+
+    public void notifyGesture(int gestureId) {
+        mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
+                gestureId, 0).sendToTarget();
+    }
+
+    public void notifyClearAccessibilityNodeInfoCache() {
+        mInvocationHandler.sendEmptyMessage(
+                InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
+    }
+
+    public void notifyMagnificationChangedLocked(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        mInvocationHandler
+                .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+    }
+
+    public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+        mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
+    }
+
+    public void notifyAccessibilityButtonClickedLocked() {
+        mInvocationHandler.notifyAccessibilityButtonClickedLocked();
+    }
+
+    public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+        mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+    }
+
+    /**
+     * Called by the invocation handler to notify the service that the
+     * state of magnification has changed.
+     */
+    private void notifyMagnificationChangedInternal(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onMagnificationChanged(region, scale, centerX, centerY);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+            }
+        }
+    }
+
+    /**
+     * Called by the invocation handler to notify the service that the state of the soft
+     * keyboard show mode has changed.
+     */
+    private void notifySoftKeyboardShowModeChangedInternal(int showState) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onSoftKeyboardShowModeChanged(showState);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+                        re);
+            }
+        }
+    }
+
+    private void notifyAccessibilityButtonClickedInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onAccessibilityButtonClicked();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+            }
+        }
+    }
+
+    private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+        // Only notify the service if it's not been notified or the state has changed
+        if (mReceivedAccessibilityButtonCallbackSinceBind
+                && (mLastAccessibilityButtonCallbackState == available)) {
+            return;
+        }
+        mReceivedAccessibilityButtonCallbackSinceBind = true;
+        mLastAccessibilityButtonCallbackState = available;
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onAccessibilityButtonAvailabilityChanged(available);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error sending accessibility button availability change to " + mService,
+                        re);
+            }
+        }
+    }
+
+    private void notifyGestureInternal(int gestureId) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onGesture(gestureId);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
+                        + " to " + mService, re);
+            }
+        }
+    }
+
+    private void notifyClearAccessibilityCacheInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.clearAccessibilityCache();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+                        + " to be cleared.", re);
+            }
+        }
+    }
+
+    private IAccessibilityServiceClient getServiceInterfaceSafely() {
+        synchronized (mLock) {
+            return mServiceInterface;
+        }
+    }
+
+    private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) {
+        if (accessibilityWindowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
+            return mSecurityPolicy.getActiveWindowId();
+        }
+        return accessibilityWindowId;
+    }
+
+    private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+        if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
+            return mSecurityPolicy.mActiveWindowId;
+        }
+        if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
+            if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
+                return mSecurityPolicy.mFocusedWindowId;
+            } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
+                return mSecurityPolicy.mAccessibilityFocusedWindowId;
+            }
+        }
+        return windowId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    private final class InvocationHandler extends Handler {
+        public static final int MSG_ON_GESTURE = 1;
+        public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
+
+        private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+        private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
+        private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
+        private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
+
+        private boolean mIsMagnificationCallbackEnabled = false;
+        private boolean mIsSoftKeyboardCallbackEnabled = false;
+
+        public InvocationHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            final int type = message.what;
+            switch (type) {
+                case MSG_ON_GESTURE: {
+                    final int gestureId = message.arg1;
+                    notifyGestureInternal(gestureId);
+                } break;
+
+                case MSG_CLEAR_ACCESSIBILITY_CACHE: {
+                    notifyClearAccessibilityCacheInternal();
+                } break;
+
+                case MSG_ON_MAGNIFICATION_CHANGED: {
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final Region region = (Region) args.arg1;
+                    final float scale = (float) args.arg2;
+                    final float centerX = (float) args.arg3;
+                    final float centerY = (float) args.arg4;
+                    notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+                    args.recycle();
+                } break;
+
+                case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
+                    final int showState = (int) message.arg1;
+                    notifySoftKeyboardShowModeChangedInternal(showState);
+                } break;
+
+                case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
+                    notifyAccessibilityButtonClickedInternal();
+                } break;
+
+                case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
+                    final boolean available = (message.arg1 != 0);
+                    notifyAccessibilityButtonAvailabilityChangedInternal(available);
+                } break;
+
+                default: {
+                    throw new IllegalArgumentException("Unknown message: " + type);
+                }
+            }
+        }
+
+        public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
+                float centerX, float centerY) {
+            if (!mIsMagnificationCallbackEnabled) {
+                // Callback is disabled, don't bother packing args.
+                return;
+            }
+
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = region;
+            args.arg2 = scale;
+            args.arg3 = centerX;
+            args.arg4 = centerY;
+
+            final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+            msg.sendToTarget();
+        }
+
+        public void setMagnificationCallbackEnabled(boolean enabled) {
+            mIsMagnificationCallbackEnabled = enabled;
+        }
+
+        public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+            if (!mIsSoftKeyboardCallbackEnabled) {
+                return;
+            }
+
+            final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
+            msg.sendToTarget();
+        }
+
+        public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+            mIsSoftKeyboardCallbackEnabled = enabled;
+        }
+
+        public void notifyAccessibilityButtonClickedLocked() {
+            final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
+            msg.sendToTarget();
+        }
+
+        public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+            final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
+                    (available ? 1 : 0), 0);
+            msg.sendToTarget();
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
deleted file mode 100644
index ad31cc5..0000000
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ /dev/null
@@ -1,1357 +0,0 @@
-/*
- ** 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.
- */
-
-package com.android.server.accessibility;
-
-import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ParceledListSlice;
-import android.graphics.Region;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.MagnificationSpec;
-import android.view.View;
-import android.view.accessibility.AccessibilityCache;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.accessibility.IAccessibilityInteractionConnection;
-import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-
-
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.DumpUtils;
-import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
-import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
- * It is responsible for behavior common to both types of clients.
- */
-abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
-        implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
-        FingerprintGestureDispatcher.FingerprintGestureClient {
-    private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "AccessibilityClientConnection";
-
-    protected final Context mContext;
-    protected final SystemSupport mSystemSupport;
-    private final WindowManagerInternal mWindowManagerService;
-    private final GlobalActionPerformer mGlobalActionPerformer;
-
-    // Handler for scheduling method invocations on the main thread.
-    public final InvocationHandler mInvocationHandler;
-
-    final int mId;
-
-    final AccessibilityServiceInfo mAccessibilityServiceInfo;
-
-    // Lock must match the one used by AccessibilityManagerService
-    protected final Object mLock;
-
-    protected final SecurityPolicy mSecurityPolicy;
-    private final AppOpsManager mAppOpsManager;
-
-    // The service that's bound to this instance. Whenever this value is non-null, this
-    // object is registered as a death recipient
-    IBinder mService;
-
-    IAccessibilityServiceClient mServiceInterface;
-
-    int mEventTypes;
-
-    int mFeedbackType;
-
-    Set<String> mPackageNames = new HashSet<>();
-
-    boolean mIsDefault;
-
-    boolean mRequestTouchExplorationMode;
-
-    boolean mRequestFilterKeyEvents;
-
-    boolean mRetrieveInteractiveWindows;
-
-    boolean mCaptureFingerprintGestures;
-
-    boolean mRequestAccessibilityButton;
-
-    boolean mReceivedAccessibilityButtonCallbackSinceBind;
-
-    boolean mLastAccessibilityButtonCallbackState;
-
-    int mFetchFlags;
-
-    long mNotificationTimeout;
-
-    final ComponentName mComponentName;
-
-    // the events pending events to be dispatched to this service
-    final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
-
-    /** Whether this service relies on its {@link AccessibilityCache} being up to date */
-    boolean mUsesAccessibilityCache = false;
-
-    // Handler only for dispatching accessibility events since we use event
-    // types as message types allowing us to remove messages per event type.
-    public Handler mEventDispatchHandler;
-
-    final IBinder mOverlayWindowToken = new Binder();
-
-
-    public interface SystemSupport {
-        /**
-         * @return The current dispatcher for key events
-         */
-        @NonNull KeyEventDispatcher getKeyEventDispatcher();
-
-        /**
-         * @param windowId The id of the window of interest
-         * @return The magnification spec for the window, or {@code null} if none is available
-         */
-        @Nullable MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId);
-
-        /**
-         * @return The current injector of motion events, if one exists
-         */
-        @Nullable MotionEventInjector getMotionEventInjectorLocked();
-
-        /**
-         * @return The current dispatcher for fingerprint gestures, if one exists
-         */
-        @Nullable FingerprintGestureDispatcher getFingerprintGestureDispatcher();
-
-        /**
-         * @return The magnification controller
-         */
-        @NonNull MagnificationController getMagnificationController();
-
-        /**
-         * Resolve a connection wrapper for a window id
-         *
-         * @param windowId The id of the window of interest
-         *
-         * @return a connection to the window
-         */
-        RemoteAccessibilityConnection getConnectionLocked(int windowId);
-
-        /**
-         * Perform the specified accessibility action
-         *
-         * @param resolvedWindowId The window ID
-         * [Other parameters match the method on IAccessibilityServiceConnection]
-         *
-         * @return Whether or not the action could be sent to the app process
-         */
-        boolean performAccessibilityAction(int resolvedWindowId,
-                long accessibilityNodeId, int action, Bundle arguments, int interactionId,
-                IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
-                long interrogatingTid);
-
-        /**
-         * Replace the interaction callback if needed, for example if the window is in picture-
-         * in-picture mode and needs its nodes replaced.
-         *
-         * @param originalCallback The callback we were planning to use
-         * @param resolvedWindowId The ID of the window we're calling
-         * @param interactionId The id for the original callback
-         * @param interrogatingPid Process ID of requester
-         * @param interrogatingTid Thread ID of requester
-         *
-         * @return The callback to use, which may be the original one.
-         */
-        @NonNull IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
-                IAccessibilityInteractionConnectionCallback originalCallback,
-                int resolvedWindowId, int interactionId, int interrogatingPid,
-                long interrogatingTid);
-
-        /**
-         * Request that the system make sure windows are available to interrogate
-         */
-        void ensureWindowsAvailableTimed();
-
-        /**
-         * Called back to notify system that the client has changed
-         * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
-         */
-        void onClientChange(boolean serviceInfoChanged);
-
-        int getCurrentUserIdLocked();
-
-        boolean isAccessibilityButtonShown();
-
-        /**
-         * Persists the component names in the specified setting in a
-         * colon separated fashion.
-         *
-         * @param settingName The setting name.
-         * @param componentNames The component names.
-         * @param userId The user id to persist the setting for.
-         */
-        void persistComponentNamesToSettingLocked(String settingName,
-                Set<ComponentName> componentNames, int userId);
-
-        /* This is exactly PendingIntent.getActivity, separated out for testability */
-        PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
-                int flags);
-    }
-
-    public AccessibilityClientConnection(Context context, ComponentName componentName,
-            AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
-            Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
-            WindowManagerInternal windowManagerInternal,
-            GlobalActionPerformer globalActionPerfomer) {
-        mContext = context;
-        mWindowManagerService = windowManagerInternal;
-        mId = id;
-        mComponentName = componentName;
-        mAccessibilityServiceInfo = accessibilityServiceInfo;
-        mLock = lock;
-        mSecurityPolicy = securityPolicy;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mGlobalActionPerformer = globalActionPerfomer;
-        mSystemSupport = systemSupport;
-        mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
-        mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                final int eventType =  message.what;
-                AccessibilityEvent event = (AccessibilityEvent) message.obj;
-                boolean serviceWantsEvent = message.arg1 != 0;
-                notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
-            }
-        };
-        setDynamicallyConfigurableProperties(accessibilityServiceInfo);
-    }
-
-    @Override
-    public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
-        if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
-            return false;
-        }
-        if((mAccessibilityServiceInfo.getCapabilities()
-                & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
-            return false;
-        }
-        try {
-            mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
-        } catch (RemoteException e) {
-            return false;
-        }
-        return true;
-    }
-
-    public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
-        mEventTypes = info.eventTypes;
-        mFeedbackType = info.feedbackType;
-        String[] packageNames = info.packageNames;
-        if (packageNames != null) {
-            mPackageNames.addAll(Arrays.asList(packageNames));
-        }
-        mNotificationTimeout = info.notificationTimeout;
-        mIsDefault = (info.flags & DEFAULT) != 0;
-
-        if (supportsFlagForNotImportantViews(info)) {
-            if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
-                mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            } else {
-                mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            }
-        }
-
-        if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
-            mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        } else {
-            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        }
-
-        mRequestTouchExplorationMode = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
-        mRequestFilterKeyEvents = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
-        mRetrieveInteractiveWindows = (info.flags
-                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
-        mCaptureFingerprintGestures = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
-        mRequestAccessibilityButton = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
-    }
-
-    protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
-        return info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
-                >= Build.VERSION_CODES.JELLY_BEAN;
-    }
-
-    public boolean canReceiveEventsLocked() {
-        return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
-    }
-
-    @Override
-    public void setOnKeyEventResult(boolean handled, int sequence) {
-        mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
-    }
-
-    @Override
-    public AccessibilityServiceInfo getServiceInfo() {
-        synchronized (mLock) {
-            return mAccessibilityServiceInfo;
-        }
-    }
-
-    @Override
-    public void setServiceInfo(AccessibilityServiceInfo info) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                // If the XML manifest had data to configure the service its info
-                // should be already set. In such a case update only the dynamically
-                // configurable properties.
-                AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
-                if (oldInfo != null) {
-                    oldInfo.updateDynamicallyConfigurableProperties(info);
-                    setDynamicallyConfigurableProperties(oldInfo);
-                } else {
-                    setDynamicallyConfigurableProperties(info);
-                }
-                mSystemSupport.onClientChange(true);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    protected abstract boolean isCalledForCurrentUserLocked();
-
-    @Override
-    public List<AccessibilityWindowInfo> getWindows() {
-        mSystemSupport.ensureWindowsAvailableTimed();
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            final boolean permissionGranted =
-                    mSecurityPolicy.canRetrieveWindowsLocked(this);
-            if (!permissionGranted) {
-                return null;
-            }
-            if (mSecurityPolicy.mWindows == null) {
-                return null;
-            }
-            List<AccessibilityWindowInfo> windows = new ArrayList<>();
-            final int windowCount = mSecurityPolicy.mWindows.size();
-            for (int i = 0; i < windowCount; i++) {
-                AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(i);
-                AccessibilityWindowInfo windowClone =
-                        AccessibilityWindowInfo.obtain(window);
-                windowClone.setConnectionId(mId);
-                windows.add(windowClone);
-            }
-            return windows;
-        }
-    }
-
-    @Override
-    public AccessibilityWindowInfo getWindow(int windowId) {
-        mSystemSupport.ensureWindowsAvailableTimed();
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            final boolean permissionGranted =
-                    mSecurityPolicy.canRetrieveWindowsLocked(this);
-            if (!permissionGranted) {
-                return null;
-            }
-            AccessibilityWindowInfo window = mSecurityPolicy.findA11yWindowInfoById(windowId);
-            if (window != null) {
-                AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
-                windowClone.setConnectionId(mId);
-                return windowClone;
-            }
-            return null;
-        }
-    }
-
-    @Override
-    public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
-            long accessibilityNodeId, String viewIdResName, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
-            throws RemoteException {
-        final int resolvedWindowId;
-        RemoteAccessibilityConnection connection;
-        Region partialInteractiveRegion = Region.obtain();
-        MagnificationSpec spec;
-        synchronized (mLock) {
-            mUsesAccessibilityCache = true;
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
-            final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
-            if (!permissionGranted) {
-                return null;
-            } else {
-                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
-                if (connection == null) {
-                    return null;
-                }
-            }
-            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
-                    resolvedWindowId, partialInteractiveRegion)) {
-                partialInteractiveRegion.recycle();
-                partialInteractiveRegion = null;
-            }
-            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
-        }
-        final int interrogatingPid = Binder.getCallingPid();
-        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
-                interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
-                    viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
-                    interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
-                    connection.getPackageName(), connection.getUid());
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-            // Recycle if passed to another process.
-            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
-                partialInteractiveRegion.recycle();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
-            long accessibilityNodeId, String text, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
-            throws RemoteException {
-        final int resolvedWindowId;
-        RemoteAccessibilityConnection connection;
-        Region partialInteractiveRegion = Region.obtain();
-        MagnificationSpec spec;
-        synchronized (mLock) {
-            mUsesAccessibilityCache = true;
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
-            final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
-            if (!permissionGranted) {
-                return null;
-            } else {
-                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
-                if (connection == null) {
-                    return null;
-                }
-            }
-            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
-                    resolvedWindowId, partialInteractiveRegion)) {
-                partialInteractiveRegion.recycle();
-                partialInteractiveRegion = null;
-            }
-            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
-        }
-        final int interrogatingPid = Binder.getCallingPid();
-        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
-                interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
-                    text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
-                    interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
-                    connection.getPackageName(), connection.getUid());
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-            // Recycle if passed to another process.
-            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
-                partialInteractiveRegion.recycle();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public String[] findAccessibilityNodeInfoByAccessibilityId(
-            int accessibilityWindowId, long accessibilityNodeId, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, int flags,
-            long interrogatingTid, Bundle arguments) throws RemoteException {
-        final int resolvedWindowId;
-        RemoteAccessibilityConnection connection;
-        Region partialInteractiveRegion = Region.obtain();
-        MagnificationSpec spec;
-        synchronized (mLock) {
-            mUsesAccessibilityCache = true;
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
-            final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
-            if (!permissionGranted) {
-                return null;
-            } else {
-                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
-                if (connection == null) {
-                    return null;
-                }
-            }
-            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
-                    resolvedWindowId, partialInteractiveRegion)) {
-                partialInteractiveRegion.recycle();
-                partialInteractiveRegion = null;
-            }
-            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
-        }
-        final int interrogatingPid = Binder.getCallingPid();
-        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
-                interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
-                    accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
-                    mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
-                    connection.getPackageName(), connection.getUid());
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-            // Recycle if passed to another process.
-            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
-                partialInteractiveRegion.recycle();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId,
-            int focusType, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
-            throws RemoteException {
-        final int resolvedWindowId;
-        RemoteAccessibilityConnection connection;
-        Region partialInteractiveRegion = Region.obtain();
-        MagnificationSpec spec;
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
-                    accessibilityWindowId, focusType);
-            final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
-            if (!permissionGranted) {
-                return null;
-            } else {
-                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
-                if (connection == null) {
-                    return null;
-                }
-            }
-            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
-                    resolvedWindowId, partialInteractiveRegion)) {
-                partialInteractiveRegion.recycle();
-                partialInteractiveRegion = null;
-            }
-            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
-        }
-        final int interrogatingPid = Binder.getCallingPid();
-        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
-                interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            connection.getRemote().findFocus(accessibilityNodeId, focusType,
-                    partialInteractiveRegion, interactionId, callback, mFetchFlags,
-                    interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
-                    connection.getPackageName(), connection.getUid());
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Slog.e(LOG_TAG, "Error calling findFocus()");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-            // Recycle if passed to another process.
-            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
-                partialInteractiveRegion.recycle();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId,
-            int direction, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
-            throws RemoteException {
-        final int resolvedWindowId;
-        RemoteAccessibilityConnection connection;
-        Region partialInteractiveRegion = Region.obtain();
-        MagnificationSpec spec;
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return null;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
-            final boolean permissionGranted =
-                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
-            if (!permissionGranted) {
-                return null;
-            } else {
-                connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
-                if (connection == null) {
-                    return null;
-                }
-            }
-            if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
-                    resolvedWindowId, partialInteractiveRegion)) {
-                partialInteractiveRegion.recycle();
-                partialInteractiveRegion = null;
-            }
-            spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
-        }
-        final int interrogatingPid = Binder.getCallingPid();
-        callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
-                interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            connection.getRemote().focusSearch(accessibilityNodeId, direction,
-                    partialInteractiveRegion, interactionId, callback, mFetchFlags,
-                    interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
-                    connection.getPackageName(), connection.getUid());
-        } catch (RemoteException re) {
-            if (DEBUG) {
-                Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-            // Recycle if passed to another process.
-            if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) {
-                partialInteractiveRegion.recycle();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int accessibilityWindowId,
-            long accessibilityNodeId, int action, Bundle arguments, int interactionId,
-            IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
-            throws RemoteException {
-
-        // Skip this if the caller is the Accessibility InteractionBridge.
-        if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
-            if (mAppOpsManager.noteOp(AppOpsManager.OP_PERFORM_ACCESSIBILITY_ACTION,
-                    Binder.getCallingUid(), mComponentName.getPackageName())
-                    != AppOpsManager.MODE_ALLOWED) {
-                return false;
-            }
-        }
-
-        final int resolvedWindowId;
-        IAccessibilityInteractionConnection connection = null;
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return false;
-            }
-            resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
-            if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId)) {
-                return false;
-            }
-        }
-        boolean returnValue =
-                mSystemSupport.performAccessibilityAction(resolvedWindowId, accessibilityNodeId,
-                action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
-        return returnValue;
-    }
-
-    @Override
-    public boolean performGlobalAction(int action) {
-        // Skip this if the caller is the Accessibility InteractionBridge.
-        if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
-            if (mAppOpsManager.noteOp(AppOpsManager.OP_PERFORM_ACCESSIBILITY_ACTION,
-                    Binder.getCallingUid(), mComponentName.getPackageName())
-                    != AppOpsManager.MODE_ALLOWED) {
-                return false;
-            }
-        }
-
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return false;
-            }
-        }
-        return mGlobalActionPerformer.performGlobalAction(action);
-    }
-
-    @Override
-    public boolean isFingerprintGestureDetectionAvailable() {
-        if (isCapturingFingerprintGestures()) {
-            FingerprintGestureDispatcher dispatcher =
-                    mSystemSupport.getFingerprintGestureDispatcher();
-            return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
-        }
-        return false;
-    }
-
-    @Override
-    public float getMagnificationScale() {
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return 1.0f;
-            }
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mSystemSupport.getMagnificationController().getScale();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
-    public Region getMagnificationRegion() {
-        synchronized (mLock) {
-            final Region region = Region.obtain();
-            if (!isCalledForCurrentUserLocked()) {
-                return region;
-            }
-            MagnificationController magnificationController =
-                    mSystemSupport.getMagnificationController();
-            boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                magnificationController.getMagnificationRegion(region);
-                return region;
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-                if (registeredJustForThisCall) {
-                    magnificationController.unregister();
-                }
-            }
-        }
-    }
-
-    @Override
-    public float getMagnificationCenterX() {
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return 0.0f;
-            }
-            MagnificationController magnificationController =
-                    mSystemSupport.getMagnificationController();
-            boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return magnificationController.getCenterX();
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-                if (registeredJustForThisCall) {
-                    magnificationController.unregister();
-                }
-            }
-        }
-    }
-
-    @Override
-    public float getMagnificationCenterY() {
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return 0.0f;
-            }
-            MagnificationController magnificationController =
-                    mSystemSupport.getMagnificationController();
-            boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return magnificationController.getCenterY();
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-                if (registeredJustForThisCall) {
-                    magnificationController.unregister();
-                }
-            }
-        }
-    }
-
-    private boolean registerMagnificationIfNeeded(
-            MagnificationController magnificationController) {
-        if (!magnificationController.isRegisteredLocked()
-                && mSecurityPolicy.canControlMagnification(this)) {
-            magnificationController.register();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean resetMagnification(boolean animate) {
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return false;
-            }
-            if (!mSecurityPolicy.canControlMagnification(this)) {
-                return false;
-            }
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mSystemSupport.getMagnificationController().reset(animate);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
-    public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
-            boolean animate) {
-        synchronized (mLock) {
-            if (!isCalledForCurrentUserLocked()) {
-                return false;
-            }
-            if (!mSecurityPolicy.canControlMagnification(this)) {
-                return false;
-            }
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                MagnificationController magnificationController =
-                        mSystemSupport.getMagnificationController();
-                if (!magnificationController.isRegisteredLocked()) {
-                    magnificationController.register();
-                }
-                return magnificationController
-                        .setScaleAndCenter(scale, centerX, centerY, animate, mId);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void setMagnificationCallbackEnabled(boolean enabled) {
-        mInvocationHandler.setMagnificationCallbackEnabled(enabled);
-    }
-
-    public boolean isMagnificationCallbackEnabled() {
-        return mInvocationHandler.mIsMagnificationCallbackEnabled;
-    }
-
-    @Override
-    public void setSoftKeyboardCallbackEnabled(boolean enabled) {
-        mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
-        synchronized (mLock) {
-            pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo()
-                    .loadLabel(mContext.getPackageManager()));
-            pw.append(", feedbackType"
-                    + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
-            pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
-            pw.append(", eventTypes="
-                    + AccessibilityEvent.eventTypeToString(mEventTypes));
-            pw.append(", notificationTimeout=" + mNotificationTimeout);
-            pw.append("]");
-        }
-    }
-
-    public void onAdded() {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mWindowManagerService.addWindowToken(mOverlayWindowToken,
-                    TYPE_ACCESSIBILITY_OVERLAY, DEFAULT_DISPLAY);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    public void onRemoved() {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mWindowManagerService.removeWindowToken(mOverlayWindowToken, true, DEFAULT_DISPLAY);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    public void resetLocked() {
-        mSystemSupport.getKeyEventDispatcher().flush(this);
-        try {
-            // Clear the proxy in the other process so this
-            // IAccessibilityServiceConnection can be garbage collected.
-            if (mServiceInterface != null) {
-                mServiceInterface.init(null, mId, null);
-            }
-        } catch (RemoteException re) {
-                /* ignore */
-        }
-        if (mService != null) {
-            mService.unlinkToDeath(this, 0);
-            mService = null;
-        }
-
-        mServiceInterface = null;
-        mReceivedAccessibilityButtonCallbackSinceBind = false;
-    }
-
-    public boolean isConnectedLocked() {
-        return (mService != null);
-    }
-
-    public void notifyAccessibilityEvent(AccessibilityEvent event) {
-        synchronized (mLock) {
-            final boolean serviceWantsEvent = wantsEventLocked(event);
-            if (!serviceWantsEvent && !mUsesAccessibilityCache &&
-                    ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
-                return;
-            }
-
-            final int eventType = event.getEventType();
-            // Make a copy since during dispatch it is possible the event to
-            // be modified to remove its source if the receiving service does
-            // not have permission to access the window content.
-            AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
-            Message message;
-            if ((mNotificationTimeout > 0)
-                    && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
-                // Allow at most one pending event
-                final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
-                mPendingEvents.put(eventType, newEvent);
-                if (oldEvent != null) {
-                    mEventDispatchHandler.removeMessages(eventType);
-                    oldEvent.recycle();
-                }
-                message = mEventDispatchHandler.obtainMessage(eventType);
-            } else {
-                // Send all messages, bypassing mPendingEvents
-                message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
-            }
-            message.arg1 = serviceWantsEvent ? 1 : 0;
-
-            mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
-        }
-    }
-
-    /**
-     * Determines if given event can be dispatched to a service based on the package of the
-     * event source. Specifically, a service is notified if it is interested in events from the
-     * package.
-     *
-     * @param event The event.
-     * @return True if the listener should be notified, false otherwise.
-     */
-    private boolean wantsEventLocked(AccessibilityEvent event) {
-
-        if (!canReceiveEventsLocked()) {
-            return false;
-        }
-
-        if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
-                && !event.isImportantForAccessibility()
-                && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
-            return false;
-        }
-
-        int eventType = event.getEventType();
-        if ((mEventTypes & eventType) != eventType) {
-            return false;
-        }
-
-        Set<String> packageNames = mPackageNames;
-        String packageName = (event.getPackageName() != null)
-                ? event.getPackageName().toString() : null;
-
-        return (packageNames.isEmpty() || packageNames.contains(packageName));
-    }
-
-    /**
-     * Notifies an accessibility service client for a scheduled event given the event type.
-     *
-     * @param eventType The type of the event to dispatch.
-     */
-    private void notifyAccessibilityEventInternal(
-            int eventType,
-            AccessibilityEvent event,
-            boolean serviceWantsEvent) {
-        IAccessibilityServiceClient listener;
-
-        synchronized (mLock) {
-            listener = mServiceInterface;
-
-            // If the service died/was disabled while the message for dispatching
-            // the accessibility event was propagating the listener may be null.
-            if (listener == null) {
-                return;
-            }
-
-            // There are two ways we notify for events, throttled AND non-throttled. If we
-            // are not throttling, then messages come with events, which we handle with
-            // minimal fuss.
-            if (event == null) {
-                // We are throttling events, so we'll send the event for this type in
-                // mPendingEvents as long as it it's null. It can only null due to a race
-                // condition:
-                //
-                //   1) A binder thread calls notifyAccessibilityServiceDelayedLocked
-                //      which posts a message for dispatching an event and stores the event
-                //      in mPendingEvents.
-                //   2) The message is pulled from the queue by the handler on the service
-                //      thread and this method is just about to acquire the lock.
-                //   3) Another binder thread acquires the lock in notifyAccessibilityEvent
-                //   4) notifyAccessibilityEvent recycles the event that this method was about
-                //      to process, replaces it with a new one, and posts a second message
-                //   5) This method grabs the new event, processes it, and removes it from
-                //      mPendingEvents
-                //   6) The second message dispatched in (4) arrives, but the event has been
-                //      remvoved in (5).
-                event = mPendingEvents.get(eventType);
-                if (event == null) {
-                    return;
-                }
-                mPendingEvents.remove(eventType);
-            }
-            if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
-                event.setConnectionId(mId);
-            } else {
-                event.setSource((View) null);
-            }
-            event.setSealed(true);
-        }
-
-        try {
-            listener.onAccessibilityEvent(event, serviceWantsEvent);
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
-            }
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
-        } finally {
-            event.recycle();
-        }
-    }
-
-    public void notifyGesture(int gestureId) {
-        mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
-                gestureId, 0).sendToTarget();
-    }
-
-    public void notifyClearAccessibilityNodeInfoCache() {
-        mInvocationHandler.sendEmptyMessage(
-                InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
-    }
-
-    public void notifyMagnificationChangedLocked(@NonNull Region region,
-            float scale, float centerX, float centerY) {
-        mInvocationHandler
-                .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
-    }
-
-    public void notifySoftKeyboardShowModeChangedLocked(int showState) {
-        mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
-    }
-
-    public void notifyAccessibilityButtonClickedLocked() {
-        mInvocationHandler.notifyAccessibilityButtonClickedLocked();
-    }
-
-    public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
-        mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
-    }
-
-    /**
-     * Called by the invocation handler to notify the service that the
-     * state of magnification has changed.
-     */
-    private void notifyMagnificationChangedInternal(@NonNull Region region,
-            float scale, float centerX, float centerY) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.onMagnificationChanged(region, scale, centerX, centerY);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
-            }
-        }
-    }
-
-    /**
-     * Called by the invocation handler to notify the service that the state of the soft
-     * keyboard show mode has changed.
-     */
-    private void notifySoftKeyboardShowModeChangedInternal(int showState) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.onSoftKeyboardShowModeChanged(showState);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
-                        re);
-            }
-        }
-    }
-
-    private void notifyAccessibilityButtonClickedInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.onAccessibilityButtonClicked();
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
-            }
-        }
-    }
-
-    private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
-        // Only notify the service if it's not been notified or the state has changed
-        if (mReceivedAccessibilityButtonCallbackSinceBind
-                && (mLastAccessibilityButtonCallbackState == available)) {
-            return;
-        }
-        mReceivedAccessibilityButtonCallbackSinceBind = true;
-        mLastAccessibilityButtonCallbackState = available;
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.onAccessibilityButtonAvailabilityChanged(available);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error sending accessibility button availability change to " + mService,
-                        re);
-            }
-        }
-    }
-
-    private void notifyGestureInternal(int gestureId) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.onGesture(gestureId);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
-                        + " to " + mService, re);
-            }
-        }
-    }
-
-    private void notifyClearAccessibilityCacheInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                listener.clearAccessibilityCache();
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
-                        + " to be cleared.", re);
-            }
-        }
-    }
-
-    private IAccessibilityServiceClient getServiceInterfaceSafely() {
-        synchronized (mLock) {
-            return mServiceInterface;
-        }
-    }
-
-    private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) {
-        if (accessibilityWindowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
-            return mSecurityPolicy.getActiveWindowId();
-        }
-        return accessibilityWindowId;
-    }
-
-    private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
-        if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
-            return mSecurityPolicy.mActiveWindowId;
-        }
-        if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
-            if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
-                return mSecurityPolicy.mFocusedWindowId;
-            } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
-                return mSecurityPolicy.mAccessibilityFocusedWindowId;
-            }
-        }
-        return windowId;
-    }
-
-    private final class InvocationHandler extends Handler {
-        public static final int MSG_ON_GESTURE = 1;
-        public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
-
-        private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
-        private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
-        private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
-        private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
-
-        private boolean mIsMagnificationCallbackEnabled = false;
-        private boolean mIsSoftKeyboardCallbackEnabled = false;
-
-        public InvocationHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            final int type = message.what;
-            switch (type) {
-                case MSG_ON_GESTURE: {
-                    final int gestureId = message.arg1;
-                    notifyGestureInternal(gestureId);
-                } break;
-
-                case MSG_CLEAR_ACCESSIBILITY_CACHE: {
-                    notifyClearAccessibilityCacheInternal();
-                } break;
-
-                case MSG_ON_MAGNIFICATION_CHANGED: {
-                    final SomeArgs args = (SomeArgs) message.obj;
-                    final Region region = (Region) args.arg1;
-                    final float scale = (float) args.arg2;
-                    final float centerX = (float) args.arg3;
-                    final float centerY = (float) args.arg4;
-                    notifyMagnificationChangedInternal(region, scale, centerX, centerY);
-                    args.recycle();
-                } break;
-
-                case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
-                    final int showState = (int) message.arg1;
-                    notifySoftKeyboardShowModeChangedInternal(showState);
-                } break;
-
-                case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
-                    notifyAccessibilityButtonClickedInternal();
-                } break;
-
-                case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
-                    final boolean available = (message.arg1 != 0);
-                    notifyAccessibilityButtonAvailabilityChangedInternal(available);
-                } break;
-
-                default: {
-                    throw new IllegalArgumentException("Unknown message: " + type);
-                }
-            }
-        }
-
-        public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
-                float centerX, float centerY) {
-            if (!mIsMagnificationCallbackEnabled) {
-                // Callback is disabled, don't bother packing args.
-                return;
-            }
-
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = region;
-            args.arg2 = scale;
-            args.arg3 = centerX;
-            args.arg4 = centerY;
-
-            final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
-            msg.sendToTarget();
-        }
-
-        public void setMagnificationCallbackEnabled(boolean enabled) {
-            mIsMagnificationCallbackEnabled = enabled;
-        }
-
-        public void notifySoftKeyboardShowModeChangedLocked(int showState) {
-            if (!mIsSoftKeyboardCallbackEnabled) {
-                return;
-            }
-
-            final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
-            msg.sendToTarget();
-        }
-
-        public void setSoftKeyboardCallbackEnabled(boolean enabled) {
-            mIsSoftKeyboardCallbackEnabled = enabled;
-        }
-
-        public void notifyAccessibilityButtonClickedLocked() {
-            final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
-            msg.sendToTarget();
-        }
-
-        public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
-            final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
-                    (available ? 1 : 0), 0);
-            msg.sendToTarget();
-        }
-    }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 972a426..50b0be1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -20,6 +20,8 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -62,7 +64,9 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -84,7 +88,6 @@
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityCache;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -114,6 +117,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -131,7 +135,7 @@
  * on the device. Events are dispatched to {@link AccessibilityService}s.
  */
 public class AccessibilityManagerService extends IAccessibilityManager.Stub
-        implements AccessibilityClientConnection.SystemSupport {
+        implements AbstractAccessibilityServiceConnection.SystemSupport {
 
     private static final boolean DEBUG = false;
 
@@ -297,6 +301,14 @@
         return state;
     }
 
+    boolean getBindInstantServiceAllowed(int userId) {
+        return  mSecurityPolicy.getBindInstantServiceAllowed(userId);
+    }
+
+    void setBindInstantServiceAllowed(int userId, boolean allowed) {
+        mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed);
+    }
+
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
             @Override
@@ -455,7 +467,7 @@
     }
 
     @Override
-    public long addClient(IAccessibilityManagerClient client, int userId) {
+    public long addClient(IAccessibilityManagerClient callback, int userId) {
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -467,15 +479,17 @@
             // the system UI or the system we add it to the global state that
             // is shared across users.
             UserState userState = getUserStateLocked(resolvedUserId);
+            Client client = new Client(callback, Binder.getCallingUid(), userState);
             if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
-                mGlobalClients.register(client);
+                mGlobalClients.register(callback, client);
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
                 return IntPair.of(
-                        userState.getClientState(), userState.mLastSentRelevantEventTypes);
+                        userState.getClientState(),
+                        client.mLastSentRelevantEventTypes);
             } else {
-                userState.mUserClients.register(client);
+                userState.mUserClients.register(callback, client);
                 // If this client is not for the current user we do not
                 // return a state since it is not for the foreground user.
                 // We will send the state to the client on a user switch.
@@ -485,7 +499,7 @@
                 }
                 return IntPair.of(
                         (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
-                        userState.mLastSentRelevantEventTypes);
+                        client.mLastSentRelevantEventTypes);
             }
         }
     }
@@ -772,7 +786,6 @@
     public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
-            onUserStateChangedLocked(getCurrentUserStateLocked());
         }
     }
 
@@ -952,7 +965,7 @@
      * Has no effect if no item has accessibility focus, if the item with accessibility
      * focus does not expose the specified action, or if the action fails.
      *
-     * @param actionId The id of the action to perform.
+     * @param action The action to perform.
      *
      * @return {@code true} if the action was performed. {@code false} if it was not.
      */
@@ -1215,14 +1228,18 @@
     private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
         mTempAccessibilityServiceInfoList.clear();
 
+        int flags = PackageManager.GET_SERVICES
+                | PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+        if (userState.mBindInstantServiceAllowed) {
+            flags |= PackageManager.MATCH_INSTANT;
+        }
+
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE),
-                PackageManager.GET_SERVICES
-                        | PackageManager.GET_META_DATA
-                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                mCurrentUserId);
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
 
         for (int i = 0, count = installedServices.size(); i < count; i++) {
             ResolveInfo resolveInfo = installedServices.get(i);
@@ -1330,33 +1347,67 @@
     }
 
     private void updateRelevantEventsLocked(UserState userState) {
-        int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
-        for (AccessibilityServiceConnection service : userState.mBoundServices) {
-            relevantEventTypes |= service.mEventTypes;
-        }
-        relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
-        int finalRelevantEventTypes = relevantEventTypes;
+        mMainHandler.post(() -> {
+            broadcastToClients(userState, ignoreRemoteException(client -> {
+                int relevantEventTypes = computeRelevantEventTypes(userState, client);
 
-        if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
-            userState.mLastSentRelevantEventTypes = finalRelevantEventTypes;
-            mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS,
-                    userState.mUserId, finalRelevantEventTypes);
-            mMainHandler.post(() -> {
-                broadcastToClients(userState, (client) -> {
-                    try {
-                        client.setRelevantEventTypes(finalRelevantEventTypes);
-                    } catch (RemoteException re) {
-                        /* ignore */
-                    }
-                });
-            });
+                if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                    client.mLastSentRelevantEventTypes = relevantEventTypes;
+                    client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                }
+            }));
+        });
+    }
+
+    private int computeRelevantEventTypes(UserState userState, Client client) {
+        int relevantEventTypes = 0;
+
+        int numBoundServices = userState.mBoundServices.size();
+        for (int i = 0; i < numBoundServices; i++) {
+            AccessibilityServiceConnection service =
+                    userState.mBoundServices.get(i);
+            relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+                    ? service.getRelevantEventTypes()
+                    : 0;
         }
+        relevantEventTypes |= isClientInPackageWhitelist(
+                mUiAutomationManager.getServiceInfo(), client)
+                ? mUiAutomationManager.getRelevantEventTypes()
+                : 0;
+        return relevantEventTypes;
+    }
+
+    private static boolean isClientInPackageWhitelist(
+            @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
+        if (serviceInfo == null) return false;
+
+        String[] clientPackages = client.mPackageNames;
+        boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames);
+        if (!result && clientPackages != null) {
+            for (String packageName : clientPackages) {
+                if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+        if (!result) {
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "Dropping events: "
+                        + Arrays.toString(clientPackages) + " -> "
+                        + serviceInfo.getComponentName().flattenToShortString()
+                        + " due to not being in package whitelist "
+                        + Arrays.toString(serviceInfo.packageNames));
+            }
+        }
+
+        return result;
     }
 
     private void broadcastToClients(
-            UserState userState, Consumer<IAccessibilityManagerClient> clientAction) {
-        mGlobalClients.broadcast(clientAction);
-        userState.mUserClients.broadcast(clientAction);
+            UserState userState, Consumer<Client> clientAction) {
+        mGlobalClients.broadcastForEachCookie(clientAction);
+        userState.mUserClients.broadcastForEachCookie(clientAction);
     }
 
     private void unbindAllServicesLocked(UserState userState) {
@@ -2157,6 +2208,7 @@
      * permission to write secure settings, since someone with that permission can enable
      * accessibility services themselves.
      */
+    @Override
     public void performAccessibilityShortcut() {
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                 && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2446,13 +2498,8 @@
                     synchronized (mLock) {
                         userState = getUserStateLocked(userId);
                     }
-                    broadcastToClients(userState, (client) -> {
-                        try {
-                            client.setRelevantEventTypes(relevantEventTypes);
-                        } catch (RemoteException re) {
-                            /* ignore */
-                        }
-                    });
+                    broadcastToClients(userState, ignoreRemoteException(
+                            client -> client.mCallback.setRelevantEventTypes(relevantEventTypes)));
                 } break;
 
                case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
@@ -2501,30 +2548,14 @@
 
         private void sendStateToClients(int clientState,
                 RemoteCallbackList<IAccessibilityManagerClient> clients) {
-            clients.broadcast((client) -> {
-                try {
-                    client.setState(clientState);
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            });
+            clients.broadcast(ignoreRemoteException(
+                    client -> client.setState(clientState)));
         }
 
         private void notifyClientsOfServicesStateChange(
                 RemoteCallbackList<IAccessibilityManagerClient> clients) {
-            try {
-                final int userClientCount = clients.beginBroadcast();
-                for (int i = 0; i < userClientCount; i++) {
-                    IAccessibilityManagerClient client = clients.getBroadcastItem(i);
-                    try {
-                        client.notifyServicesStateChanged();
-                    } catch (RemoteException re) {
-                        /* ignore */
-                    }
-                }
-            } finally {
-                clients.finishBroadcast();
-            }
+            clients.broadcast(ignoreRemoteException(
+                    client -> client.notifyServicesStateChanged()));
         }
     }
 
@@ -2692,6 +2723,14 @@
         }
     }
 
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+                callback, resultReceiver);
+    }
+
     final class WindowsForAccessibilityCallback implements
             WindowManagerInternal.WindowsForAccessibilityCallback {
 
@@ -3059,6 +3098,32 @@
             return uidPackages;
         }
 
+        private boolean getBindInstantServiceAllowed(int userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                    "getBindInstantServiceAllowed");
+            UserState state = mUserStates.get(userId);
+            return (state != null) && state.mBindInstantServiceAllowed;
+        }
+
+        private void setBindInstantServiceAllowed(int userId, boolean allowed) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                    "setBindInstantServiceAllowed");
+            UserState state = mUserStates.get(userId);
+            if (state == null) {
+                if (!allowed) {
+                    return;
+                }
+                state = new UserState(userId);
+                mUserStates.put(userId, state);
+            }
+            if (state.mBindInstantServiceAllowed != allowed) {
+                state.mBindInstantServiceAllowed = allowed;
+                onUserStateChangedLocked(state);
+            }
+        }
+
         public void clearWindowsLocked() {
             List<WindowInfo> windows = Collections.emptyList();
             final int activeWindowId = mActiveWindowId;
@@ -3336,20 +3401,20 @@
         }
 
         public boolean canGetAccessibilityNodeInfoLocked(
-                AccessibilityClientConnection service, int windowId) {
+                AbstractAccessibilityServiceConnection service, int windowId) {
             return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
         }
 
-        public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
+        public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) {
             return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
         }
 
-        public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
+        public boolean canRetrieveWindowContentLocked(AbstractAccessibilityServiceConnection service) {
             return (service.mAccessibilityServiceInfo.getCapabilities()
                     & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
         }
 
-        public boolean canControlMagnification(AccessibilityClientConnection service) {
+        public boolean canControlMagnification(AbstractAccessibilityServiceConnection service) {
             return (service.mAccessibilityServiceInfo.getCapabilities()
                     & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
         }
@@ -3446,7 +3511,7 @@
                 final int windowCount = mWindows.size();
                 for (int i = 0; i < windowCount; i++) {
                     AccessibilityWindowInfo window = mWindows.get(i);
-                    if (window.inPictureInPicture()) {
+                    if (window.isInPictureInPictureMode()) {
                         return window;
                     }
                 }
@@ -3477,13 +3542,26 @@
         }
     }
 
+    /** Represents an {@link AccessibilityManager} */
+    class Client {
+        final IAccessibilityManagerClient mCallback;
+        final String[] mPackageNames;
+        int mLastSentRelevantEventTypes;
+
+        private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+            mCallback = callback;
+            mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+            mLastSentRelevantEventTypes = computeRelevantEventTypes(userState, this);
+        }
+    }
+
     public class UserState {
         public final int mUserId;
 
         // Non-transient state.
 
         public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
-            new RemoteCallbackList<>();
+                new RemoteCallbackList<>();
 
         public final SparseArray<RemoteAccessibilityConnection> mInteractionConnections =
                 new SparseArray<>();
@@ -3495,8 +3573,6 @@
         public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
                 new CopyOnWriteArrayList<>();
 
-        public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
         public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
                 new HashMap<>();
 
@@ -3530,6 +3606,8 @@
         public boolean mIsFilterKeyEventsEnabled;
         public boolean mAccessibilityFocusOnlyInActiveWindow;
 
+        public boolean mBindInstantServiceAllowed;
+
         public UserState(int userId) {
             mUserId = userId;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9cafa1e..96b8979 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -19,9 +19,7 @@
 import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -39,7 +37,6 @@
 import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.ref.WeakReference;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -50,7 +47,7 @@
  * passed to the service it represents as soon it is bound. It also serves as the
  * connection for the service.
  */
-class AccessibilityServiceConnection extends AccessibilityClientConnection {
+class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
     /*
      Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
@@ -94,10 +91,12 @@
         if (userState == null) return;
         final long identity = Binder.clearCallingIdentity();
         try {
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+            if (userState.mBindInstantServiceAllowed) {
+                flags |= Context.BIND_ALLOW_INSTANT;
+            }
             if (mService == null && mContext.bindServiceAsUser(
-                    mIntent, this,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    new UserHandle(userState.mUserId))) {
+                    mIntent, this, flags, new UserHandle(userState.mUserId))) {
                 userState.getBindingServicesLocked().add(mComponentName);
             }
         } finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
new file mode 100644
index 0000000..ff59c24
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * 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.accessibility;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command implementation for the accessibility manager service
+ */
+final class AccessibilityShellCommand extends ShellCommand {
+    final @NonNull AccessibilityManagerService mService;
+
+    AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "get-bind-instant-service-allowed": {
+                return runGetBindInstantServiceAllowed();
+            }
+            case "set-bind-instant-service-allowed": {
+                return runSetBindInstantServiceAllowed();
+            }
+        }
+        return -1;
+    }
+
+    private int runGetBindInstantServiceAllowed() {
+        final Integer userId = parseUserId();
+        if (userId == null) {
+            return -1;
+        }
+        getOutPrintWriter().println(Boolean.toString(
+                mService.getBindInstantServiceAllowed(userId)));
+        return 0;
+    }
+
+    private int runSetBindInstantServiceAllowed() {
+        final Integer userId = parseUserId();
+        if (userId == null) {
+            return -1;
+        }
+        final String allowed = getNextArgRequired();
+        if (allowed == null) {
+            getErrPrintWriter().println("Error: no true/false specified");
+            return -1;
+        }
+        mService.setBindInstantServiceAllowed(userId,
+                Boolean.parseBoolean(allowed));
+        return 0;
+    }
+
+    private Integer parseUserId() {
+        final String option = getNextOption();
+        if (option != null) {
+            if (option.equals("--user")) {
+                return UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Unknown option: " + option);
+                return null;
+            }
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Accessibility service (accessibility) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set-bind-instant-service-allowed [--user <USER_ID>] true|false ");
+        pw.println("    Set whether binding to services provided by instant apps is allowed.");
+        pw.println("  get-bind-instant-service-allowed [--user <USER_ID>]");
+        pw.println("    Get whether binding to services provided by instant apps is allowed.");
+    }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f0571126..ed3b3e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -18,6 +18,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.Nullable;
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -28,6 +29,7 @@
 import android.util.Slog;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.util.DumpUtils;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -45,6 +47,8 @@
 
     private AccessibilityServiceInfo mUiAutomationServiceInfo;
 
+    private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
+
     private int mUiAutomationFlags;
 
     private IBinder mUiAutomationServiceOwner;
@@ -75,7 +79,7 @@
             Context context, AccessibilityServiceInfo accessibilityServiceInfo,
             int id, Handler mainHandler, Object lock,
             AccessibilityManagerService.SecurityPolicy securityPolicy,
-            AccessibilityClientConnection.SystemSupport systemSupport,
+            AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
             WindowManagerInternal windowManagerInternal,
             GlobalActionPerformer globalActionPerfomer, int flags) {
         accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -92,6 +96,7 @@
             return;
         }
 
+        mSystemSupport = systemSupport;
         mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
                 mainHandler, lock, securityPolicy, systemSupport, windowManagerInternal,
                 globalActionPerfomer);
@@ -153,6 +158,17 @@
         return mUiAutomationService.mEventTypes;
     }
 
+    int getRelevantEventTypes() {
+        if (mUiAutomationService == null) return 0;
+        return mUiAutomationService.getRelevantEventTypes();
+    }
+
+    @Nullable
+    AccessibilityServiceInfo getServiceInfo() {
+        if (mUiAutomationService == null) return null;
+        return mUiAutomationService.getServiceInfo();
+    }
+
     void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (mUiAutomationService != null) {
             mUiAutomationService.dump(fd, pw, args);
@@ -169,9 +185,10 @@
             mUiAutomationServiceOwner.unlinkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0);
             mUiAutomationServiceOwner = null;
         }
+        mSystemSupport.onClientChange(false);
     }
 
-    private class UiAutomationService extends AccessibilityClientConnection {
+    private class UiAutomationService extends AbstractAccessibilityServiceConnection {
         private final Handler mMainHandler;
 
         UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
@@ -224,6 +241,17 @@
             return true;
         }
 
+        @Override
+        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+            synchronized (mLock) {
+                pw.append("Ui Automation[eventTypes="
+                        + AccessibilityEvent.eventTypeToString(mEventTypes));
+                pw.append(", notificationTimeout=" + mNotificationTimeout);
+                pw.append("]");
+            }
+        }
+
         // Since this isn't really an accessibility service, several methods are just stubbed here.
         @Override
         public boolean setSoftKeyboardShowMode(int mode) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 690c45b..e1cb154 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -568,13 +568,12 @@
 
         @Override
         public FillEventHistory getFillEventHistory() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getFillEventHistory(uid);
+                    return service.getFillEventHistory(getCallingUid());
                 }
             }
 
@@ -583,13 +582,12 @@
 
         @Override
         public UserData getUserData() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getUserData(uid);
+                    return service.getUserData(getCallingUid());
                 }
             }
 
@@ -598,26 +596,24 @@
 
         @Override
         public void setUserData(UserData userData) throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.setUserData(uid, userData);
+                    service.setUserData(getCallingUid(), userData);
                 }
             }
         }
 
         @Override
         public boolean isFieldClassificationEnabled() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.isFieldClassificationEnabled();
+                    return service.isFieldClassificationEnabled(getCallingUid());
                 }
             }
 
@@ -625,6 +621,20 @@
         }
 
         @Override
+        public ComponentName getAutofillServiceComponentName() throws RemoteException {
+            final int userId = UserHandle.getCallingUserId();
+
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.getServiceComponentName();
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
                 throws RemoteException {
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4bf3c5a..4cdfd62 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -52,6 +51,7 @@
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
@@ -81,6 +81,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -399,7 +400,7 @@
             return;
         }
 
-        session.logContextCommittedLocked();
+        session.logContextCommitted();
 
         final boolean finished = session.showSaveLocked();
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
@@ -712,7 +713,7 @@
     /**
      * Updates the last fill response when an autofill context is committed.
      */
-    void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+    void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
             @Nullable ArrayList<String> selectedDatasets,
             @Nullable ArraySet<String> ignoredDatasets,
             @Nullable ArrayList<AutofillId> changedFieldIds,
@@ -720,38 +721,44 @@
             @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
-            @Nullable ArrayList<Match> detectedMatchesList,
+            @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
             @NonNull String appPackageName) {
+        if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+            AutofillId[] detectedFieldsIds = null;
+            FieldClassification[] detectedFieldClassifications = null;
+            if (detectedFieldIdsList != null) {
+                detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
+                detectedFieldIdsList.toArray(detectedFieldsIds);
+                detectedFieldClassifications =
+                        new FieldClassification[detectedFieldClassificationsList.size()];
+                detectedFieldClassificationsList.toArray(detectedFieldClassifications);
 
-        synchronized (mLock) {
-            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
-                AutofillId[] detectedFieldsIds = null;
-                Match[] detectedMatches = null;
-                if (detectedFieldIdsList != null) {
-                    detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
-                    detectedFieldIdsList.toArray(detectedFieldsIds);
-                    detectedMatches = new Match[detectedMatchesList.size()];
-                    detectedMatchesList.toArray(detectedMatches);
-
-                    final int size = detectedMatchesList.size();
-                    float totalScore = 0;
-                    for (int i = 0; i < size; i++) {
-                        totalScore += detectedMatches[i].getScore();
+                final int numberFields = detectedFieldsIds.length;
+                int totalSize = 0;
+                float totalScore = 0;
+                for (int i = 0; i < numberFields; i++) {
+                    final FieldClassification fc = detectedFieldClassifications[i];
+                    final List<Match> matches = fc.getMatches();
+                    final int size = matches.size();
+                    totalSize += size;
+                    for (int j = 0; j < size; j++) {
+                        totalScore += matches.get(j).getScore();
                     }
-                    final int averageScore = (int) ((totalScore * 100) / size);
-                    mMetricsLogger.write(
-                            Helper.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                    appPackageName, getServicePackageName())
-                            .setCounterValue(size)
-                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore));
-
                 }
-                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
-                        clientState, selectedDatasets, ignoredDatasets,
-                        changedFieldIds, changedDatasetIds,
-                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        detectedFieldsIds, detectedMatches));
+
+                final int averageScore = (int) ((totalScore * 100) / totalSize);
+                mMetricsLogger.write(Helper
+                        .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+                                appPackageName, getServicePackageName())
+                        .setCounterValue(numberFields)
+                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
+                                averageScore));
             }
+            mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+                    clientState, selectedDatasets, ignoredDatasets,
+                    changedFieldIds, changedDatasetIds,
+                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                    detectedFieldsIds, detectedFieldClassifications));
         }
     }
 
@@ -825,7 +832,7 @@
             pw.println(mContext.getString(R.string.config_defaultAutofillService));
         pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
         pw.print(prefix); pw.print("Field classification enabled: ");
-            pw.println(isFieldClassificationEnabled());
+            pw.println(isFieldClassificationEnabledLocked());
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
         pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
@@ -1085,7 +1092,18 @@
         return false;
     }
 
-    boolean isFieldClassificationEnabled() {
+    // Called by AutofillManager, checks UID.
+    boolean isFieldClassificationEnabled(int uid) {
+        synchronized (mLock) {
+            if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+                return false;
+            }
+            return isFieldClassificationEnabledLocked();
+        }
+    }
+
+    // Called by internally, no need to check UID.
+    boolean isFieldClassificationEnabledLocked() {
         return Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 0,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 56f5f64..01f9084 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -65,7 +65,7 @@
 import android.service.autofill.SaveRequest;
 import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
-import android.service.autofill.EditDistanceScorer;
+import android.service.autofill.FieldClassification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -499,6 +499,8 @@
     @Override
     public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
             @NonNull String servicePackageName) {
+        final AutofillId[] fieldClassificationIds;
+
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -509,14 +511,13 @@
                 processNullResponseLocked(requestFlags);
                 return;
             }
-        }
 
-        final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
-        // TODO(b/67867469): remove once feature is finished (or use method from AFM to check)
-        if (fieldClassificationIds != null && !mService.isFieldClassificationEnabled()) {
-            Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
-            processNullResponseLocked(requestFlags);
-            return;
+            fieldClassificationIds = response.getFieldClassificationIds();
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+                Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+                processNullResponseLocked(requestFlags);
+                return;
+            }
         }
 
         mService.setLastResponse(id, response);
@@ -905,7 +906,15 @@
      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
      * when necessary.
      */
-    public void logContextCommittedLocked() {
+    public void logContextCommitted() {
+        mHandlerCaller.getHandler().post(() -> {
+            synchronized (mLock) {
+                logContextCommittedLocked();
+            }
+        });
+    }
+
+    private void logContextCommittedLocked() {
         final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
         if (lastResponse == null) return;
 
@@ -962,15 +971,15 @@
         final UserData userData = mService.getUserData();
 
         final ArrayList<AutofillId> detectedFieldIds;
-        final ArrayList<Match> detectedMatches;
+        final ArrayList<FieldClassification> detectedFieldClassifications;
 
         if (userData != null) {
             final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
             detectedFieldIds = new ArrayList<>(maxFieldsSize);
-            detectedMatches = new ArrayList<>(maxFieldsSize);
+            detectedFieldClassifications = new ArrayList<>(maxFieldsSize);
         } else {
             detectedFieldIds = null;
-            detectedMatches = null;
+            detectedFieldClassifications = null;
         }
 
         for (int i = 0; i < mViewStates.size(); i++) {
@@ -1079,8 +1088,8 @@
 
                     // Sets field classification score for field
                     if (userData!= null) {
-                        setScore(detectedFieldIds, detectedMatches, userData, viewState.id,
-                                currentValue);
+                        setScore(detectedFieldIds, detectedFieldClassifications, userData,
+                                viewState.id, currentValue);
                     }
                 } // else
             } // else
@@ -1094,7 +1103,7 @@
                     + ", changedDatasetIds=" + changedDatasetIds
                     + ", manuallyFilledIds=" + manuallyFilledIds
                     + ", detectedFieldIds=" + detectedFieldIds
-                    + ", detectedMatches=" + detectedMatches
+                    + ", detectedFieldClassifications=" + detectedFieldClassifications
                     );
         }
 
@@ -1114,19 +1123,20 @@
             }
         }
 
-        mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+        mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
                 changedFieldIds, changedDatasetIds,
                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                detectedFieldIds, detectedMatches, mComponentName.getPackageName());
+                detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName());
     }
 
     /**
-     * Adds the top score match to {@code detectedFieldsIds} and {@code detectedMatches} for
+     * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
      */
     private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds,
-            @NonNull ArrayList<Match> detectedMatches, @NonNull UserData userData,
-            @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) {
+            @NonNull ArrayList<FieldClassification> detectedFieldClassifications,
+            @NonNull UserData userData, @NonNull AutofillId fieldId,
+            @NonNull AutofillValue currentValue) {
 
         final String[] userValues = userData.getValues();
         final String[] remoteIds = userData.getRemoteIds();
@@ -1139,23 +1149,26 @@
                     + valuesLength + ", ids.length = " + idsLength);
             return;
         }
-        String remoteId = null;
-        float topScore = 0;
+
+        ArrayList<Match> matches = null;
         for (int i = 0; i < userValues.length; i++) {
+            String remoteId = remoteIds[i];
             final String value = userValues[i];
             final float score = userData.getScorer().getScore(currentValue, value);
-            if (score > topScore) {
-                topScore = score;
-                remoteId = remoteIds[i];
+            if (score > 0) {
+                if (sVerbose) {
+                    Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId);
+                }
+                if (matches == null) {
+                    matches = new ArrayList<>(userValues.length);
+                }
+                matches.add(new Match(remoteId, score));
             }
+            else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId);
         }
-
-        if (remoteId != null && topScore > 0) {
-            if (sVerbose) Slog.v(TAG, "setScores(): top score for #" + fieldId + " is " + topScore);
+        if (matches != null) {
             detectedFieldIds.add(fieldId);
-            detectedMatches.add(new Match(remoteId, topScore));
-        } else if (sVerbose) {
-            Slog.v(TAG, "setScores(): no top score for #" + fieldId + ": " + topScore);
+            detectedFieldClassifications.add(new FieldClassification(matches));
         }
     }
 
@@ -1354,7 +1367,10 @@
                 }
 
                 if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
-                mService.logSaveShown(id, mClientState);
+
+                // Use handler so logContextCommitted() is logged first
+                mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
+
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
@@ -1554,7 +1570,7 @@
      *
      * <p>A new request will be started in 2 scenarios:
      * <ol>
-     *   <li>If the user manually requested autofill after the view was already filled.
+     *   <li>If the user manually requested autofill.
      *   <li>If the view is part of a new partition.
      * </ol>
      *
@@ -1562,14 +1578,10 @@
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
      */
-    private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id,
+    private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
             @NonNull ViewState viewState, int flags) {
-        // First check if this is a manual request after view was autofilled.
-        final int state = viewState.getState();
-        final boolean restart = (state & STATE_AUTOFILLED) != 0
-                && (flags & FLAG_MANUAL_REQUEST) != 0;
-        if (restart) {
-            if (sDebug) Slog.d(TAG, "Re-starting session on view  " + id);
+        if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+            if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
             viewState.setState(STATE_RESTARTED_SESSION);
             requestNewFillResponseLocked(flags);
             return;
@@ -1662,7 +1674,7 @@
                         isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
                 mViewStates.put(id, viewState);
 
-                // TODO(b/67867469): for optimization purposes, should also ignore if change is
+                // TODO(b/70407264): for optimization purposes, should also ignore if change is
                 // detectable, and batch-send them when the session is finished (but that will
                 // require tracking detectable fields on AutofillManager)
                 if (isIgnored) {
@@ -1724,7 +1736,7 @@
                 if (sVerbose && virtualBounds != null) {
                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                 }
-                requestNewFillResponseIfNecessaryLocked(id, viewState, flags);
+                requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
 
                 // Remove the UI if the ViewState has changed.
                 if (mCurrentViewId != viewState.id) {
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index f185443..fbdb183 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -483,6 +483,7 @@
             description.currentDestinationString = currentDestinationString;
             description.dataManagementIntent = dataManagementIntent;
             description.dataManagementLabel = dataManagementLabel;
+            Slog.d(TAG, "Transport " + name + " updated its attributes");
         }
     }
 
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 25c3fe4..5504695 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -35,6 +35,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethod;
 import com.android.internal.view.IInputMethodClient;
@@ -93,7 +94,6 @@
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Environment;
@@ -270,8 +270,6 @@
     private final AppOpsManager mAppOpsManager;
     private final UserManager mUserManager;
 
-    final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
-
     // All known input methods.  mMethodMap also serves as the global
     // lock for this class.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
@@ -1778,6 +1776,7 @@
         return flags;
     }
 
+    @NonNull
     InputBindResult attachNewInputLocked(
             /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
         if (!mBoundToMethod) {
@@ -1801,11 +1800,12 @@
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             showCurrentInputLocked(getAppShowFlags(), null);
         }
-        return new InputBindResult(session.session,
-                (session.channel != null ? session.channel.dup() : null),
+        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
+                session.session, (session.channel != null ? session.channel.dup() : null),
                 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
     }
 
+    @NonNull
     InputBindResult startInputLocked(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
             IInputMethodClient client, IInputContext inputContext,
@@ -1813,7 +1813,7 @@
             @Nullable EditorInfo attribute, int controlFlags) {
         // If no method is currently selected, do nothing.
         if (mCurMethodId == null) {
-            return mNoBinding;
+            return InputBindResult.NO_IME;
         }
 
         ClientState cs = mClients.get(client.asBinder());
@@ -1825,7 +1825,7 @@
         if (attribute == null) {
             Slog.w(TAG, "Ignoring startInput with null EditorInfo."
                     + " uid=" + cs.uid + " pid=" + cs.pid);
-            return null;
+            return InputBindResult.NULL_EDITOR_INFO;
         }
 
         try {
@@ -1839,7 +1839,7 @@
                     Slog.w(TAG, "Starting input on non-focused client " + cs.client
                             + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
                 }
-                return null;
+                return InputBindResult.NOT_IME_TARGET_WINDOW;
             }
         } catch (RemoteException e) {
         }
@@ -1848,20 +1848,21 @@
                 controlFlags, startInputReason);
     }
 
+    @NonNull
     InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
             @NonNull EditorInfo attribute, int controlFlags,
             /* @InputMethodClient.StartInputReason */ final int startInputReason) {
         // If no method is currently selected, do nothing.
         if (mCurMethodId == null) {
-            return mNoBinding;
+            return InputBindResult.NO_IME;
         }
 
         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
                 attribute.packageName)) {
             Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
                     + " uid=" + cs.uid + " package=" + attribute.packageName);
-            return mNoBinding;
+            return InputBindResult.INVALID_PACKAGE_NAME;
         }
 
         if (mCurClient != cs) {
@@ -1901,7 +1902,9 @@
                     // Return to client, and we will get back with it when
                     // we have had a session made for it.
                     requestClientSessionLocked(cs);
-                    return new InputBindResult(null, null, mCurId, mCurSeq,
+                    return new InputBindResult(
+                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
+                            null, null, mCurId, mCurSeq,
                             mCurUserActionNotificationSequenceNumber);
                 } else if (SystemClock.uptimeMillis()
                         < (mLastBindTime+TIME_TO_RECONNECT)) {
@@ -1912,7 +1915,9 @@
                     // we can report back.  If it has been too long, we want
                     // to fall through so we can try a disconnect/reconnect
                     // to see if we can get back in touch with the service.
-                    return new InputBindResult(null, null, mCurId, mCurSeq,
+                    return new InputBindResult(
+                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
+                            null, null, mCurId, mCurSeq,
                             mCurUserActionNotificationSequenceNumber);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
@@ -1926,13 +1931,15 @@
 
     InputBindResult startInputInnerLocked() {
         if (mCurMethodId == null) {
-            return mNoBinding;
+            return InputBindResult.NO_IME;
         }
 
         if (!mSystemReady) {
             // If the system is not yet ready, we shouldn't be running third
             // party code.
-            return new InputBindResult(null, null, mCurMethodId, mCurSeq,
+            return new InputBindResult(
+                    InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
+                    null, null, mCurMethodId, mCurSeq,
                     mCurUserActionNotificationSequenceNumber);
         }
 
@@ -1959,23 +1966,24 @@
                 mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
             } catch (RemoteException e) {
             }
-            return new InputBindResult(null, null, mCurId, mCurSeq,
+            return new InputBindResult(
+                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
+                    null, null, mCurId, mCurSeq,
                     mCurUserActionNotificationSequenceNumber);
-        } else {
-            mCurIntent = null;
-            Slog.w(TAG, "Failure connecting to input method service: "
-                    + mCurIntent);
         }
-        return null;
+        mCurIntent = null;
+        Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
+        return InputBindResult.IME_NOT_CONNECTED;
     }
 
+    @NonNull
     private InputBindResult startInput(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
             IInputMethodClient client, IInputContext inputContext,
             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
             @Nullable EditorInfo attribute, int controlFlags) {
         if (!calledFromValidUser()) {
-            return null;
+            return InputBindResult.INVALID_USER;
         }
         synchronized (mMethodMap) {
             if (DEBUG) {
@@ -2719,6 +2727,7 @@
         return res;
     }
 
+    @NonNull
     @Override
     public InputBindResult startInputOrWindowGainedFocus(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
@@ -2726,16 +2735,27 @@
             int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
             int unverifiedTargetSdkVersion) {
+        final InputBindResult result;
         if (windowToken != null) {
-            return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
+            result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
                     softInputMode, windowFlags, attribute, inputContext, missingMethods,
                     unverifiedTargetSdkVersion);
         } else {
-            return startInput(startInputReason, client, inputContext, missingMethods, attribute,
+            result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
                     controlFlags);
         }
+        if (result == null) {
+            // This must never happen, but just in case.
+            Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+                    + InputMethodClient.getStartInputReason(startInputReason)
+                    + " windowFlags=#" + Integer.toHexString(windowFlags)
+                    + " editorInfo=" + attribute);
+            return InputBindResult.NULL;
+        }
+        return result;
     }
 
+    @NonNull
     private InputBindResult windowGainedFocus(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
             IInputMethodClient client, IBinder windowToken, int controlFlags,
@@ -2778,7 +2798,7 @@
                             Slog.w(TAG, "Focus gain on non-focused client " + cs.client
                                     + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
                         }
-                        return null;
+                        return InputBindResult.NOT_IME_TARGET_WINDOW;
                     }
                 } catch (RemoteException e) {
                 }
@@ -2788,7 +2808,7 @@
                     Slog.w(TAG, "If you want to interect with IME, you need "
                             + "android.permission.INTERACT_ACROSS_USERS_FULL");
                     hideCurrentInputLocked(0, null);
-                    return null;
+                    return InputBindResult.INVALID_USER;
                 }
 
                 if (mCurFocusedWindow == windowToken) {
@@ -2800,7 +2820,9 @@
                         return startInputUncheckedLocked(cs, inputContext, missingMethods,
                                 attribute, controlFlags, startInputReason);
                     }
-                    return null;
+                    return new InputBindResult(
+                            InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
+                            null, null, null, -1, -1);
                 }
                 mCurFocusedWindow = windowToken;
                 mCurFocusedWindowSoftInputMode = softInputMode;
@@ -4663,23 +4685,31 @@
         @ShellCommandResult
         @Override
         public int onCommand(@Nullable String cmd) {
-            if (cmd == null) {
-                return handleDefaultCommands(cmd);
+            // For existing "adb shell ime <command>".
+            if ("ime".equals(cmd)) {
+                final String imeCommand = getNextArg();
+                if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) {
+                    onImeCommandHelp();
+                    return ShellCommandResult.SUCCESS;
+                }
+                switch (imeCommand) {
+                    case "list":
+                        return mService.handleShellCommandListInputMethods(this);
+                    case "enable":
+                        return mService.handleShellCommandEnableDisableInputMethod(this, true);
+                    case "disable":
+                        return mService.handleShellCommandEnableDisableInputMethod(this, false);
+                    case "set":
+                        return mService.handleShellCommandSetInputMethod(this);
+                    case "reset":
+                        return mService.handleShellCommandResetInputMethod(this);
+                    default:
+                        getOutPrintWriter().println("Unknown command: " + imeCommand);
+                        return ShellCommandResult.FAILURE;
+                }
             }
-            switch (cmd) {
-                case "list":
-                    return mService.handleShellCommandListInputMethods(this);
-                case "enable":
-                    return mService.handleShellCommandEnableDisableInputMethod(this, true);
-                case "disable":
-                    return mService.handleShellCommandEnableDisableInputMethod(this, false);
-                case "set":
-                    return mService.handleShellCommandSetInputMethod(this);
-                case "reset-ime":
-                    return mService.handleShellCommandResetInputMethod(this);
-                default:
-                    return handleDefaultCommands(cmd);
-            }
+
+            return handleDefaultCommands(cmd);
         }
 
         @BinderThread
@@ -4691,19 +4721,48 @@
                 pw.println("    Prints this help text.");
                 pw.println("  dump [options]");
                 pw.println("    Synonym of dumpsys.");
-                pw.println("  list [-a] [-s]");
-                pw.println("    prints all enabled input methods.");
-                pw.println("     -a: see all input methods");
-                pw.println("     -s: only a single summary line of each");
-                pw.println("  enable <ID>");
-                pw.println("    allows the given input method ID to be used.");
-                pw.println("  disable <ID>");
-                pw.println("    disallows the given input method ID to be used.");
-                pw.println("  set <ID>");
-                pw.println("    switches to the given input method ID.");
-                pw.println("  reset-ime");
-                pw.println("    reset currently selected/enabled IMEs to the default ones as if");
-                pw.println("    the device is initially booted with the current locale.");
+                pw.println("  ime <command> [options]");
+                pw.println("    Manipulate IMEs.  Run \"ime help\" for details.");
+            }
+        }
+
+        private void onImeCommandHelp() {
+            try (IndentingPrintWriter pw =
+                         new IndentingPrintWriter(getOutPrintWriter(), "  ", 100)) {
+                pw.println("ime <command>:");
+                pw.increaseIndent();
+
+                pw.println("list [-a] [-s]");
+                pw.increaseIndent();
+                pw.println("prints all enabled input methods.");
+                pw.increaseIndent();
+                pw.println("-a: see all input methods");
+                pw.println("-s: only a single summary line of each");
+                pw.decreaseIndent();
+                pw.decreaseIndent();
+
+                pw.println("enable <ID>");
+                pw.increaseIndent();
+                pw.println("allows the given input method ID to be used.");
+                pw.decreaseIndent();
+
+                pw.println("disable <ID>");
+                pw.increaseIndent();
+                pw.println("disallows the given input method ID to be used.");
+                pw.decreaseIndent();
+
+                pw.println("set <ID>");
+                pw.increaseIndent();
+                pw.println("switches to the given input method ID.");
+                pw.decreaseIndent();
+
+                pw.println("reset");
+                pw.increaseIndent();
+                pw.println("reset currently selected/enabled IMEs to the default ones as if "
+                        + "the device is initially booted with the current locale.");
+                pw.decreaseIndent();
+
+                pw.decreaseIndent();
             }
         }
     }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index e9eb3b3..d3ab125 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -34,6 +34,7 @@
 import android.net.IpSecTransformResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.NetworkUtils;
+import android.net.TrafficStats;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
@@ -120,6 +121,7 @@
     }
 
     private final IpSecServiceConfiguration mSrvConfig;
+    final UidFdTagger mUidFdTagger;
 
     /**
      * Interface for user-reference and kernel-resource cleanup.
@@ -762,8 +764,23 @@
     /** @hide */
     @VisibleForTesting
     public IpSecService(Context context, IpSecServiceConfiguration config) {
+        this(context, config, (fd, uid) ->  {
+            try{
+                TrafficStats.setThreadStatsUid(uid);
+                TrafficStats.tagFileDescriptor(fd);
+            } finally {
+                TrafficStats.clearThreadStatsUid();
+            }
+        });
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public IpSecService(
+            Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
         mContext = context;
         mSrvConfig = config;
+        mUidFdTagger = uidFdTagger;
     }
 
     public void systemReady() {
@@ -925,6 +942,26 @@
     }
 
     /**
+     * Functional interface to do traffic tagging of given sockets to UIDs.
+     *
+     * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
+     * sockets are billed to the UID that the UDP encap socket was created on behalf of.
+     *
+     * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
+     * methods that cannot be easily mocked/tested.
+     */
+    @VisibleForTesting
+    public interface UidFdTagger {
+        /**
+         * Sets socket tag to assign all traffic to the provided UID.
+         *
+         * <p>Since the socket is created on behalf of an unprivileged application, all traffic
+         * should be accounted to the UID of the unprivileged application.
+         */
+        public void tag(FileDescriptor fd, int uid) throws IOException;
+    }
+
+    /**
      * Open a socket via the system server and bind it to the specified port (random if port=0).
      * This will return a PFD to the user that represent a bound UDP socket. The system server will
      * cache the socket and a record of its owner so that it can and must be freed when no longer
@@ -939,7 +976,8 @@
         }
         checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
 
-        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
         int resourceId = mNextResourceId.getAndIncrement();
         FileDescriptor sockFd = null;
         try {
@@ -948,13 +986,8 @@
             }
 
             sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+            mUidFdTagger.tag(sockFd, callingUid);
 
-            if (port != 0) {
-                Log.v(TAG, "Binding to port " + port);
-                Os.bind(sockFd, INADDR_ANY, port);
-            } else {
-                port = bindToRandomPort(sockFd);
-            }
             // This code is common to both the unspecified and specified port cases
             Os.setsockoptInt(
                     sockFd,
@@ -962,6 +995,14 @@
                     OsConstants.UDP_ENCAP,
                     OsConstants.UDP_ENCAP_ESPINUDP);
 
+            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+            if (port != 0) {
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+            } else {
+                port = bindToRandomPort(sockFd);
+            }
+
             userRecord.mEncapSocketRecords.put(
                     resourceId,
                     new RefcountedResource<EncapSocketRecord>(
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0a86281..57c992f 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1144,7 +1144,7 @@
     }
 
     /**
-     * Returns the system information of the GNSS hardware.
+     * Returns the year of the GNSS hardware.
      */
     @Override
     public int getGnssYearOfHardware() {
@@ -1155,6 +1155,19 @@
         }
     }
 
+
+    /**
+     * Returns the model name of the GNSS hardware.
+     */
+    @Override
+    public String getGnssHardwareModelName() {
+        if (mGnssSystemInfoProvider != null) {
+            return mGnssSystemInfoProvider.getGnssHardwareModelName();
+        } else {
+            return LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN;
+        }
+    }
+
     /**
      * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly
      * (try to) access GNSS information at this layer.
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7f0b508..6a0d3ff 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -31,8 +31,11 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.ScreenObserver;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.KeyguardManager;
 import android.app.usage.StorageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -160,7 +163,8 @@
  * watch for and manage dynamically added storage, such as SD cards and USB mass
  * storage. Also decides how storage should be presented to users on the device.
  */
-class StorageManagerService extends IStorageManager.Stub implements Watchdog.Monitor {
+class StorageManagerService extends IStorageManager.Stub
+        implements Watchdog.Monitor, ScreenObserver {
 
     // Static direct instance pointer for the tightly-coupled idle service to use
     static StorageManagerService sSelf = null;
@@ -402,6 +406,7 @@
     private volatile boolean mSystemReady = false;
     private volatile boolean mBootCompleted = false;
     private volatile boolean mDaemonConnected = false;
+    private volatile boolean mSecureKeyguardShowing = true;
 
     private PackageManagerService mPms;
 
@@ -827,6 +832,7 @@
                     mVold.onUserStarted(userId);
                     mStoraged.onUserStarted(userId);
                 }
+                mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
             }
@@ -878,6 +884,24 @@
         }
     }
 
+    @Override
+    public void onAwakeStateChanged(boolean isAwake) {
+        // Ignored
+    }
+
+    @Override
+    public void onKeyguardStateChanged(boolean isShowing) {
+        // Push down current secure keyguard status so that we ignore malicious
+        // USB devices while locked.
+        mSecureKeyguardShowing = isShowing
+                && mContext.getSystemService(KeyguardManager.class).isDeviceSecure();
+        try {
+            mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+        }
+    }
+
     void runIdleMaintenance(Runnable callback) {
         mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
     }
@@ -1414,6 +1438,9 @@
     }
 
     private void systemReady() {
+        LocalServices.getService(ActivityManagerInternal.class)
+                .registerScreenObserver(this);
+
         mSystemReady = true;
         mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
     }
@@ -2691,15 +2718,14 @@
             final boolean primary = true;
             final boolean removable = primaryPhysical;
             final boolean emulated = !primaryPhysical;
-            final long mtpReserveSize = 0L;
             final boolean allowMassStorage = false;
             final long maxFileSize = 0L;
             final UserHandle owner = new UserHandle(userId);
             final String uuid = null;
             final String state = Environment.MEDIA_REMOVED;
 
-            res.add(0, new StorageVolume(id, StorageVolume.STORAGE_ID_INVALID, path,
-                    description, primary, removable, emulated, mtpReserveSize,
+            res.add(0, new StorageVolume(id, path,
+                    description, primary, removable, emulated,
                     allowMassStorage, maxFileSize, owner, uuid, state));
         }
 
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0ffc779..31aea63 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1406,7 +1406,7 @@
             mLocalUnlockedUsers.put(userId, true);
         }
         if (userId < 1) return;
-        syncSharedAccounts(userId);
+        mHandler.post(() -> syncSharedAccounts(userId));
     }
 
     private void syncSharedAccounts(int userId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3cd2f6a..2f7d4c1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -348,7 +348,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true, callerFg, false);
+                    callingPid, callingUid, userId, true, callerFg, false, false);
         if (res == null) {
             return null;
         }
@@ -597,7 +597,7 @@
 
         // If this service is active, make sure it is stopped.
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false);
+                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
         if (r != null) {
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
@@ -658,7 +658,7 @@
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
-                UserHandle.getCallingUserId(), false, false, false);
+                UserHandle.getCallingUserId(), false, false, false, false);
 
         IBinder ret = null;
         if (r != null) {
@@ -1282,12 +1282,19 @@
                     + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
         }
 
+        if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) {
+            throw new SecurityException(
+                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                            + ") set BIND_ALLOW_INSTANT when binding service " + service);
+        }
+
         final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
+        final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal);
+                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
         }
@@ -1657,7 +1664,8 @@
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
             String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
-            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) {
+            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+            boolean allowInstant) {
         ServiceRecord r = null;
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
@@ -1685,11 +1693,14 @@
         }
         if (r == null) {
             try {
+                int flags = ActivityManagerService.STOCK_PM_FLAGS
+                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+                if (allowInstant) {
+                    flags |= PackageManager.MATCH_INSTANT;
+                }
                 // TODO: come back and remove this assumption to triage all services
                 ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
-                        resolvedType, ActivityManagerService.STOCK_PM_FLAGS
-                                | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                        userId, callingUid);
+                        resolvedType, flags, userId, callingUid);
                 ServiceInfo sInfo =
                     rInfo != null ? rInfo.serviceInfo : null;
                 if (sInfo == null) {
@@ -2640,7 +2651,7 @@
                 try {
                     bumpServiceExecutingLocked(s, false, "unbind");
                     if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
-                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
                         // then update it in the LRU list here because this may be causing
                         // it to go down there and we want it to start out near the top.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index b52db87..b3a596c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -66,6 +66,7 @@
     static final String KEY_BG_START_TIMEOUT = "service_bg_start_timeout";
     static final String KEY_BOUND_SERVICE_CRASH_RESTART_DURATION = "service_crash_restart_duration";
     static final String KEY_BOUND_SERVICE_CRASH_MAX_RETRY = "service_crash_max_retry";
+    static final String KEY_PROCESS_START_ASYNC = "process_start_async";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -93,6 +94,7 @@
     private static final long DEFAULT_BG_START_TIMEOUT = 15*1000;
     private static final long DEFAULT_BOUND_SERVICE_CRASH_RESTART_DURATION = 30*60_000;
     private static final int DEFAULT_BOUND_SERVICE_CRASH_MAX_RETRY = 16;
+    private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
 
 
     // Maximum number of cached processes we will allow.
@@ -202,6 +204,9 @@
     // Maximum number of retries for bound foreground services that crash soon after start
     public long BOUND_SERVICE_MAX_CRASH_RETRY = DEFAULT_BOUND_SERVICE_CRASH_MAX_RETRY;
 
+    // Indicates if the processes need to be started asynchronously.
+    public boolean FLAG_PROCESS_START_ASYNC = DEFAULT_PROCESS_START_ASYNC;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -325,6 +330,8 @@
                 DEFAULT_BOUND_SERVICE_CRASH_RESTART_DURATION);
             BOUND_SERVICE_MAX_CRASH_RETRY = mParser.getInt(KEY_BOUND_SERVICE_CRASH_MAX_RETRY,
                 DEFAULT_BOUND_SERVICE_CRASH_MAX_RETRY);
+            FLAG_PROCESS_START_ASYNC = mParser.getBoolean(KEY_PROCESS_START_ASYNC,
+                    DEFAULT_PROCESS_START_ASYNC);
 
             updateMaxCachedProcesses();
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8530313..d92b3b8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -34,6 +34,7 @@
 import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
 import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
 import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -210,6 +211,7 @@
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.ScreenObserver;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
@@ -352,6 +354,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongSparseArray;
 import android.util.StatsLog;
 import android.util.TimingsTraceLog;
 import android.util.DebugUtils;
@@ -388,6 +391,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.BinderInternal;
+import com.android.internal.os.ByteTransferPipe;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
@@ -1595,6 +1599,20 @@
     @VisibleForTesting
     long mProcStateSeqCounter = 0;
 
+    /**
+     * A global counter for generating sequence numbers to uniquely identify pending process starts.
+     */
+    @GuardedBy("this")
+    private long mProcStartSeqCounter = 0;
+
+    /**
+     * Contains {@link ProcessRecord} objects for pending process starts.
+     *
+     * Mapping: {@link #mProcStartSeqCounter} -> {@link ProcessRecord}
+     */
+    @GuardedBy("this")
+    private final LongSparseArray<ProcessRecord> mPendingStarts = new LongSparseArray<>();
+
     private final Injector mInjector;
 
     static final class ProcessChangeItem {
@@ -1627,6 +1645,8 @@
         }
     }
 
+    final List<ScreenObserver> mScreenObservers = new ArrayList<>();
+
     final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
     ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
 
@@ -1749,13 +1769,13 @@
     static final int LOG_STACK_STATE = 60;
     static final int VR_MODE_CHANGE_MSG = 61;
     static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
-    static final int NOTIFY_VR_SLEEPING_MSG = 65;
+    static final int DISPATCH_SCREEN_AWAKE_MSG = 64;
+    static final int DISPATCH_SCREEN_KEYGUARD_MSG = 65;
     static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
     static final int DISPATCH_PENDING_INTENT_CANCEL_MSG = 67;
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
-    static final int NOTIFY_VR_KEYGUARD_MSG = 74;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1782,6 +1802,8 @@
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
     final Handler mUiHandler;
+    final ServiceThread mProcStartHandlerThread;
+    final Handler mProcStartHandler;
 
     final ActivityManagerConstants mConstants;
 
@@ -2391,11 +2413,17 @@
                     }
                 }
             } break;
-            case NOTIFY_VR_SLEEPING_MSG: {
-                notifyVrManagerOfSleepState(msg.arg1 != 0);
+            case DISPATCH_SCREEN_AWAKE_MSG: {
+                final boolean isAwake = msg.arg1 != 0;
+                for (int i = mScreenObservers.size() - 1; i >= 0; i--) {
+                    mScreenObservers.get(i).onAwakeStateChanged(isAwake);
+                }
             } break;
-            case NOTIFY_VR_KEYGUARD_MSG: {
-                notifyVrManagerOfKeyguardState(msg.arg1 != 0);
+            case DISPATCH_SCREEN_KEYGUARD_MSG: {
+                final boolean isShowing = msg.arg1 != 0;
+                for (int i = mScreenObservers.size() - 1; i >= 0; i--) {
+                    mScreenObservers.get(i).onKeyguardStateChanged(isShowing);
+                }
             } break;
             case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
                 synchronized (ActivityManagerService.this) {
@@ -2708,6 +2736,8 @@
         mVrController = null;
         mLockTaskController = null;
         mLifecycleManager = null;
+        mProcStartHandlerThread = null;
+        mProcStartHandler = null;
     }
 
     // Note: This method is invoked on the main thread but may need to attach various
@@ -2732,6 +2762,11 @@
         mHandler = new MainHandler(mHandlerThread.getLooper());
         mUiHandler = mInjector.getUiHandler(this);
 
+        mProcStartHandlerThread = new ServiceThread(TAG + ":procStart",
+                THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
+        mProcStartHandlerThread.start();
+        mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());
+
         mConstants = new ActivityManagerConstants(this, mHandler);
 
         /* static; one-time init here */
@@ -3294,32 +3329,6 @@
                 mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
     }
 
-    private void sendNotifyVrManagerOfSleepState(boolean isSleeping) {
-        mHandler.sendMessage(
-                mHandler.obtainMessage(NOTIFY_VR_SLEEPING_MSG, isSleeping ? 1 : 0, 0));
-    }
-
-    private void notifyVrManagerOfSleepState(boolean isSleeping) {
-        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
-        if (vrService == null) {
-            return;
-        }
-        vrService.onSleepStateChanged(isSleeping);
-    }
-
-    private void sendNotifyVrManagerOfKeyguardState(boolean isShowing) {
-        mHandler.sendMessage(
-                mHandler.obtainMessage(NOTIFY_VR_KEYGUARD_MSG, isShowing ? 1 : 0, 0));
-    }
-
-    private void notifyVrManagerOfKeyguardState(boolean isShowing) {
-        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
-        if (vrService == null) {
-            return;
-        }
-        vrService.onKeyguardStateChanged(isShowing);
-    }
-
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
         msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -3393,8 +3402,12 @@
         if (lrui >= 0) {
             if (!app.killed) {
                 Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
-                killProcessQuiet(app.pid);
-                killProcessGroup(app.uid, app.pid);
+                if (app.pid > 0) {
+                    killProcessQuiet(app.pid);
+                    killProcessGroup(app.uid, app.pid);
+                } else {
+                    app.pendingStart = false;
+                }
             }
             if (lrui <= mLruProcessActivityStart) {
                 mLruProcessActivityStart--;
@@ -3655,7 +3668,7 @@
                 || transit == TRANSIT_TASK_TO_FRONT;
     }
 
-    int startIsolatedProcess(String entryPoint, String[] entryPointArgs,
+    boolean startIsolatedProcess(String entryPoint, String[] entryPointArgs,
             String processName, String abiOverride, int uid, Runnable crashHandler) {
         synchronized(this) {
             ApplicationInfo info = new ApplicationInfo();
@@ -3677,7 +3690,7 @@
                     null /* hostingName */, true /* allowWhileBooting */, true /* isolated */,
                     uid, true /* keepIfLarge */, abiOverride, entryPoint, entryPointArgs,
                     crashHandler);
-            return proc != null ? proc.pid : 0;
+            return proc != null;
         }
     }
 
@@ -3798,9 +3811,9 @@
         }
 
         checkTime(startTime, "startProcess: stepping in to startProcess");
-        startProcessLocked(app, hostingType, hostingNameStr, abiOverride);
+        final boolean success = startProcessLocked(app, hostingType, hostingNameStr, abiOverride);
         checkTime(startTime, "startProcess: done starting proc!");
-        return (app.pid != 0) ? app : null;
+        return success ? app : null;
     }
 
     boolean isAllowedWhileBooting(ApplicationInfo ai) {
@@ -3812,8 +3825,14 @@
         startProcessLocked(app, hostingType, hostingNameStr, null /* abiOverride */);
     }
 
-    private final void startProcessLocked(ProcessRecord app, String hostingType,
+    /**
+     * @return {@code true} if process start is successful, false otherwise.
+     */
+    private final boolean startProcessLocked(ProcessRecord app, String hostingType,
             String hostingNameStr, String abiOverride) {
+        if (app.pendingStart) {
+            return true;
+        }
         long startTime = SystemClock.elapsedRealtime();
         if (app.pid > 0 && app.pid != MY_PID) {
             checkTime(startTime, "startProcess: removing from pids map");
@@ -3969,90 +3988,10 @@
             // Start the process.  It will either succeed and return a result containing
             // the PID of the new process, or else throw a RuntimeException.
             final String entryPoint = "android.app.ActivityThread";
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
-                    app.processName);
-            checkTime(startTime, "startProcess: asking zygote to start proc");
-            ProcessStartResult startResult;
-            if (hostingType.equals("webview_service")) {
-                startResult = startWebView(entryPoint,
-                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
-                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, null, null);
-            } else {
-                startResult = Process.start(entryPoint,
-                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
-                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, invokeWith, null);
-            }
-            checkTime(startTime, "startProcess: returned from zygote!");
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-            mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
-            checkTime(startTime, "startProcess: done updating battery stats");
-
-            EventLog.writeEvent(EventLogTags.AM_PROC_START,
-                    UserHandle.getUserId(uid), startResult.pid, uid,
-                    app.processName, hostingType,
-                    hostingNameStr != null ? hostingNameStr : "");
-
-            try {
-                AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
-                        seInfo, app.info.sourceDir, startResult.pid);
-            } catch (RemoteException ex) {
-                // Ignore
-            }
-
-            if (app.persistent) {
-                Watchdog.getInstance().processStarted(app.processName, startResult.pid);
-            }
-
-            checkTime(startTime, "startProcess: building log message");
-            StringBuilder buf = mStringBuilder;
-            buf.setLength(0);
-            buf.append("Start proc ");
-            buf.append(startResult.pid);
-            buf.append(':');
-            buf.append(app.processName);
-            buf.append('/');
-            UserHandle.formatUid(buf, uid);
-            if (app.isolatedEntryPoint != null) {
-                buf.append(" [");
-                buf.append(app.isolatedEntryPoint);
-                buf.append("]");
-            }
-            buf.append(" for ");
-            buf.append(hostingType);
-            if (hostingNameStr != null) {
-                buf.append(" ");
-                buf.append(hostingNameStr);
-            }
-            Slog.i(TAG, buf.toString());
-            app.setPid(startResult.pid);
-            app.usingWrapper = startResult.usingWrapper;
-            app.removed = false;
-            app.killed = false;
-            app.killedByAm = false;
-            checkTime(startTime, "startProcess: starting to update pids map");
-            ProcessRecord oldApp;
-            synchronized (mPidsSelfLocked) {
-                oldApp = mPidsSelfLocked.get(startResult.pid);
-            }
-            // If there is already an app occupying that pid that hasn't been cleaned up
-            if (oldApp != null && !app.isolated) {
-                // Clean up anything relating to this pid first
-                Slog.w(TAG, "Reusing pid " + startResult.pid
-                        + " while app is still mapped to it");
-                cleanUpApplicationRecordLocked(oldApp, false, false, -1,
-                        true /*replacingPid*/);
-            }
-            synchronized (mPidsSelfLocked) {
-                this.mPidsSelfLocked.put(startResult.pid, app);
-                Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
-                msg.obj = app;
-                mHandler.sendMessageDelayed(msg, startResult.usingWrapper
-                        ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
-            }
-            checkTime(startTime, "startProcess: done updating pids map");
+            return startProcessLocked(hostingType, hostingNameStr, entryPoint, app, uid, gids,
+                    runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith,
+                    startTime);
         } catch (RuntimeException e) {
             Slog.e(TAG, "Failure starting process " + app.processName, e);
 
@@ -4064,9 +4003,222 @@
             // it doesn't hurt to use it again.)
             forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), false,
                     false, true, false, false, UserHandle.getUserId(app.userId), "start failure");
+            return false;
         }
     }
 
+    @GuardedBy("this")
+    private boolean startProcessLocked(String hostingType, String hostingNameStr, String entryPoint,
+            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
+            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
+            long startTime) {
+        app.pendingStart = true;
+        app.killedByAm = false;
+        app.removed = false;
+        app.killed = false;
+        final long startSeq = app.startSeq = ++mProcStartSeqCounter;
+        app.setStartParams(uid, hostingType, hostingNameStr, seInfo, startTime);
+        if (mConstants.FLAG_PROCESS_START_ASYNC) {
+            if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES,
+                    "Posting procStart msg for " + app.toShortString());
+            mProcStartHandler.post(() -> {
+                try {
+                    synchronized (ActivityManagerService.this) {
+                        final String reason = isProcStartValidLocked(app, startSeq);
+                        if (reason != null) {
+                            Slog.w(TAG_PROCESSES, app + " not valid anymore,"
+                                    + " don't start process, " + reason);
+                            app.pendingStart = false;
+                            return;
+                        }
+                        app.usingWrapper = invokeWith != null
+                                || SystemProperties.get("wrap." + app.processName) != null;
+                        mPendingStarts.put(startSeq, app);
+                    }
+                    final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint,
+                            app, app.startUid, gids, runtimeFlags, mountExternal, app.seInfo,
+                            requiredAbi, instructionSet, invokeWith, app.startTime);
+                    synchronized (ActivityManagerService.this) {
+                        handleProcessStartedLocked(app, startResult, startSeq);
+                    }
+                } catch (RuntimeException e) {
+                    synchronized (ActivityManagerService.this) {
+                        Slog.e(TAG, "Failure starting process " + app.processName, e);
+                        mPendingStarts.remove(startSeq);
+                        app.pendingStart = false;
+                        forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
+                                false, false, true, false, false,
+                                UserHandle.getUserId(app.userId), "start failure");
+                    }
+                }
+            });
+            return true;
+        } else {
+            try {
+                final ProcessStartResult startResult = startProcess(hostingType, entryPoint, app,
+                        uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,
+                        invokeWith, startTime);
+                handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
+                        startSeq, false);
+            } catch (RuntimeException e) {
+                Slog.e(TAG, "Failure starting process " + app.processName, e);
+                app.pendingStart = false;
+                forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
+                        false, false, true, false, false,
+                        UserHandle.getUserId(app.userId), "start failure");
+            }
+            return app.pid > 0;
+        }
+    }
+
+    private ProcessStartResult startProcess(String hostingType, String entryPoint,
+            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
+            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
+            long startTime) {
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
+                    app.processName);
+            checkTime(startTime, "startProcess: asking zygote to start proc");
+            final ProcessStartResult startResult;
+            if (hostingType.equals("webview_service")) {
+                startResult = startWebView(entryPoint,
+                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
+                        app.info.dataDir, null,
+                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+            } else {
+                startResult = Process.start(entryPoint,
+                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
+                        app.info.dataDir, invokeWith,
+                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+            }
+            checkTime(startTime, "startProcess: returned from zygote!");
+            return startResult;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    @GuardedBy("this")
+    private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+        StringBuilder sb = null;
+        if (app.killedByAm) {
+            if (sb == null) sb = new StringBuilder();
+            sb.append("killedByAm=true;");
+        }
+        if (mProcessNames.get(app.processName, app.uid) != app) {
+            if (sb == null) sb = new StringBuilder();
+            sb.append("No entry in mProcessNames;");
+        }
+        if (!app.pendingStart) {
+            if (sb == null) sb = new StringBuilder();
+            sb.append("pendingStart=false;");
+        }
+        if (app.startSeq > expectedStartSeq) {
+            if (sb == null) sb = new StringBuilder();
+            sb.append("seq=" + app.startSeq + ",expected=" + expectedStartSeq + ";");
+        }
+        return sb == null ? null : sb.toString();
+    }
+
+    @GuardedBy("this")
+    private boolean handleProcessStartedLocked(ProcessRecord pending,
+            ProcessStartResult startResult, long expectedStartSeq) {
+        // Indicates that this process start has been taken care of.
+        if (mPendingStarts.get(expectedStartSeq) == null) {
+            if (pending.pid == startResult.pid) {
+                pending.usingWrapper = startResult.usingWrapper;
+                // TODO: Update already existing clients of usingWrapper
+            }
+            return false;
+        }
+        return handleProcessStartedLocked(pending, startResult.pid, startResult.usingWrapper,
+                expectedStartSeq, false);
+    }
+
+    @GuardedBy("this")
+    private boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,
+            long expectedStartSeq, boolean procAttached) {
+        mPendingStarts.remove(expectedStartSeq);
+        final String reason = isProcStartValidLocked(app, expectedStartSeq);
+        if (reason != null) {
+            Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" + pid
+                    + ", " + reason);
+            app.pendingStart = false;
+            Process.killProcessQuiet(pid);
+            Process.killProcessGroup(app.uid, app.pid);
+            return false;
+        }
+        mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
+        checkTime(app.startTime, "startProcess: done updating battery stats");
+
+        EventLog.writeEvent(EventLogTags.AM_PROC_START,
+                UserHandle.getUserId(app.startUid), pid, app.startUid,
+                app.processName, app.hostingType,
+                app.hostingNameStr != null ? app.hostingNameStr : "");
+
+        try {
+            AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
+                    app.seInfo, app.info.sourceDir, pid);
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+
+        if (app.persistent) {
+            Watchdog.getInstance().processStarted(app.processName, pid);
+        }
+
+        checkTime(app.startTime, "startProcess: building log message");
+        StringBuilder buf = mStringBuilder;
+        buf.setLength(0);
+        buf.append("Start proc ");
+        buf.append(pid);
+        buf.append(':');
+        buf.append(app.processName);
+        buf.append('/');
+        UserHandle.formatUid(buf, app.startUid);
+        if (app.isolatedEntryPoint != null) {
+            buf.append(" [");
+            buf.append(app.isolatedEntryPoint);
+            buf.append("]");
+        }
+        buf.append(" for ");
+        buf.append(app.hostingType);
+        if (app.hostingNameStr != null) {
+            buf.append(" ");
+            buf.append(app.hostingNameStr);
+        }
+        Slog.i(TAG, buf.toString());
+        app.setPid(pid);
+        app.usingWrapper = usingWrapper;
+        app.pendingStart = false;
+        checkTime(app.startTime, "startProcess: starting to update pids map");
+        ProcessRecord oldApp;
+        synchronized (mPidsSelfLocked) {
+            oldApp = mPidsSelfLocked.get(pid);
+        }
+        // If there is already an app occupying that pid that hasn't been cleaned up
+        if (oldApp != null && !app.isolated) {
+            // Clean up anything relating to this pid first
+            Slog.w(TAG, "Reusing pid " + pid
+                    + " while app is still mapped to it");
+            cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+                    true /*replacingPid*/);
+        }
+        synchronized (mPidsSelfLocked) {
+            this.mPidsSelfLocked.put(pid, app);
+            if (!procAttached) {
+                Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
+                msg.obj = app;
+                mHandler.sendMessageDelayed(msg, usingWrapper
+                        ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
+            }
+        }
+        checkTime(app.startTime, "startProcess: done updating pids map");
+        return true;
+    }
+
     void updateUsageStats(ActivityRecord component, boolean resumed) {
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
                 "updateUsageStats: comp=" + component + "res=" + resumed);
@@ -5903,7 +6055,7 @@
                 return;
             }
 
-            if (app != null) {
+            if (app != null && app.pid > 0) {
                 ArrayList<Integer> firstPids = new ArrayList<Integer>();
                 firstPids.add(app.pid);
                 dumpStackTraces(tracesPath, firstPids, null, null, true /* useTombstoned */);
@@ -6878,13 +7030,19 @@
             mHeavyWeightProcess = null;
         }
         boolean needRestart = false;
-        if (app.pid > 0 && app.pid != MY_PID) {
+        if ((app.pid > 0 && app.pid != MY_PID) || (app.pid == 0 && app.pendingStart)) {
             int pid = app.pid;
-            synchronized (mPidsSelfLocked) {
-                mPidsSelfLocked.remove(pid);
-                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            if (pid > 0) {
+                synchronized (mPidsSelfLocked) {
+                    mPidsSelfLocked.remove(pid);
+                    mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+                }
+                mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
+                if (app.isolated) {
+                    mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+                    getPackageManagerInternalLocked().removeIsolatedUid(app.uid);
+                }
             }
-            mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
             boolean willRestart = false;
             if (app.persistent && !app.isolated) {
                 if (!callerWillRestart) {
@@ -6894,10 +7052,6 @@
                 }
             }
             app.kill(reason, true);
-            if (app.isolated) {
-                mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
-                getPackageManagerInternalLocked().removeIsolatedUid(app.uid);
-            }
             handleAppDiedLocked(app, willRestart, allowRestart);
             if (willRestart) {
                 removeLruProcessLocked(app);
@@ -6971,7 +7125,7 @@
     }
 
     private final boolean attachApplicationLocked(IApplicationThread thread,
-            int pid) {
+            int pid, int callingUid, long startSeq) {
 
         // Find the application record that is being attached...  either via
         // the pid if we are running in multiple processes, or just pull the
@@ -6986,6 +7140,17 @@
             app = null;
         }
 
+        // It's possible that process called attachApplication before we got a chance to
+        // update the internal state.
+        if (app == null && startSeq > 0) {
+            final ProcessRecord pending = mPendingStarts.get(startSeq);
+            if (pending != null && pending.startUid == callingUid
+                    && handleProcessStartedLocked(pending, pid, pending.usingWrapper,
+                            startSeq, true)) {
+                app = pending;
+            }
+        }
+
         if (app == null) {
             Slog.w(TAG, "No pending application record for pid " + pid
                     + " (IApplicationThread " + thread + "); dropping process");
@@ -7280,11 +7445,12 @@
     }
 
     @Override
-    public final void attachApplication(IApplicationThread thread) {
+    public final void attachApplication(IApplicationThread thread, long startSeq) {
         synchronized (this) {
             int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             final long origId = Binder.clearCallingIdentity();
-            attachApplicationLocked(thread, callingPid);
+            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
             Binder.restoreCallingIdentity(origId);
         }
     }
@@ -10198,26 +10364,6 @@
     }
 
     @Override
-    public void cancelTaskThumbnailTransition(int taskId) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
-                "cancelTaskThumbnailTransition()");
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (this) {
-                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
-                if (task == null) {
-                    Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
-                    return;
-                }
-                task.cancelThumbnailTransition();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
         enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
@@ -11348,7 +11494,11 @@
 
     private final long[] mProcessStateStatsLongs = new long[1];
 
-    boolean isProcessAliveLocked(ProcessRecord proc) {
+    private boolean isProcessAliveLocked(ProcessRecord proc) {
+        if (proc.pid <= 0) {
+            if (DEBUG_OOM_ADJ) Slog.d(TAG, "Process hasn't started yet: " + proc);
+            return false;
+        }
         if (proc.procStatFile == null) {
             proc.procStatFile = "/proc/" + proc.pid + "/stat";
         }
@@ -12434,7 +12584,8 @@
             if (wasAwake != isAwake) {
                 // Also update state in a special way for running foreground services UI.
                 mServices.updateScreenStateLocked(isAwake);
-                sendNotifyVrManagerOfSleepState(!isAwake);
+                mHandler.obtainMessage(DISPATCH_SCREEN_AWAKE_MSG, isAwake ? 1 : 0, 0)
+                        .sendToTarget();
             }
         }
     }
@@ -12590,7 +12741,9 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
-        sendNotifyVrManagerOfKeyguardState(showing);
+
+        mHandler.obtainMessage(DISPATCH_SCREEN_KEYGUARD_MSG, showing ? 1 : 0, 0)
+                .sendToTarget();
     }
 
     @Override
@@ -13973,8 +14126,7 @@
             for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
                 ProcessRecord proc = mLruProcesses.get(i);
                 if (proc.notCachedSinceIdle) {
-                    if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP_SLEEPING
-                            && proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+                    if (proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
                             && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
                         if (doKilling && proc.initialIdlePss != 0
                                 && proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -15906,6 +16058,14 @@
             }
             pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
         }
+        if (dumpAll && mPendingStarts.size() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            pw.println("  mPendingStarts: ");
+            for (int i = 0, len = mPendingStarts.size(); i < len; ++i ) {
+                pw.println("    " + mPendingStarts.keyAt(i) + ": " + mPendingStarts.valueAt(i));
+            }
+        }
         if (dumpPackage == null) {
             pw.println("  mGlobalConfiguration: " + getGlobalConfiguration());
             mStackSupervisor.dumpDisplayConfigs(pw, "  ");
@@ -16940,7 +17100,7 @@
                 }
                 for (int i=mLruProcesses.size()-1; i>=0; i--) {
                     ProcessRecord proc = mLruProcesses.get(i);
-                    if (proc.pid == pid) {
+                    if (proc.pid > 0 && proc.pid == pid) {
                         procs.add(proc);
                     } else if (allPkgs && proc.pkgList != null
                             && proc.pkgList.containsKey(args[start])) {
@@ -17061,20 +17221,24 @@
         }
     }
 
+    private static void sortMemItems(List<MemItem> items) {
+        Collections.sort(items, new Comparator<MemItem>() {
+            @Override
+            public int compare(MemItem lhs, MemItem rhs) {
+                if (lhs.pss < rhs.pss) {
+                    return 1;
+                } else if (lhs.pss > rhs.pss) {
+                    return -1;
+                }
+                return 0;
+            }
+        });
+    }
+
     static final void dumpMemItems(PrintWriter pw, String prefix, String tag,
             ArrayList<MemItem> items, boolean sort, boolean isCompact, boolean dumpSwapPss) {
         if (sort && !isCompact) {
-            Collections.sort(items, new Comparator<MemItem>() {
-                @Override
-                public int compare(MemItem lhs, MemItem rhs) {
-                    if (lhs.pss < rhs.pss) {
-                        return 1;
-                    } else if (lhs.pss > rhs.pss) {
-                        return -1;
-                    }
-                    return 0;
-                }
-            });
+            sortMemItems(items);
         }
 
         for (int i=0; i<items.size(); i++) {
@@ -17102,6 +17266,33 @@
         }
     }
 
+    static final void dumpMemItems(ProtoOutputStream proto, long fieldId, String tag,
+            ArrayList<MemItem> items, boolean sort, boolean dumpSwapPss) {
+        if (sort) {
+            sortMemItems(items);
+        }
+
+        for (int i=0; i<items.size(); i++) {
+            MemItem mi = items.get(i);
+            final long token = proto.start(fieldId);
+
+            proto.write(MemInfoProto.MemItem.TAG, tag);
+            proto.write(MemInfoProto.MemItem.LABEL, mi.shortLabel);
+            proto.write(MemInfoProto.MemItem.IS_PROC, mi.isProc);
+            proto.write(MemInfoProto.MemItem.ID, mi.id);
+            proto.write(MemInfoProto.MemItem.HAS_ACTIVITIES, mi.hasActivities);
+            proto.write(MemInfoProto.MemItem.PSS_KB, mi.pss);
+            if (dumpSwapPss) {
+                proto.write(MemInfoProto.MemItem.SWAP_PSS_KB, mi.swapPss);
+            }
+            if (mi.subitems != null) {
+                dumpMemItems(proto, MemInfoProto.MemItem.SUB_ITEMS, mi.shortLabel, mi.subitems,
+                        true, dumpSwapPss);
+            }
+            proto.end(token);
+        }
+    }
+
     // These are in KB.
     static final long[] DUMP_MEM_BUCKETS = new long[] {
         5*1024, 7*1024, 10*1024, 15*1024, 20*1024, 30*1024, 40*1024, 80*1024,
@@ -17313,7 +17504,7 @@
 
         ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
         if (opts.dumpProto) {
-            dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+            dumpApplicationMemoryUsage(fd, opts, innerArgs, brief, procs);
         } else {
             dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
         }
@@ -17803,7 +17994,7 @@
         }
     }
 
-    private final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+    private final void dumpApplicationMemoryUsage(FileDescriptor fd,
             MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
             ArrayList<ProcessRecord> procs) {
         final long uptimeMs = SystemClock.uptimeMillis();
@@ -17845,8 +18036,8 @@
                             final int pid = r.pid;
                             final long nToken = proto.start(MemInfoProto.NATIVE_PROCESSES);
 
-                            proto.write(MemInfoProto.NativeProcess.PID, pid);
-                            proto.write(MemInfoProto.NativeProcess.PROCESS_NAME, r.baseName);
+                            proto.write(MemInfoProto.ProcessMemory.PID, pid);
+                            proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.baseName);
 
                             if (mi == null) {
                                 mi = new Debug.MemoryInfo();
@@ -17872,8 +18063,332 @@
             return;
         }
 
-        // TODO: finish
-        pw.println("Java processes aren't implemented yet. Have a coffee instead! :]");
+        if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+            opts.dumpDetails = true;
+        }
+
+        ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        proto.write(MemInfoProto.UPTIME_DURATION_MS, uptimeMs);
+        proto.write(MemInfoProto.ELAPSED_REALTIME_MS, realtimeMs);
+
+        ArrayList<MemItem> procMems = new ArrayList<MemItem>();
+        final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
+        long nativePss = 0;
+        long nativeSwapPss = 0;
+        long dalvikPss = 0;
+        long dalvikSwapPss = 0;
+        long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+                EmptyArray.LONG;
+        long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+                EmptyArray.LONG;
+        long otherPss = 0;
+        long otherSwapPss = 0;
+        long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+        long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+
+        long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
+        long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
+        ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[])
+                new ArrayList[DUMP_MEM_OOM_LABEL.length];
+
+        long totalPss = 0;
+        long totalSwapPss = 0;
+        long cachedPss = 0;
+        long cachedSwapPss = 0;
+        boolean hasSwapPss = false;
+
+        Debug.MemoryInfo mi = null;
+        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+            final ProcessRecord r = procs.get(i);
+            final IApplicationThread thread;
+            final int pid;
+            final int oomAdj;
+            final boolean hasActivities;
+            synchronized (this) {
+                thread = r.thread;
+                pid = r.pid;
+                oomAdj = r.getSetAdjWithServices();
+                hasActivities = r.activities.size() > 0;
+            }
+            if (thread == null) {
+                continue;
+            }
+            if (mi == null) {
+                mi = new Debug.MemoryInfo();
+            }
+            if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
+                Debug.getMemoryInfo(pid, mi);
+                hasSwapPss = mi.hasSwappedOutPss;
+            } else {
+                mi.dalvikPss = (int) Debug.getPss(pid, tmpLong, null);
+                mi.dalvikPrivateDirty = (int) tmpLong[0];
+            }
+            if (opts.dumpDetails) {
+                if (opts.localOnly) {
+                    final long aToken = proto.start(MemInfoProto.APP_PROCESSES);
+                    final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY);
+                    proto.write(MemInfoProto.ProcessMemory.PID, pid);
+                    proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.processName);
+                    ActivityThread.dumpMemInfoTable(proto, mi, opts.dumpDalvik,
+                            opts.dumpSummaryOnly, 0, 0, 0, 0, 0, 0);
+                    proto.end(mToken);
+                    proto.end(aToken);
+                } else {
+                    try {
+                        ByteTransferPipe tp = new ByteTransferPipe();
+                        try {
+                            thread.dumpMemInfoProto(tp.getWriteFd(),
+                                mi, opts.dumpFullDetails, opts.dumpDalvik, opts.dumpSummaryOnly,
+                                opts.dumpUnreachable, innerArgs);
+                            proto.write(MemInfoProto.APP_PROCESSES, tp.get());
+                        } finally {
+                            tp.kill();
+                        }
+                    } catch (IOException e) {
+                        Log.e(TAG, "Got IOException!", e);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Got RemoteException!", e);
+                    }
+                }
+            }
+
+            final long myTotalPss = mi.getTotalPss();
+            final long myTotalUss = mi.getTotalUss();
+            final long myTotalSwapPss = mi.getTotalSwappedOutPss();
+
+            synchronized (this) {
+                if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+                    // Record this for posterity if the process has been stable.
+                    r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList);
+                }
+            }
+
+            if (!opts.isCheckinRequest && mi != null) {
+                totalPss += myTotalPss;
+                totalSwapPss += myTotalSwapPss;
+                MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
+                        (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
+                        myTotalSwapPss, pid, hasActivities);
+                procMems.add(pssItem);
+                procMemsMap.put(pid, pssItem);
+
+                nativePss += mi.nativePss;
+                nativeSwapPss += mi.nativeSwappedOutPss;
+                dalvikPss += mi.dalvikPss;
+                dalvikSwapPss += mi.dalvikSwappedOutPss;
+                for (int j=0; j<dalvikSubitemPss.length; j++) {
+                    dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                    dalvikSubitemSwapPss[j] +=
+                            mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                }
+                otherPss += mi.otherPss;
+                otherSwapPss += mi.otherSwappedOutPss;
+                for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                    long mem = mi.getOtherPss(j);
+                    miscPss[j] += mem;
+                    otherPss -= mem;
+                    mem = mi.getOtherSwappedOutPss(j);
+                    miscSwapPss[j] += mem;
+                    otherSwapPss -= mem;
+                }
+
+                if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                    cachedPss += myTotalPss;
+                    cachedSwapPss += myTotalSwapPss;
+                }
+
+                for (int oomIndex=0; oomIndex<oomPss.length; oomIndex++) {
+                    if (oomIndex == (oomPss.length - 1)
+                            || (oomAdj >= DUMP_MEM_OOM_ADJ[oomIndex]
+                                    && oomAdj < DUMP_MEM_OOM_ADJ[oomIndex + 1])) {
+                        oomPss[oomIndex] += myTotalPss;
+                        oomSwapPss[oomIndex] += myTotalSwapPss;
+                        if (oomProcs[oomIndex] == null) {
+                            oomProcs[oomIndex] = new ArrayList<MemItem>();
+                        }
+                        oomProcs[oomIndex].add(pssItem);
+                        break;
+                    }
+                }
+            }
+        }
+
+        long nativeProcTotalPss = 0;
+
+        if (procs.size() > 1 && !opts.packages) {
+            // If we are showing aggregations, also look for native processes to
+            // include so that our aggregations are more accurate.
+            updateCpuStatsNow();
+            mi = null;
+            synchronized (mProcessCpuTracker) {
+                final int N = mProcessCpuTracker.countStats();
+                for (int i=0; i<N; i++) {
+                    ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                    if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+                        if (mi == null) {
+                            mi = new Debug.MemoryInfo();
+                        }
+                        if (!brief && !opts.oomOnly) {
+                            Debug.getMemoryInfo(st.pid, mi);
+                        } else {
+                            mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
+                            mi.nativePrivateDirty = (int)tmpLong[0];
+                        }
+
+                        final long myTotalPss = mi.getTotalPss();
+                        final long myTotalSwapPss = mi.getTotalSwappedOutPss();
+                        totalPss += myTotalPss;
+                        nativeProcTotalPss += myTotalPss;
+
+                        MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
+                                st.name, myTotalPss, mi.getSummaryTotalSwapPss(), st.pid, false);
+                        procMems.add(pssItem);
+
+                        nativePss += mi.nativePss;
+                        nativeSwapPss += mi.nativeSwappedOutPss;
+                        dalvikPss += mi.dalvikPss;
+                        dalvikSwapPss += mi.dalvikSwappedOutPss;
+                        for (int j=0; j<dalvikSubitemPss.length; j++) {
+                            dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                            dalvikSubitemSwapPss[j] +=
+                                    mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                        }
+                        otherPss += mi.otherPss;
+                        otherSwapPss += mi.otherSwappedOutPss;
+                        for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                            long mem = mi.getOtherPss(j);
+                            miscPss[j] += mem;
+                            otherPss -= mem;
+                            mem = mi.getOtherSwappedOutPss(j);
+                            miscSwapPss[j] += mem;
+                            otherSwapPss -= mem;
+                        }
+                        oomPss[0] += myTotalPss;
+                        oomSwapPss[0] += myTotalSwapPss;
+                        if (oomProcs[0] == null) {
+                            oomProcs[0] = new ArrayList<MemItem>();
+                        }
+                        oomProcs[0].add(pssItem);
+                    }
+                }
+            }
+
+            ArrayList<MemItem> catMems = new ArrayList<MemItem>();
+
+            catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, -1));
+            final int dalvikId = -2;
+            catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikId));
+            catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, -3));
+            for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                String label = Debug.MemoryInfo.getOtherLabel(j);
+                catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], j));
+            }
+            if (dalvikSubitemPss.length > 0) {
+                // Add dalvik subitems.
+                for (MemItem memItem : catMems) {
+                    int memItemStart = 0, memItemEnd = 0;
+                    if (memItem.id == dalvikId) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_DALVIK_OTHER) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_DEX) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_ART) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_ART_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_ART_END;
+                    } else {
+                        continue;  // No subitems, continue.
+                    }
+                    memItem.subitems = new ArrayList<MemItem>();
+                    for (int j=memItemStart; j<=memItemEnd; j++) {
+                        final String name = Debug.MemoryInfo.getOtherLabel(
+                                Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                        memItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j],
+                                dalvikSubitemSwapPss[j], j));
+                    }
+                }
+            }
+
+            ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
+            for (int j=0; j<oomPss.length; j++) {
+                if (oomPss[j] != 0) {
+                    String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+                            : DUMP_MEM_OOM_LABEL[j];
+                    MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
+                            DUMP_MEM_OOM_ADJ[j]);
+                    item.subitems = oomProcs[j];
+                    oomMems.add(item);
+                }
+            }
+
+            opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+            if (!opts.oomOnly) {
+                dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_PROCESS, "proc",
+                        procMems, true, opts.dumpSwapPss);
+            }
+            dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_OOM_ADJUSTMENT, "oom",
+                    oomMems, false, opts.dumpSwapPss);
+            if (!brief && !opts.oomOnly) {
+                dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_CATEGORY, "cat",
+                        catMems, true, opts.dumpSwapPss);
+            }
+            MemInfoReader memInfo = new MemInfoReader();
+            memInfo.readMemInfo();
+            if (nativeProcTotalPss > 0) {
+                synchronized (this) {
+                    final long cachedKb = memInfo.getCachedSizeKb();
+                    final long freeKb = memInfo.getFreeSizeKb();
+                    final long zramKb = memInfo.getZramTotalSizeKb();
+                    final long kernelKb = memInfo.getKernelUsedSizeKb();
+                    EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024,
+                            kernelKb*1024, nativeProcTotalPss*1024);
+                    mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb,
+                            nativeProcTotalPss);
+                }
+            }
+            if (!brief) {
+                proto.write(MemInfoProto.TOTAL_RAM_KB, memInfo.getTotalSizeKb());
+                proto.write(MemInfoProto.STATUS, mLastMemoryLevel);
+                proto.write(MemInfoProto.CACHED_PSS_KB, cachedPss);
+                proto.write(MemInfoProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb());
+                proto.write(MemInfoProto.FREE_KB, memInfo.getFreeSizeKb());
+            }
+            long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
+                    - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+                    - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
+            proto.write(MemInfoProto.USED_PSS_KB, totalPss - cachedPss);
+            proto.write(MemInfoProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb());
+            proto.write(MemInfoProto.LOST_RAM_KB, lostRAM);
+            if (!brief) {
+                if (memInfo.getZramTotalSizeKb() != 0) {
+                    proto.write(MemInfoProto.TOTAL_ZRAM_KB, memInfo.getZramTotalSizeKb());
+                    proto.write(MemInfoProto.ZRAM_PHYSICAL_USED_IN_SWAP_KB,
+                            memInfo.getSwapTotalSizeKb() - memInfo.getSwapFreeSizeKb());
+                    proto.write(MemInfoProto.TOTAL_ZRAM_SWAP_KB, memInfo.getSwapTotalSizeKb());
+                }
+                final long[] ksm = getKsmInfo();
+                proto.write(MemInfoProto.KSM_SHARING_KB, ksm[KSM_SHARING]);
+                proto.write(MemInfoProto.KSM_SHARED_KB, ksm[KSM_SHARED]);
+                proto.write(MemInfoProto.KSM_UNSHARED_KB, ksm[KSM_UNSHARED]);
+                proto.write(MemInfoProto.KSM_VOLATILE_KB, ksm[KSM_VOLATILE]);
+
+                proto.write(MemInfoProto.TUNING_MB, ActivityManager.staticGetMemoryClass());
+                proto.write(MemInfoProto.TUNING_LARGE_MB, ActivityManager.staticGetLargeMemoryClass());
+                proto.write(MemInfoProto.OOM_KB,
+                        mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024);
+                proto.write(MemInfoProto.RESTORE_LIMIT_KB,
+                        mProcessList.getCachedRestoreThresholdKb());
+
+                proto.write(MemInfoProto.IS_LOW_RAM_DEVICE, ActivityManager.isLowRamDeviceStatic());
+                proto.write(MemInfoProto.IS_HIGH_END_GFX, ActivityManager.isHighEndGfx());
+            }
+        }
+
+        proto.flush();
     }
 
     private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
@@ -18366,7 +18881,7 @@
 
         for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
             ProcessChangeItem item = mPendingProcessChanges.get(i);
-            if (item.pid == app.pid) {
+            if (app.pid > 0 && item.pid == app.pid) {
                 mPendingProcessChanges.remove(i);
                 mAvailProcessChanges.add(item);
             }
@@ -18418,6 +18933,7 @@
                 ProcessList.remove(app.pid);
             }
             addProcessNameLocked(app);
+            app.pendingStart = false;
             startProcessLocked(app, "restart", app.processName);
             return true;
         } else if (app.pid > 0 && app.pid != MY_PID) {
@@ -19364,16 +19880,14 @@
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                 ALLOW_NON_FULL, "broadcast", callerPackage);
 
-        // Make sure that the user who is receiving this broadcast is running.
-        // If not, we will just skip it. Make an exception for shutdown broadcasts
-        // and upgrade steps.
-
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
+        // Make sure that the user who is receiving this broadcast or its parent is running.
+        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
             if ((callingUid != SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                 Slog.w(TAG, "Skipping broadcast of " + intent
-                        + ": user " + userId + " is stopped");
+                        + ": user " + userId + " and its parent (if any) are stopped");
                 return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
             }
         }
@@ -21155,7 +21669,7 @@
         int procState;
         boolean foregroundActivities = false;
         mTmpBroadcastQueue.clear();
-        if (app == TOP_APP) {
+        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
             // The last app on the list is the foreground app.
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -21191,6 +21705,13 @@
             procState = ActivityManager.PROCESS_STATE_SERVICE;
             if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making exec-service: " + app);
             //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+        } else if (app == TOP_APP) {
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            app.adjType = "top-sleeping";
+            foregroundActivities = true;
+            procState = PROCESS_STATE_CUR_TOP;
+            if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making top: " + app);
         } else {
             // As far as we know the process is empty.  We may change our mind later.
             schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
@@ -22639,7 +23160,7 @@
         if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
             isInteraction = true;
             app.fgInteractionTime = 0;
-        } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+        } else if (app.curProcState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
             if (app.fgInteractionTime == 0) {
                 app.fgInteractionTime = nowElapsed;
                 isInteraction = false;
@@ -23073,7 +23594,8 @@
                                 break;
                         }
                     }
-                } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+                        && !app.killedByAm) {
                     if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
                             && app.thread != null) {
                         try {
@@ -23610,7 +24132,7 @@
                         + ")\n");
                     if (app.pid > 0 && app.pid != MY_PID) {
                         app.kill("empty", false);
-                    } else {
+                    } else if (app.thread != null) {
                         try {
                             app.thread.scheduleExit();
                         } catch (Exception e) {
@@ -24126,7 +24648,7 @@
         }
 
         @Override
-        public int startIsolatedProcess(String entryPoint, String[] entryPointArgs,
+        public boolean startIsolatedProcess(String entryPoint, String[] entryPointArgs,
                 String processName, String abiOverride, int uid, Runnable crashHandler) {
             return ActivityManagerService.this.startIsolatedProcess(entryPoint, entryPointArgs,
                     processName, abiOverride, uid, crashHandler);
@@ -24527,6 +25049,11 @@
             }
             return false;
         }
+
+        @Override
+        public void registerScreenObserver(ScreenObserver observer) {
+            mScreenObservers.add(observer);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 69f6d5e..8eb5197 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityOptions.ANIM_CUSTOM;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
@@ -1476,6 +1477,9 @@
                         }
                     }
                     break;
+                case ANIM_OPEN_CROSS_PROFILE_APPS:
+                    service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps();
+                    break;
                 default:
                     Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
                     break;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index d0bc33a..35465a7 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -521,13 +521,13 @@
      *
      * @param app The ProcessRecord in which the error occurred.
      * @param condition Crashing, Application Not Responding, etc.  Values are defined in
-     *                      ActivityManager.AppErrorStateInfo
+     *                      ActivityManager.ProcessErrorStateInfo
      * @param activity The activity associated with the crash, if known.
      * @param shortMsg Short message describing the crash.
      * @param longMsg Long message describing the crash.
      * @param stackTrace Full crash stack trace, may be null.
      *
-     * @return Returns a fully-formed AppErrorStateInfo record.
+     * @return Returns a fully-formed ProcessErrorStateInfo record.
      */
     private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
             int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index f3ccba5..4582430 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -116,6 +116,50 @@
         return scheduleSyncLocked("remove-uid", UPDATE_CPU);
     }
 
+    @Override
+    public Future<?> scheduleReadProcStateCpuTimes() {
+        synchronized (mStats) {
+            if (!mStats.mPerProcStateCpuTimesAvailable) {
+                return null;
+            }
+        }
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (!mExecutorService.isShutdown()) {
+                return mExecutorService.submit(mReadProcStateCpuTimesTask);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+        synchronized (mStats) {
+            if (!mStats.mPerProcStateCpuTimesAvailable) {
+                return null;
+            }
+        }
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (!mExecutorService.isShutdown()) {
+                return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+            }
+        }
+        return null;
+    }
+
+    private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
+        @Override
+        public void run() {
+            mStats.updateProcStateCpuTimes();
+        }
+    };
+
+    private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
+        @Override
+        public void run() {
+            mStats.copyFromAllUidsCpuTimes();
+        }
+    };
+
     public synchronized Future<?> scheduleWrite() {
         if (mExecutorService.isShutdown()) {
             return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -185,6 +229,10 @@
                 }
             }
 
+            if ((updateFlags & UPDATE_CPU) != 0) {
+                mStats.copyFromAllUidsCpuTimes();
+            }
+
             // Clean up any UIDs if necessary.
             synchronized (mStats) {
                 for (int uid : uidsToRemove) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 93fb3e3..35318f6 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -38,6 +38,7 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.os.WorkSource;
+import android.os.connectivity.CellularBatteryStats;
 import android.os.health.HealthStatsParceler;
 import android.os.health.HealthStatsWriter;
 import android.os.health.UidHealthStats;
@@ -207,6 +208,10 @@
         }
     }
 
+    private void syncStats(String reason, int flags) {
+        awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize the low power observer later.
@@ -225,7 +230,7 @@
     public void shutdown() {
         Slog.w("BatteryStats", "Writing battery stats before shutdown...");
 
-        awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("shutdown", BatteryExternalStatsWorker.UPDATE_ALL);
 
         synchronized (mStats) {
             mStats.shutdownLocked();
@@ -357,7 +362,7 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -372,7 +377,7 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -459,15 +464,16 @@
             boolean unimportantForLogging) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging,
-                    SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+            mStats.noteStartWakeLocked(uid, pid, null, name, historyName, type,
+                    unimportantForLogging, SystemClock.elapsedRealtime(),
+                    SystemClock.uptimeMillis());
         }
     }
 
     public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteStopWakeLocked(uid, pid, name, historyName, type,
+            mStats.noteStopWakeLocked(uid, pid, null, name, historyName, type,
                     SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
         }
     }
@@ -1237,8 +1243,7 @@
                     }
                     mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
                 } else if ("--write".equals(arg)) {
-                    awaitUninterruptibly(mWorker.scheduleSync("dump",
-                            BatteryExternalStatsWorker.UPDATE_ALL));
+                    syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
                     synchronized (mStats) {
                         mStats.writeSyncLocked();
                         pw.println("Battery stats written.");
@@ -1302,7 +1307,7 @@
                 flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
             }
             // Fetch data from external sources and update the BatteryStatsImpl object with them.
-            awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1405,6 +1410,16 @@
     }
 
     /**
+     * Gets a snapshot of cellular stats
+     * @hide
+     */
+    public CellularBatteryStats getCellularBatteryStats() {
+        synchronized (mStats) {
+            return mStats.getCellularBatteryStats();
+        }
+    }
+
+    /**
      * Gets a snapshot of the system health for a particular uid.
      */
     @Override
@@ -1415,8 +1430,7 @@
         }
         long ident = Binder.clearCallingIdentity();
         try {
-            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
-                    BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
             synchronized (mStats) {
                 return getHealthStatsForUidLocked(requestUid);
             }
@@ -1440,8 +1454,7 @@
         long ident = Binder.clearCallingIdentity();
         int i=-1;
         try {
-            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
-                    BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
             synchronized (mStats) {
                 final int N = requestUids.length;
                 final HealthStatsParceler[] results = new HealthStatsParceler[N];
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index aa82d00..ea90db3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -322,7 +322,7 @@
     public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
         boolean didSomething = false;
         final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.pid == app.pid) {
+        if (br != null && br.curApp.pid > 0 && br.curApp.pid == app.pid) {
             if (br.curApp != app) {
                 Slog.e(TAG, "App mismatch when sending pending broadcast to "
                         + app.processName + ", intended target is " + br.curApp.processName);
@@ -876,9 +876,16 @@
                         + mPendingBroadcast.curApp);
 
                 boolean isDead;
-                synchronized (mService.mPidsSelfLocked) {
-                    ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
-                    isDead = proc == null || proc.crashing;
+                if (mPendingBroadcast.curApp.pid > 0) {
+                    synchronized (mService.mPidsSelfLocked) {
+                        ProcessRecord proc = mService.mPidsSelfLocked.get(
+                                mPendingBroadcast.curApp.pid);
+                        isDead = proc == null || proc.crashing;
+                    }
+                } else {
+                    final ProcessRecord proc = mService.mProcessNames.get(
+                            mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
+                    isDead = proc == null || !proc.pendingStart;
                 }
                 if (!isDead) {
                     // It's still alive, so keep waiting
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
index 014f708..1e70809 100644
--- a/services/core/java/com/android/server/am/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -21,6 +21,7 @@
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -43,8 +44,12 @@
      */
     void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
         transaction.schedule();
-        // TODO: b/70616950
-        //transaction.recycle();
+        if (!(transaction.getClient() instanceof Binder)) {
+            // If client is not an instance of Binder - it's a remote call and at this point it is
+            // safe to recycle the object. All objects used for local calls will be recycled after
+            // the transaction is executed on client in ActivityThread.
+            transaction.recycle();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6fb3dbb..ab5d64c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -364,9 +364,6 @@
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
                 procState = "FGS ";
                 break;
-            case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
-                procState = "TPSL";
-                break;
             case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
                 procState = "IMPF";
                 break;
@@ -379,15 +376,18 @@
             case ActivityManager.PROCESS_STATE_BACKUP:
                 procState = "BKUP";
                 break;
-            case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
-                procState = "HVY ";
-                break;
             case ActivityManager.PROCESS_STATE_SERVICE:
                 procState = "SVC ";
                 break;
             case ActivityManager.PROCESS_STATE_RECEIVER:
                 procState = "RCVR";
                 break;
+            case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+                procState = "TPSL";
+                break;
+            case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+                procState = "HVY ";
+                break;
             case ActivityManager.PROCESS_STATE_HOME:
                 procState = "HOME";
                 break;
@@ -485,14 +485,14 @@
         PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BACKUP
-        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PROC_MEM_SERVICE,               // ActivityManager.PROCESS_STATE_SERVICE
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_RECEIVER
+        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_HOME
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -507,14 +507,14 @@
         PSS_FIRST_TOP_INTERVAL,         // ActivityManager.PROCESS_STATE_TOP
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BACKUP
-        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_SERVICE
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HOME
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -529,14 +529,14 @@
         PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_TOP
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BACKUP
-        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_SERVICE
         PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -545,20 +545,20 @@
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
-    private static final long[] sTestFirstAwakePssTimes = new long[] {
+    private static final long[] sTestFirstPssTimes = new long[] {
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_PERSISTENT
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_TOP
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
-        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -567,20 +567,20 @@
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
-    private static final long[] sTestSameAwakePssTimes = new long[] {
+    private static final long[] sTestSamePssTimes = new long[] {
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_PERSISTENT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TOP
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_BACKUP
-        PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_SERVICE
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HOME
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -601,8 +601,8 @@
             boolean sleeping, long now) {
         final long[] table = test
                 ? (first
-                        ? sTestFirstAwakePssTimes
-                        : sTestSameAwakePssTimes)
+                        ? sTestFirstPssTimes
+                        : sTestSamePssTimes)
                 : (first
                         ? sFirstAwakePssTimes
                         : sSameAwakePssTimes);
@@ -628,6 +628,7 @@
 
     /**
      * Set the out-of-memory badness adjustment for a process.
+     * If {@code pid <= 0}, this method will be a no-op.
      *
      * @param pid The process identifier to set.
      * @param uid The uid of the app
@@ -636,6 +637,10 @@
      * {@hide}
      */
     public static final void setOomAdj(int pid, int uid, int amt) {
+        // This indicates that the process is not started yet and so no need to proceed further.
+        if (pid <= 0) {
+            return;
+        }
         if (amt == UNKNOWN_ADJ)
             return;
 
@@ -657,6 +662,10 @@
      * {@hide}
      */
     public static final void remove(int pid) {
+        // This indicates that the process is not started yet and so no need to proceed further.
+        if (pid <= 0) {
+            return;
+        }
         ByteBuffer buf = ByteBuffer.allocate(4 * 2);
         buf.putInt(LMK_PROCREMOVE);
         buf.putInt(pid);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 71d6604..a1e5947 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -196,6 +196,9 @@
 
     String shortStringName;     // caching of toShortString() result.
     String stringName;          // caching of toString() result.
+    boolean pendingStart;       // Process start is pending.
+    long startSeq;              // Seq no. indicating the latest process start associated with
+                                // this process record.
 
     // These reports are generated & stored when an app gets into an error condition.
     // They will be "null" when all is OK.
@@ -211,6 +214,23 @@
     // App is allowed to manage whitelists such as temporary Power Save mode whitelist.
     boolean whitelistManager;
 
+    // Params used in starting this process.
+    String hostingType;
+    String hostingNameStr;
+    String seInfo;
+    long startTime;
+    // This will be same as {@link #uid} usually except for some apps used during factory testing.
+    int startUid;
+
+    void setStartParams(int startUid, String hostingType, String hostingNameStr, String seInfo,
+            long startTime) {
+        this.startUid = startUid;
+        this.hostingType = hostingType;
+        this.hostingNameStr = hostingNameStr;
+        this.seInfo = seInfo;
+        this.startTime = startTime;
+    }
+
     void dump(PrintWriter pw, String prefix) {
         final long nowUptime = SystemClock.uptimeMillis();
 
@@ -348,6 +368,10 @@
         if (hasStartedServices) {
             pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
         }
+        if (pendingStart) {
+            pw.print(prefix); pw.print("pendingStart="); pw.println(pendingStart);
+        }
+        pw.print(prefix); pw.print("startSeq="); pw.println(startSeq);
         if (setProcState > ActivityManager.PROCESS_STATE_SERVICE) {
             pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
                     if (lastCpuTime > 0) {
@@ -627,9 +651,13 @@
             if (noisy) {
                 Slog.i(TAG, "Killing " + toShortString() + " (adj " + setAdj + "): " + reason);
             }
-            EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
-            Process.killProcessQuiet(pid);
-            ActivityManagerService.killProcessGroup(uid, pid);
+            if (pid > 0) {
+                EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
+                Process.killProcessQuiet(pid);
+                ActivityManagerService.killProcessGroup(uid, pid);
+            } else {
+                pendingStart = false;
+            }
             if (!persistent) {
                 killed = true;
                 killedByAm = true;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 91b3315..4aef95d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -772,10 +772,6 @@
         mWindowContainerController.cancelWindowTransition();
     }
 
-    void cancelThumbnailTransition() {
-        mWindowContainerController.cancelThumbnailTransition();
-    }
-
     /**
      * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
      */
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 14260c5..34621e0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1737,6 +1737,19 @@
         }
     }
 
+    boolean isUserOrItsParentRunning(int userId) {
+        synchronized (mLock) {
+            if (isUserRunning(userId, 0)) {
+                return true;
+            }
+            final int parentUserId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+            if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+                return false;
+            }
+            return isUserRunning(parentUserId, 0);
+        }
+    }
+
     boolean isCurrentProfile(int userId) {
         synchronized (mLock) {
             return ArrayUtils.contains(mCurrentProfileIds, userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index efa0bf8..6e7b43e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2203,7 +2203,7 @@
         return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
     }
 
-    /** @see AudioManager#getStreamMinVolume(int) */
+    /** @see AudioManager#getStreamMinVolumeInt(int) */
     public int getStreamMinVolume(int streamType) {
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].getMinIndex() + 5) / 10;
@@ -3884,7 +3884,8 @@
         IsInCall = telecomManager.isInCall();
         Binder.restoreCallingIdentity(ident);
 
-        return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION);
+        return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION ||
+                getMode() == AudioManager.MODE_IN_CALL);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c2167eb..85686ae 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
     private final float mProjMatrix[] = new float[16];
     private final int[] mGLBuffers = new int[2];
     private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
-    private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+    private int mOpacityLoc, mGammaLoc, mSaturationLoc;
     private int mProgram;
 
     // Vertex and corresponding texture coordinates.
@@ -246,7 +246,6 @@
         mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
         mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
         mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
-        mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
         mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
 
         GLES20.glUseProgram(mProgram);
@@ -395,9 +394,8 @@
             double sign = cos < 0 ? -1 : 1;
             float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
             float saturation = (float) Math.pow(level, 4);
-            float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
             float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
-            drawFaded(opacity, 1.f / gamma, saturation, scale);
+            drawFaded(opacity, 1.f / gamma, saturation);
             if (checkGlErrors("drawFrame")) {
                 return false;
             }
@@ -409,10 +407,10 @@
         return showSurface(1.0f);
     }
 
-    private void drawFaded(float opacity, float gamma, float saturation, float scale) {
+    private void drawFaded(float opacity, float gamma, float saturation) {
         if (DEBUG) {
             Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
-                        ", saturation=" + saturation + ", scale=" + scale);
+                        ", saturation=" + saturation);
         }
         // Use shaders
         GLES20.glUseProgram(mProgram);
@@ -423,7 +421,6 @@
         GLES20.glUniform1f(mOpacityLoc, opacity);
         GLES20.glUniform1f(mGammaLoc, gamma);
         GLES20.glUniform1f(mSaturationLoc, saturation);
-        GLES20.glUniform1f(mScaleLoc, scale);
 
         // Use textures
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 41d9feb..9640e04 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -179,7 +179,7 @@
     }
 
     /**
-     * Handles a nanoapp load event.
+     * Notifies the client of a nanoapp load event if the connection is open.
      *
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
@@ -195,7 +195,7 @@
     }
 
     /**
-     * Handles a nanoapp unload event.
+     * Notifies the client of a nanoapp unload event if the connection is open.
      *
      * @param nanoAppId the ID of the nanoapp that was unloaded.
      */
@@ -211,7 +211,7 @@
     }
 
     /**
-     * Handles a hub reset for this client.
+     * Notifies the client of a hub reset event if the connection is open.
      */
     /* package */ void onHubReset() {
         if (mConnectionOpen.get()) {
@@ -223,4 +223,21 @@
             }
         }
     }
+
+    /**
+     * Notifies the client of a nanoapp abort event if the connection is open.
+     *
+     * @param nanoAppId the ID of the nanoapp that aborted
+     * @param abortCode the nanoapp specific abort code
+     */
+    /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onNanoAppAborted(nanoAppId, abortCode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onNanoAppAborted on client"
+                        + " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index d58a746..60b5b1f 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -149,35 +149,38 @@
     }
 
     /**
-     * Handles a nanoapp load event.
-     *
-     * @param contextHubId the ID of the hub where the nanoapp was loaded.
-     * @param nanoAppId    the ID of the nanoapp that was loaded.
+     * @param contextHubId the ID of the hub where the nanoapp was loaded
+     * @param nanoAppId    the ID of the nanoapp that was loaded
      */
     /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
         forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
     }
 
     /**
-     * Handles a nanoapp unload event.
-     *
-     * @param contextHubId the ID of the hub where the nanoapp was unloaded.
-     * @param nanoAppId    the ID of the nanoapp that was unloaded.
+     * @param contextHubId the ID of the hub where the nanoapp was unloaded
+     * @param nanoAppId    the ID of the nanoapp that was unloaded
      */
     /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
         forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
     }
 
     /**
-     * Handles a hub reset.
-     *
-     * @param contextHubId the ID of the hub that has reset.
+     * @param contextHubId the ID of the hub that has reset
      */
     /* package */ void onHubReset(int contextHubId) {
         forEachClientOfHub(contextHubId, client -> client.onHubReset());
     }
 
     /**
+     * @param contextHubId the ID of the hub that contained the nanoapp that aborted
+     * @param nanoAppId the ID of the nanoapp that aborted
+     * @param abortCode the nanoapp specific abort code
+     */
+    /* package */ void onNanoAppAborted(int contextHubId, long nanoAppId, int abortCode) {
+        forEachClientOfHub(contextHubId, client -> client.onNanoAppAborted(nanoAppId, abortCode));
+    }
+
+    /**
      * Creates a new ContextHubClientBroker object for a client and registers it with the client
      * manager.
      *
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 1ad0cf9..dc95d41 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -79,7 +79,8 @@
 
     private final Context mContext;
 
-    private final ContextHubInfo[] mContextHubInfo;
+    private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+    private final List<ContextHubInfo> mContextHubInfoList;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
@@ -141,8 +142,9 @@
         if (mContextHubProxy == null) {
             mTransactionManager = null;
             mClientManager = null;
-            mDefaultClientMap = Collections.EMPTY_MAP;
-            mContextHubInfo = new ContextHubInfo[0];
+            mDefaultClientMap = Collections.emptyMap();
+            mContextHubIdToInfoMap = Collections.emptyMap();
+            mContextHubInfoList = Collections.emptyList();
             return;
         }
 
@@ -157,20 +159,16 @@
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubList = Collections.emptyList();
         }
-        mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+        mContextHubIdToInfoMap = Collections.unmodifiableMap(
+                ContextHubServiceUtil.createContextHubInfoMap(hubList));
+        mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            int contextHubId = contextHubInfo.getId();
-
+        for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
             IContextHubClient client = mClientManager.registerClient(
                     createDefaultClientCallback(contextHubId), contextHubId);
             defaultClientMap.put(contextHubId, client);
-        }
-        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
 
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            int contextHubId = contextHubInfo.getId();
             try {
                 mContextHubProxy.registerCallback(
                         contextHubId, new ContextHubServiceCallback(contextHubId));
@@ -178,18 +176,12 @@
                 Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
                         + contextHubId + ")", e);
             }
-        }
 
-        // Do a query to initialize the service cache list of nanoapps
-        // TODO(b/69270990): Remove this when old API is deprecated
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            queryNanoAppsInternal(contextHubInfo.getId());
+            // Do a query to initialize the service cache list of nanoapps
+            // TODO(b/69270990): Remove this when old API is deprecated
+            queryNanoAppsInternal(contextHubId);
         }
-
-        for (int i = 0; i < mContextHubInfo.length; i++) {
-            Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
-                    + ", name:  " + mContextHubInfo[i].getName());
-        }
+        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
     }
 
     /**
@@ -267,27 +259,29 @@
     @Override
     public int[] getContextHubHandles() throws RemoteException {
         checkPermissions();
-        int[] returnArray = new int[mContextHubInfo.length];
-
-        Log.d(TAG, "System supports " + returnArray.length + " hubs");
-        for (int i = 0; i < returnArray.length; ++i) {
-            returnArray[i] = i;
-            Log.d(TAG, String.format("Hub %s is mapped to %d",
-                    mContextHubInfo[i].getName(), returnArray[i]));
-        }
-
-        return returnArray;
+        return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
     }
 
     @Override
-    public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
+    public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
         checkPermissions();
-        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid context hub handle " + contextHubId);
-            return null; // null means fail
+        if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
+            return null;
         }
 
-        return mContextHubInfo[contextHubId];
+        return mContextHubIdToInfoMap.get(contextHubHandle);
+    }
+
+    /**
+     * Returns a List of ContextHubInfo object describing the available hubs.
+     *
+     * @return the List of ContextHubInfo objects
+     */
+    @Override
+    public List<ContextHubInfo> getContextHubs() throws RemoteException {
+        checkPermissions();
+        return mContextHubInfoList;
     }
 
     /**
@@ -351,28 +345,27 @@
     }
 
     @Override
-    public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
+    public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
         checkPermissions();
         if (mContextHubProxy == null) {
             return -1;
         }
-
-        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
+        if (!isValidContextHubId(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in loadNanoApp");
             return -1;
         }
-        if (app == null) {
-            Log.e(TAG, "Invalid null app");
+        if (nanoApp == null) {
+            Log.e(TAG, "NanoApp cannot be null in loadNanoApp");
             return -1;
         }
 
         // Create an internal IContextHubTransactionCallback for the old API clients
-        NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+        NanoAppBinary nanoAppBinary = new NanoAppBinary(nanoApp.getAppBinary());
         IContextHubTransactionCallback onCompleteCallback =
-                createLoadTransactionCallback(contextHubId, nanoAppBinary);
+                createLoadTransactionCallback(contextHubHandle, nanoAppBinary);
 
         ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
-                contextHubId, nanoAppBinary, onCompleteCallback);
+                contextHubHandle, nanoAppBinary, onCompleteCallback);
 
         mTransactionManager.addTransaction(transaction);
         return 0;
@@ -388,7 +381,7 @@
         NanoAppInstanceInfo info =
                 mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
         if (info == null) {
-            Log.e(TAG, "Cannot find nanoapp with handle " + nanoAppHandle);
+            Log.e(TAG, "Invalid nanoapp handle " + nanoAppHandle + " in unloadNanoApp");
             return -1;
         }
 
@@ -411,7 +404,8 @@
     }
 
     @Override
-    public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) throws RemoteException {
+    public int[] findNanoAppOnHub(
+            int contextHubHandle, NanoAppFilter filter) throws RemoteException {
         checkPermissions();
 
         ArrayList<Integer> foundInstances = new ArrayList<>();
@@ -425,12 +419,6 @@
         for (int i = 0; i < foundInstances.size(); i++) {
             retArray[i] = foundInstances.get(i).intValue();
         }
-
-        if (retArray.length == 0) {
-            Log.d(TAG, "No nanoapps found on hub ID " + hubHandle + " using NanoAppFilter: "
-                    + filter);
-        }
-
         return retArray;
     }
 
@@ -460,29 +448,29 @@
     }
 
     @Override
-    public int sendMessage(
-            int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+    public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
+            throws RemoteException {
         checkPermissions();
         if (mContextHubProxy == null) {
             return -1;
         }
         if (msg == null) {
-            Log.e(TAG, "ContextHubMessage cannot be null");
+            Log.e(TAG, "ContextHubMessage cannot be null in sendMessage");
             return -1;
         }
         if (msg.getData() == null) {
-            Log.e(TAG, "ContextHubMessage message body cannot be null");
+            Log.e(TAG, "ContextHubMessage message body cannot be null in sendMessage");
             return -1;
         }
-        if (!mDefaultClientMap.containsKey(hubHandle)) {
-            Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
+        if (!isValidContextHubId(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in sendMessage");
             return -1;
         }
 
         boolean success = false;
         if (nanoAppHandle == OS_APP_INSTANCE) {
             if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
-                success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+                success = (queryNanoAppsInternal(contextHubHandle) == Result.OK);
             } else {
                 Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
             }
@@ -492,9 +480,9 @@
                 NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
                         info.getAppId(), msg.getMsgType(), msg.getData());
 
-                IContextHubClient client = mDefaultClientMap.get(hubHandle);
+                IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
                 success = (client.sendMessageToNanoApp(message) ==
-                        ContextHubTransaction.TRANSACTION_SUCCESS);
+                        ContextHubTransaction.RESULT_SUCCESS);
             } else {
                 Log.e(TAG, "Failed to send nanoapp message - nanoapp with handle "
                         + nanoAppHandle + " does not exist.");
@@ -583,7 +571,7 @@
      * @param abortCode    the nanoapp-specific abort code
      */
     private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
-        // TODO(b/31049861): Implement this
+        mClientManager.onNanoAppAborted(contextHubId, nanoAppId, abortCode);
     }
 
     /**
@@ -605,13 +593,7 @@
      * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
      */
     private boolean isValidContextHubId(int contextHubId) {
-        for (ContextHubInfo hubInfo : mContextHubInfo) {
-            if (hubInfo.getId() == contextHubId) {
-                return true;
-            }
-        }
-
-        return false;
+        return mContextHubIdToInfoMap.containsKey(contextHubId);
     }
 
     /**
@@ -660,7 +642,7 @@
         if (nanoAppBinary == null) {
             Log.e(TAG, "NanoAppBinary cannot be null in loadNanoAppOnHub");
             transactionCallback.onTransactionComplete(
-                    ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+                    ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
             return;
         }
 
@@ -772,8 +754,8 @@
         pw.println("");
         // dump ContextHubInfo
         pw.println("=================== CONTEXT HUBS ====================");
-        for (int i = 0; i < mContextHubInfo.length; i++) {
-            pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+        for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
+            pw.println(hubInfo);
         }
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
@@ -789,7 +771,8 @@
         ContextHubServiceUtil.checkPermissions(mContext);
     }
 
-    private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+    private int onMessageReceiptOldApi(
+            int msgType, int contextHubHandle, int appInstance, byte[] data) {
         if (data == null) {
             return -1;
         }
@@ -797,7 +780,8 @@
         int msgVersion = 0;
         int callbacksCount = mCallbacksList.beginBroadcast();
         Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
-                hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+                contextHubHandle + ", appInstance " + appInstance + ", callBackCount "
+                + callbacksCount);
 
         if (callbacksCount < 1) {
             Log.v(TAG, "No message callbacks registered.");
@@ -808,7 +792,7 @@
         for (int i = 0; i < callbacksCount; ++i) {
             IContextHubCallback callback = mCallbacksList.getBroadcastItem(i);
             try {
-                callback.onMessageReceipt(hubHandle, appInstance, msg);
+                callback.onMessageReceipt(contextHubHandle, appInstance, msg);
             } catch (RemoteException e) {
                 Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ").");
                 continue;
@@ -833,7 +817,7 @@
         if (mContextHubProxy == null) {
             try {
                 callback.onTransactionComplete(
-                        ContextHubTransaction.TRANSACTION_FAILED_HAL_UNAVAILABLE);
+                        ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
             }
@@ -844,7 +828,7 @@
                     + ContextHubTransaction.typeToString(transactionType, false /* upperCase */)
                     + " transaction for invalid hub ID " + contextHubId);
             try {
-                callback.onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+                callback.onTransactionComplete(ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
             }
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 6faeb72..c356b63 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -30,6 +30,9 @@
 import android.hardware.location.NanoAppState;
 import android.util.Log;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ArrayList;
 
@@ -43,19 +46,20 @@
             + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
     /**
-     * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+     * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
+     * ArrayList of HIDL ContextHub objects.
      *
      * @param hubList the ContextHub ArrayList
-     * @return the ContextHubInfo array
+     * @return the HashMap object
      */
     /* package */
-    static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
-        ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
-        for (int i = 0; i < hubList.size(); i++) {
-            contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+    static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHub> hubList) {
+        HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
+        for (ContextHub contextHub : hubList) {
+            contextHubIdToInfoMap.put(contextHub.hubId, new ContextHubInfo(contextHub));
         }
 
-        return contextHubInfoList;
+        return contextHubIdToInfoMap;
     }
 
     /**
@@ -90,6 +94,22 @@
     }
 
     /**
+     * Creates a primitive integer array given a Collection<Integer>.
+     * @param collection the collection to iterate
+     * @return the primitive integer array
+     */
+    static int[] createPrimitiveIntArray(Collection<Integer> collection) {
+        int[] primitiveArray = new int[collection.size()];
+
+        int i = 0;
+        for (int contextHubId : collection) {
+            primitiveArray[i++] = contextHubId;
+        }
+
+        return primitiveArray;
+    }
+
+    /**
      * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
      * android.hardware.location.NanoAppBinary object.
      *
@@ -195,17 +215,17 @@
     static int toTransactionResult(int halResult) {
         switch (halResult) {
             case Result.OK:
-                return ContextHubTransaction.TRANSACTION_SUCCESS;
+                return ContextHubTransaction.RESULT_SUCCESS;
             case Result.BAD_PARAMS:
-                return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             case Result.NOT_INIT:
-                return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+                return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
             case Result.TRANSACTION_PENDING:
-                return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+                return ContextHubTransaction.RESULT_FAILED_PENDING;
             case Result.TRANSACTION_FAILED:
             case Result.UNKNOWN_FAILURE:
             default: /* fall through */
-                return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
index 412d43d..cced781 100644
--- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -120,7 +120,7 @@
 
             @Override
             /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
-                if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+                if (result == ContextHubTransaction.RESULT_SUCCESS) {
                     // NOTE: The legacy JNI code used to do a query right after a load success
                     // to synchronize the service cache. Instead store the binary that was
                     // requested to load to update the cache later without doing a query.
@@ -130,7 +130,7 @@
                 }
                 try {
                     onCompleteCallback.onTransactionComplete(result);
-                    if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+                    if (result == ContextHubTransaction.RESULT_SUCCESS) {
                         mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
                     }
                 } catch (RemoteException e) {
@@ -166,12 +166,12 @@
 
             @Override
             /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
-                if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+                if (result == ContextHubTransaction.RESULT_SUCCESS) {
                     mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId);
                 }
                 try {
                     onCompleteCallback.onTransactionComplete(result);
-                    if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+                    if (result == ContextHubTransaction.RESULT_SUCCESS) {
                         mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
                     }
                 } catch (RemoteException e) {
@@ -334,8 +334,8 @@
 
         transaction.onTransactionComplete(
                 (result == TransactionResult.SUCCESS) ?
-                        ContextHubTransaction.TRANSACTION_SUCCESS :
-                        ContextHubTransaction.TRANSACTION_FAILED_AT_HUB);
+                        ContextHubTransaction.RESULT_SUCCESS :
+                        ContextHubTransaction.RESULT_FAILED_AT_HUB);
         removeTransactionAndStartNext();
     }
 
@@ -356,7 +356,7 @@
             return;
         }
 
-        transaction.onQueryResponse(ContextHubTransaction.TRANSACTION_SUCCESS, nanoAppStateList);
+        transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
         removeTransactionAndStartNext();
     }
 
@@ -416,7 +416,7 @@
                         if (!transaction.isComplete()) {
                             Log.d(TAG, transaction + " timed out");
                             transaction.onTransactionComplete(
-                                    ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+                                    ContextHubTransaction.RESULT_FAILED_TIMEOUT);
 
                             removeTransactionAndStartNext();
                         }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index b324002..e6de07d 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -414,16 +414,16 @@
     private WorkSource mClientSource = new WorkSource();
 
     private GeofenceHardwareImpl mGeofenceHardwareImpl;
-    private int mYearOfHardware = 0;
+
+    // Volatile for simple inter-thread sync on these values.
+    private volatile int mHardwareYear = 0;
+    private volatile String mHardwareModelName = LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN;
 
     // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
     // stops output right at 600m/s, depriving this of the information of a device that reaches
     // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
     private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
 
-    // TODO: improve comment
-    // Volatile to ensure that potentially near-concurrent outputs from HAL
-    // react to this value change promptly
     private volatile boolean mItarSpeedLimitExceeded = false;
 
     // GNSS Metrics
@@ -1328,6 +1328,14 @@
             } else if (!mStarted) {
                 // start GPS
                 startNavigating(singleShot);
+            } else {
+                // GNSS Engine is already ON, but no GPS_CAPABILITY_SCHEDULING
+                mAlarmManager.cancel(mTimeoutIntent);
+                if (mFixInterval >= NO_FIX_TIMEOUT) {
+                    // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
+                    // and our fix interval is not short
+                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                            SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent);                }
             }
         } else {
             updateClientUids(new WorkSource());
@@ -1825,33 +1833,53 @@
     /**
      * called from native code to inform us what the GPS engine capabilities are
      */
-    private void setEngineCapabilities(int capabilities) {
-        mEngineCapabilities = capabilities;
+    private void setEngineCapabilities(final int capabilities) {
+        // send to handler thread for fast native return, and in-order handling
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mEngineCapabilities = capabilities;
 
-        if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
-            mOnDemandTimeInjection = true;
-            requestUtcTime();
-        }
+                if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
+                    mOnDemandTimeInjection = true;
+                    requestUtcTime();
+                }
 
-        mGnssMeasurementsProvider.onCapabilitiesUpdated(
-                (capabilities & GPS_CAPABILITY_MEASUREMENTS) == GPS_CAPABILITY_MEASUREMENTS);
-        mGnssNavigationMessageProvider.onCapabilitiesUpdated(
-                (capabilities & GPS_CAPABILITY_NAV_MESSAGES) == GPS_CAPABILITY_NAV_MESSAGES);
+                mGnssMeasurementsProvider.onCapabilitiesUpdated(hasCapability(
+                        GPS_CAPABILITY_MEASUREMENTS));
+                mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasCapability(
+                        GPS_CAPABILITY_NAV_MESSAGES));
+            }
+        });
+   }
+
+    /**
+     * Called from native code to inform us the hardware year.
+     */
+    private void setGnssYearOfHardware(final int yearOfHardware) {
+        // mHardwareYear is simply set here, to be read elsewhere, and is volatile for safe sync
+        if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware);
+        mHardwareYear = yearOfHardware;
     }
 
     /**
-     * Called from native code to inform us the hardware information.
+     * Called from native code to inform us the hardware model name.
      */
-    private void setGnssYearOfHardware(int yearOfHardware) {
-        if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware);
-        mYearOfHardware = yearOfHardware;
+    private void setGnssHardwareModelName(final String modelName) {
+        // mHardwareModelName is simply set here, to be read elsewhere, and volatile for safe sync
+        if (DEBUG) Log.d(TAG, "setGnssModelName called with " + modelName);
+        mHardwareModelName = modelName;
     }
 
     public interface GnssSystemInfoProvider {
         /**
-         * Returns the year of GPS hardware.
+         * Returns the year of underlying GPS hardware.
          */
         int getGnssYearOfHardware();
+        /**
+         * Returns the model name of underlying GPS hardware.
+         */
+        String getGnssHardwareModelName();
     }
 
     /**
@@ -1861,7 +1889,11 @@
         return new GnssSystemInfoProvider() {
             @Override
             public int getGnssYearOfHardware() {
-                return mYearOfHardware;
+                return mHardwareYear;
+            }
+            @Override
+            public String getGnssHardwareModelName() {
+                return mHardwareModelName;
             }
         };
     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 60f451a..9275497 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -28,6 +28,8 @@
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -36,6 +38,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.PasswordMetrics;
 import android.app.backup.BackupManager;
 import android.app.trust.IStrongAuthTracker;
@@ -60,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.ShellCallback;
 import android.os.StrictMode;
 import android.os.SystemProperties;
@@ -75,6 +79,10 @@
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore.UserNotAuthenticatedException;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader.RecoverableKeyStoreLoaderException;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
@@ -83,6 +91,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -93,11 +102,13 @@
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
-import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 
 import libcore.util.HexEncoding;
 
@@ -169,6 +180,8 @@
 
     private final KeyStore mKeyStore;
 
+    private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+
     private boolean mFirstCallToVold;
     protected IGateKeeperService mGateKeeperService;
 
@@ -367,6 +380,10 @@
             return KeyStore.getInstance();
         }
 
+        public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
+            return RecoverableKeyStoreManager.getInstance(mContext);
+        }
+
         public IStorageManager getStorageManager() {
             final IBinder service = ServiceManager.getService("mount");
             if (service != null) {
@@ -393,6 +410,7 @@
         mInjector = injector;
         mContext = injector.getContext();
         mKeyStore = injector.getKeyStore();
+        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
         mHandler = injector.getHandler();
         mStrongAuth = injector.getStrongAuth();
         mActivityManager = injector.getActivityManager();
@@ -876,14 +894,26 @@
             String managedUserPassword) {
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
-            setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
-            if (enabled) {
-                mStorage.removeChildProfileLock(userId);
-                removeKeystoreProfileKey(userId);
-            } else {
-                tieManagedProfileLockIfNecessary(userId, managedUserPassword);
-            }
+            setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
         }
+        notifySeparateProfileChallengeChanged(userId);
+    }
+
+    @GuardedBy("mSeparateChallengeLock")
+    private void setSeparateProfileChallengeEnabledLocked(@UserIdInt int userId, boolean enabled,
+            String managedUserPassword) {
+        setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
+        if (enabled) {
+            mStorage.removeChildProfileLock(userId);
+            removeKeystoreProfileKey(userId);
+        } else {
+            tieManagedProfileLockIfNecessary(userId, managedUserPassword);
+        }
+    }
+
+    private void notifySeparateProfileChallengeChanged(int userId) {
+        LocalServices.getService(DevicePolicyManagerInternal.class)
+                .reportSeparateProfileChallengeChanged(userId);
     }
 
     @Override
@@ -1219,9 +1249,10 @@
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
-            setSeparateProfileChallengeEnabled(userId, true, null);
+            setSeparateProfileChallengeEnabledLocked(userId, true, null);
             notifyPasswordChanged(userId);
         }
+        notifySeparateProfileChallengeChanged(userId);
     }
 
     private void setLockCredentialInternal(String credential, int credentialType,
@@ -1726,6 +1757,10 @@
                     }
                 }
             }
+            // Use credentials to create recoverable keystore snapshot.
+            mRecoverableKeyStoreManager.lockScreenSecretAvailable(storedHash.type, credential,
+                userId);
+
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
                 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
@@ -1914,6 +1949,90 @@
         }
     }
 
+    @Override
+    public void initRecoveryService(@NonNull String rootCertificateAlias,
+            @NonNull byte[] signedPublicKeyList, @UserIdInt int userId)
+            throws RemoteException {
+        mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias,
+                signedPublicKeyList, userId);
+    }
+
+    @Override
+    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId)
+            throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
+    }
+
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+            throws RemoteException {
+        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+    }
+
+    public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+    }
+
+    @Override
+    public void setServerParameters(long serverParameters, @UserIdInt int userId)
+            throws RemoteException {
+        mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
+    }
+
+    @Override
+    public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
+            int status, @UserIdInt int userId) throws RemoteException {
+        mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
+    }
+
+    public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+    }
+
+    @Override
+    public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
+            int[] secretTypes, @UserIdInt int userId) throws RemoteException {
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId);
+    }
+
+    @Override
+    public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId);
+
+    }
+
+    @Override
+    public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
+        throw new SecurityException("Not implemented");
+    }
+
+    @Override
+    public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret,
+            @UserIdInt int userId) throws RemoteException {
+        mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId);
+    }
+
+    @Override
+    public byte[] startRecoverySession(@NonNull String sessionId,
+            @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
+            @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets,
+            @UserIdInt int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
+                vaultParams, vaultChallenge, secrets, userId);
+    }
+
+    @Override
+    public Map<String, byte[]> recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId)
+            throws RemoteException {
+        return mRecoverableKeyStoreManager.recoverKeys(
+                sessionId, recoveryKeyBlob, applicationKeys, userId);
+    }
+
+    @Override
+    public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+        return mRecoverableKeyStoreManager.generateAndStoreKey(alias);
+    }
+
     private static final String[] VALID_SETTINGS = new String[] {
             LockPatternUtils.LOCKOUT_PERMANENT_KEY,
             LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
@@ -2344,9 +2463,10 @@
         }
         if (result) {
             synchronized (mSeparateChallengeLock) {
-                setSeparateProfileChallengeEnabled(userId, true, null);
+                setSeparateProfileChallengeEnabledLocked(userId, true, null);
             }
             notifyPasswordChanged(userId);
+            notifySeparateProfileChallengeChanged(userId);
         }
         return result;
     }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
new file mode 100644
index 0000000..9a4d051
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreProvider;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+
+public interface AndroidKeyStoreFactory {
+    KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
+
+    class Impl implements AndroidKeyStoreFactory {
+        @Override
+        public KeyStoreProxy getKeyStoreForUid(int uid)
+                throws KeyStoreException, NoSuchProviderException {
+            return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java
new file mode 100644
index 0000000..c7a98b6
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when the {@link PlatformDecryptionKey} instance has a different generation ID from
+ * the {@link WrappedKey} instance.
+ *
+ * @hide
+ */
+public class BadPlatformKeyException extends Exception {
+
+    /**
+     * A new instance with {@code message}.
+     *
+     * @hide
+     */
+    public BadPlatformKeyException(String message) {
+        super(message);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java
new file mode 100644
index 0000000..5155a99
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown initializing {@link PlatformKeyManager} if the user is not secure (i.e., has no
+ * lock screen set).
+ */
+public class InsecureUserException extends Exception {
+
+    /**
+     * A new instance with {@code message} error message.
+     */
+    public InsecureUserException(String message) {
+        super(message);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
new file mode 100644
index 0000000..8103177
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
@@ -0,0 +1,46 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Proxies {@link java.security.KeyStore}. As all of its methods are final, it cannot otherwise be
+ * mocked for tests.
+ *
+ * @hide
+ */
+public interface KeyStoreProxy {
+
+    /** @see KeyStore#containsAlias(String) */
+    boolean containsAlias(String alias) throws KeyStoreException;
+
+    /** @see KeyStore#getKey(String, char[]) */
+    Key getKey(String alias, char[] password)
+            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException;
+
+    /** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */
+    void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
+            throws KeyStoreException;
+
+    /** @see KeyStore#deleteEntry(String) */
+    void deleteEntry(String alias) throws KeyStoreException;
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
new file mode 100644
index 0000000..59132da
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.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.locksettings.recoverablekeystore;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Implementation of {@link KeyStoreProxy} that delegates all method calls to the {@link KeyStore}.
+ */
+public class KeyStoreProxyImpl implements KeyStoreProxy {
+
+    private final KeyStore mKeyStore;
+
+    /**
+     * A new instance, delegating to {@code keyStore}.
+     */
+    public KeyStoreProxyImpl(KeyStore keyStore) {
+        mKeyStore = keyStore;
+    }
+
+    @Override
+    public boolean containsAlias(String alias) throws KeyStoreException {
+        return mKeyStore.containsAlias(alias);
+    }
+
+    @Override
+    public Key getKey(String alias, char[] password)
+            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
+        return mKeyStore.getKey(alias, password);
+    }
+
+    @Override
+    public void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
+            throws KeyStoreException {
+        mKeyStore.setEntry(alias, entry, protParam);
+    }
+
+    @Override
+    public void deleteEntry(String alias) throws KeyStoreException {
+        mKeyStore.deleteEntry(alias);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
new file mode 100644
index 0000000..3434eee
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -0,0 +1,310 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Task to sync application keys to a remote vault service.
+ *
+ * @hide
+ */
+public class KeySyncTask implements Runnable {
+    private static final String TAG = "KeySyncTask";
+
+    private static final String RECOVERY_KEY_ALGORITHM = "AES";
+    private static final int RECOVERY_KEY_SIZE_BITS = 256;
+    private static final int SALT_LENGTH_BYTES = 16;
+    private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
+    private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
+
+    private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private final int mUserId;
+    private final RecoverableSnapshotConsumer mSnapshotConsumer;
+    private final int mCredentialType;
+    private final String mCredential;
+    private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
+    private final VaultKeySupplier mVaultKeySupplier;
+
+    public static KeySyncTask newInstance(
+            Context context,
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            int userId,
+            int credentialType,
+            String credential
+    ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
+        return new KeySyncTask(
+                recoverableKeyStoreDb,
+                userId,
+                credentialType,
+                credential,
+                () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId),
+                (salt, recoveryKey, applicationKeys) -> {
+                    // TODO: implement sending intent
+                },
+                () -> {
+                    throw new UnsupportedOperationException("Not implemented vault key.");
+                });
+    }
+
+    /**
+     * A new task.
+     *
+     * @param recoverableKeyStoreDb Database where the keys are stored.
+     * @param userId The uid of the user whose profile has been unlocked.
+     * @param credentialType The type of credential - i.e., pattern or password.
+     * @param credential The credential, encoded as a {@link String}.
+     * @param platformKeyManagerFactory Instantiates a {@link PlatformKeyManager} for the user.
+     *     This is a factory to enable unit testing, as otherwise it would be impossible to test
+     *     without a screen unlock occurring!
+     */
+    @VisibleForTesting
+    KeySyncTask(
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            int userId,
+            int credentialType,
+            String credential,
+            PlatformKeyManager.Factory platformKeyManagerFactory,
+            RecoverableSnapshotConsumer snapshotConsumer,
+            VaultKeySupplier vaultKeySupplier) {
+        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
+        mUserId = userId;
+        mCredentialType = credentialType;
+        mCredential = credential;
+        mPlatformKeyManagerFactory = platformKeyManagerFactory;
+        mSnapshotConsumer = snapshotConsumer;
+        mVaultKeySupplier = vaultKeySupplier;
+    }
+
+    @Override
+    public void run() {
+        try {
+            syncKeys();
+        } catch (Exception e) {
+            Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+        }
+    }
+
+    private void syncKeys() {
+        if (!isSyncPending()) {
+            Log.d(TAG, "Key sync not needed.");
+            return;
+        }
+
+        byte[] salt = generateSalt();
+        byte[] localLskfHash = hashCredentials(salt, mCredential);
+
+        Map<String, SecretKey> rawKeys;
+        try {
+            rawKeys = getKeysToSync();
+        } catch (GeneralSecurityException e) {
+            Log.e(TAG, "Failed to load recoverable keys for sync", e);
+            return;
+        } catch (InsecureUserException e) {
+            Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
+                    + "lock screen. This should be impossible.", e);
+            return;
+        } catch (BadPlatformKeyException e) {
+            Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
+                    + "BadPlatformKeyException should be impossible.", e);
+            return;
+        }
+
+        SecretKey recoveryKey;
+        try {
+            recoveryKey = generateRecoveryKey();
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf("AES should never be unavailable", e);
+            return;
+        }
+
+        Map<String, byte[]> encryptedApplicationKeys;
+        try {
+            encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
+                    recoveryKey, rawKeys);
+        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+            Log.wtf(TAG,
+                    "Should be impossible: could not encrypt application keys with random key",
+                    e);
+            return;
+        }
+
+        // TODO: construct vault params and vault metadata
+        byte[] vaultParams = {};
+
+        byte[] encryptedRecoveryKey;
+        try {
+            encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
+                    mVaultKeySupplier.get(),
+                    localLskfHash,
+                    vaultParams,
+                    recoveryKey);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
+            return;
+        } catch (InvalidKeyException e) {
+            Log.e(TAG,"Could not encrypt with recovery key", e);
+            return;
+        }
+
+        mSnapshotConsumer.accept(salt, encryptedRecoveryKey, encryptedApplicationKeys);
+    }
+
+    private PublicKey getVaultPublicKey() {
+        // TODO: fill this in
+        throw new UnsupportedOperationException("TODO: get vault public key.");
+    }
+
+    /**
+     * Returns all of the recoverable keys for the user.
+     */
+    private Map<String, SecretKey> getKeysToSync()
+            throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
+            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
+        PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
+        PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey();
+        Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
+                mUserId, decryptKey.getGenerationId());
+        return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
+    }
+
+    /**
+     * Returns {@code true} if a sync is pending.
+     */
+    private boolean isSyncPending() {
+        // TODO: implement properly. For now just always syncing if the user has any recoverable
+        // keys. We need to keep track of when the store's state actually changes.
+        return !mRecoverableKeyStoreDb.getAllKeys(
+                mUserId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(mUserId)).isEmpty();
+    }
+
+    /**
+     * The UI best suited to entering the given lock screen. This is synced with the vault so the
+     * user can be shown the same UI when recovering the vault on another device.
+     *
+     * @return The format - either pattern, pin, or password.
+     */
+    @VisibleForTesting
+    @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat(
+            int credentialType, String credential) {
+        if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+            return KeyStoreRecoveryMetadata.TYPE_PATTERN;
+        } else if (isPin(credential)) {
+            return KeyStoreRecoveryMetadata.TYPE_PIN;
+        } else {
+            return KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+        }
+    }
+
+    /**
+     * Generates a salt to include with the lock screen hash.
+     *
+     * @return The salt.
+     */
+    private byte[] generateSalt() {
+        byte[] salt = new byte[SALT_LENGTH_BYTES];
+        new SecureRandom().nextBytes(salt);
+        return salt;
+    }
+
+    /**
+     * Returns {@code true} if {@code credential} looks like a pin.
+     */
+    @VisibleForTesting
+    static boolean isPin(@NonNull String credential) {
+        int length = credential.length();
+        for (int i = 0; i < length; i++) {
+            if (!Character.isDigit(credential.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Hashes {@code credentials} with the given {@code salt}.
+     *
+     * @return The SHA-256 hash.
+     */
+    @VisibleForTesting
+    static byte[] hashCredentials(byte[] salt, String credentials) {
+        byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
+        ByteBuffer byteBuffer = ByteBuffer.allocate(
+                salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        byteBuffer.putInt(salt.length);
+        byteBuffer.put(salt);
+        byteBuffer.putInt(credentialsBytes.length);
+        byteBuffer.put(credentialsBytes);
+        byte[] bytes = byteBuffer.array();
+
+        try {
+            return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible, SHA-256 must be supported on Android.
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
+        keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
+        return keyGenerator.generateKey();
+    }
+
+    /**
+     * TODO: just replace with the Intent call. I'm not sure exactly what this looks like, hence
+     * this interface, so I can test in the meantime.
+     */
+    public interface RecoverableSnapshotConsumer {
+        void accept(
+                byte[] salt,
+                byte[] encryptedRecoveryKey,
+                Map<String, byte[]> encryptedApplicationKeys);
+    }
+
+    /**
+     * TODO: until this is in the database, so we can test.
+     */
+    public interface VaultKeySupplier {
+        PublicKey get();
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index e4d2b953..4597fad 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -16,15 +16,21 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
+import java.security.KeyFactory;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.crypto.AEADBadTagException;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
@@ -36,6 +42,7 @@
  */
 public class KeySyncUtils {
 
+    private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC";
     private static final String RECOVERY_KEY_ALGORITHM = "AES";
     private static final int RECOVERY_KEY_SIZE_BITS = 256;
 
@@ -45,9 +52,15 @@
             "V1 locally_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
     private static final byte[] ENCRYPTED_APPLICATION_KEY_HEADER =
             "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_CLAIM_HEADER =
+            "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
 
     private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8);
 
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+
     /**
      * Encrypts the recovery key using both the lock screen hash and the remote storage's public
      * key.
@@ -63,7 +76,7 @@
      *
      * @hide
      */
-    public byte[] thmEncryptRecoveryKey(
+    public static byte[] thmEncryptRecoveryKey(
             PublicKey publicKey,
             byte[] lockScreenHash,
             byte[] vaultParams,
@@ -105,7 +118,8 @@
      * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
      * @throws InvalidKeyException if the hash cannot be used to encrypt for some reason.
      */
-    private static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
+    @VisibleForTesting
+    static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
             throws NoSuchAlgorithmException, InvalidKeyException {
         return SecureBox.encrypt(
                 /*theirPublicKey=*/ null,
@@ -121,7 +135,7 @@
      */
     public static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
         KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
-        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, SecureRandom.getInstanceStrong());
+        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, new SecureRandom());
         return keyGenerator.generateKey();
     }
 
@@ -153,13 +167,137 @@
     }
 
     /**
-     * Returns a new array, the contents of which are the concatenation of {@code a} and {@code b}.
+     * Returns a random 16-byte key claimant.
+     *
+     * @hide
      */
-    private static byte[] concat(byte[] a, byte[] b) {
-        byte[] result = new byte[a.length + b.length];
-        System.arraycopy(a, 0, result, 0, a.length);
-        System.arraycopy(b, 0, result, a.length, b.length);
-        return result;
+    public static byte[] generateKeyClaimant() {
+        SecureRandom secureRandom = new SecureRandom();
+        byte[] key = new byte[KEY_CLAIMANT_LENGTH_BYTES];
+        secureRandom.nextBytes(key);
+        return key;
+    }
+
+    /**
+     * Encrypts a claim to recover a remote recovery key.
+     *
+     * @param publicKey The public key of the remote server.
+     * @param vaultParams Associated vault parameters.
+     * @param challenge The challenge issued by the server.
+     * @param thmKfHash The THM hash of the lock screen.
+     * @param keyClaimant The random key claimant.
+     * @return The encrypted recovery claim, to be sent to the remote server.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present.
+     * @throws InvalidKeyException if the {@code publicKey} could not be used to encrypt.
+     *
+     * @hide
+     */
+    public static byte[] encryptRecoveryClaim(
+            PublicKey publicKey,
+            byte[] vaultParams,
+            byte[] challenge,
+            byte[] thmKfHash,
+            byte[] keyClaimant) throws NoSuchAlgorithmException, InvalidKeyException {
+        return SecureBox.encrypt(
+                publicKey,
+                /*sharedSecret=*/ null,
+                /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                /*payload=*/ concat(thmKfHash, keyClaimant));
+    }
+
+    /**
+     * Decrypts response from recovery claim, returning the locally encrypted key.
+     *
+     * @param keyClaimant The key claimant, used by the remote service to encrypt the response.
+     * @param vaultParams Vault params associated with the claim.
+     * @param encryptedResponse The encrypted response.
+     * @return The locally encrypted recovery key.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present.
+     * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptRecoveryClaimResponse(
+            byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*encryptedPayload=*/ encryptedResponse);
+    }
+
+    /**
+     * Decrypts a recovery key, after having retrieved it from a remote server.
+     *
+     * @param lskfHash The lock screen hash associated with the key.
+     * @param encryptedRecoveryKey The encrypted key.
+     * @return The raw key material.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ lskfHash,
+                /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER,
+                /*encryptedPayload=*/ encryptedRecoveryKey);
+    }
+
+    /**
+     * Decrypts an application key, using the recovery key.
+     *
+     * @param recoveryKey The recovery key - used to wrap all application keys.
+     * @param encryptedApplicationKey The application key to unwrap.
+     * @return The raw key material of the application key.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ recoveryKey,
+                /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER,
+                /*encryptedPayload=*/ encryptedApplicationKey);
+    }
+
+    /**
+     * Deserializes a X509 public key.
+     *
+     * @param key The bytes of the key.
+     * @return The key.
+     * @throws NoSuchAlgorithmException if the public key algorithm is unavailable.
+     * @throws InvalidKeySpecException if the bytes of the key are not a valid key.
+     */
+    public static PublicKey deserializePublicKey(byte[] key)
+            throws NoSuchAlgorithmException, InvalidKeySpecException {
+        KeyFactory keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM);
+        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key);
+        return keyFactory.generatePublic(publicKeySpec);
+    }
+
+    /**
+     * Returns the concatenation of all the given {@code arrays}.
+     */
+    @VisibleForTesting
+    static byte[] concat(byte[]... arrays) {
+        int length = 0;
+        for (byte[] array : arrays) {
+            length += array.length;
+        }
+
+        byte[] concatenated = new byte[length];
+        int pos = 0;
+        for (byte[] array : arrays) {
+            System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length);
+            pos += array.length;
+        }
+
+        return concatenated;
     }
 
     // Statics only
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
new file mode 100644
index 0000000..0f17294
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
@@ -0,0 +1,68 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available.
+ * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
+ * class.
+ *
+ * @hide
+ */
+public class ListenersStorage {
+    private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
+
+    private static final ListenersStorage mInstance = new ListenersStorage();
+    public static ListenersStorage getInstance() {
+        return mInstance;
+    }
+
+    /**
+     * Sets new listener for the recovery agent, identified by {@code uid}
+     *
+     * @param recoveryAgentUid uid
+     * @param intent PendingIntent which will be triggered than new snapshot is available.
+     */
+    public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
+        mAgentIntents.put(recoveryAgentUid, intent);
+    }
+
+    /**
+     * Notifies recovery agent, that new snapshot is available.
+     * Does nothing if a listener was not registered.
+     *
+     * @param recoveryAgentUid uid.
+     */
+    public void recoverySnapshotAvailable(int recoveryAgentUid) {
+        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+        if (intent != null) {
+            try {
+                intent.send();
+            } catch (PendingIntent.CanceledException e) {
+                // Ignore - sending intent is not allowed.
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java
new file mode 100644
index 0000000..35571f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.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.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreSecretKey;
+
+/**
+ * Used to unwrap recoverable keys before syncing them with remote storage.
+ *
+ * <p>This is a private key stored in AndroidKeyStore. Has an associated generation ID, which is
+ * stored with wrapped keys, allowing us to ensure the wrapped key has the same version as the
+ * platform key.
+ *
+ * @hide
+ */
+public class PlatformDecryptionKey {
+
+    private final int mGenerationId;
+    private final AndroidKeyStoreSecretKey mKey;
+
+    /**
+     * A new instance.
+     *
+     * @param generationId The generation ID of the platform key.
+     * @param key The key handle in AndroidKeyStore.
+     *
+     * @hide
+     */
+    public PlatformDecryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
+        mGenerationId = generationId;
+        mKey = key;
+    }
+
+    /**
+     * Returns the generation ID.
+     *
+     * @hide
+     */
+    public int getGenerationId() {
+        return mGenerationId;
+    }
+
+    /**
+     * Returns the actual key, which can be used to decrypt.
+     *
+     * @hide
+     */
+    public AndroidKeyStoreSecretKey getKey() {
+        return mKey;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
new file mode 100644
index 0000000..38f5b45
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
@@ -0,0 +1,62 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreSecretKey;
+
+/**
+ * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk.
+ *
+ * <p>Identified by a generation ID, which increments whenever a new platform key is generated. A
+ * new key must be generated whenever the user disables their lock screen, as the decryption key is
+ * tied to lock-screen authentication.
+ *
+ * <p>One current platform key exists per profile on the device. (As each must be tied to a
+ * different user's lock screen.)
+ *
+ * @hide
+ */
+public class PlatformEncryptionKey {
+
+    private final int mGenerationId;
+    private final AndroidKeyStoreSecretKey mKey;
+
+    /**
+     * A new instance.
+     *
+     * @param generationId The generation ID of the key.
+     * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock.
+     */
+    public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
+        mGenerationId = generationId;
+        mKey = key;
+    }
+
+    /**
+     * Returns the generation ID of the key.
+     */
+    public int getGenerationId() {
+        return mGenerationId;
+    }
+
+    /**
+     * Returns the actual key, which can only be used to encrypt.
+     */
+    public AndroidKeyStoreSecretKey getKey() {
+        return mKey;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
new file mode 100644
index 0000000..95f5cb7
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.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 com.android.server.locksettings.recoverablekeystore;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Locale;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+
+/**
+ * Manages creating and checking the validity of the platform key.
+ *
+ * <p>The platform key is used to wrap the material of recoverable keys before persisting them to
+ * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with
+ * a recovery key and syncing them with remote storage.
+ *
+ * <p>Each platform key has two entries in AndroidKeyStore:
+ *
+ * <ul>
+ *     <li>Encrypt entry - this entry enables the root user to at any time encrypt.
+ *     <li>Decrypt entry - this entry enables the root user to decrypt only after recent user
+ *       authentication, i.e., within 15 seconds after a screen unlock.
+ * </ul>
+ *
+ * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm.
+ *
+ * @hide
+ */
+public class PlatformKeyManager {
+    private static final String TAG = "PlatformKeyManager";
+
+    private static final String KEY_ALGORITHM = "AES";
+    private static final int KEY_SIZE_BITS = 256;
+    private static final String KEY_ALIAS_PREFIX =
+            "com.android.server.locksettings.recoverablekeystore/platform/";
+    private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
+    private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
+    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+
+    private final Context mContext;
+    private final KeyStoreProxy mKeyStore;
+    private final RecoverableKeyStoreDb mDatabase;
+    private final int mUserId;
+
+    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+
+    /**
+     * A new instance operating on behalf of {@code userId}, storing its prefs in the location
+     * defined by {@code context}.
+     *
+     * @param context This should be the context of the RecoverableKeyStoreLoader service.
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
+     * @throws KeyStoreException if failed to initialize AndroidKeyStore.
+     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+     * @throws InsecureUserException if the user does not have a lock screen set.
+     * @throws SecurityException if the caller does not have permission to write to /data/system.
+     *
+     * @hide
+     */
+    public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
+            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+        context = context.getApplicationContext();
+        PlatformKeyManager keyManager = new PlatformKeyManager(
+                userId,
+                context,
+                new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
+                database);
+        keyManager.init();
+        return keyManager;
+    }
+
+    @VisibleForTesting
+    PlatformKeyManager(
+            int userId,
+            Context context,
+            KeyStoreProxy keyStore,
+            RecoverableKeyStoreDb database) {
+        mUserId = userId;
+        mKeyStore = keyStore;
+        mContext = context;
+        mDatabase = database;
+    }
+
+    /**
+     * Returns the current generation ID of the platform key. This increments whenever a platform
+     * key has to be replaced. (e.g., because the user has removed and then re-added their lock
+     * screen).
+     *
+     * @hide
+     */
+    public int getGenerationId() {
+        int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
+        if (generationId == -1) {
+            return 1;
+        }
+        return generationId;
+    }
+
+    /**
+     * Returns {@code true} if the platform key is available. A platform key won't be available if
+     * the user has not set up a lock screen.
+     *
+     * @hide
+     */
+    public boolean isAvailable() {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId);
+    }
+
+    /**
+     * Generates a new key and increments the generation ID. Should be invoked if the platform key
+     * is corrupted and needs to be rotated.
+     *
+     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+     * @throws KeyStoreException if there is an error in AndroidKeyStore.
+     *
+     * @hide
+     */
+    public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
+        int nextId = getGenerationId() + 1;
+        generateAndLoadKey(nextId);
+        setGenerationId(nextId);
+    }
+
+    /**
+     * Returns the platform key used for encryption.
+     *
+     * @throws KeyStoreException if there was an AndroidKeyStore error.
+     * @throws UnrecoverableKeyException if the key could not be recovered.
+     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+     *
+     * @hide
+     */
+    public PlatformEncryptionKey getEncryptKey()
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
+        int generationId = getGenerationId();
+        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
+                getEncryptAlias(generationId), /*password=*/ null);
+        return new PlatformEncryptionKey(generationId, key);
+    }
+
+    /**
+     * Returns the platform key used for decryption. Only works after a recent screen unlock.
+     *
+     * @throws KeyStoreException if there was an AndroidKeyStore error.
+     * @throws UnrecoverableKeyException if the key could not be recovered.
+     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+     *
+     * @hide
+     */
+    public PlatformDecryptionKey getDecryptKey()
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
+        int generationId = getGenerationId();
+        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
+                getDecryptAlias(generationId), /*password=*/ null);
+        return new PlatformDecryptionKey(generationId, key);
+    }
+
+    /**
+     * Initializes the class. If there is no current platform key, and the user has a lock screen
+     * set, will create the platform key and set the generation ID.
+     *
+     * @throws KeyStoreException if there was an error in AndroidKeyStore.
+     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+     *
+     * @hide
+     */
+    public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+        if (!isAvailable()) {
+            throw new InsecureUserException(String.format(
+                    Locale.US, "%d does not have a lock screen set.", mUserId));
+        }
+
+        int generationId = getGenerationId();
+        if (isKeyLoaded(generationId)) {
+            Log.i(TAG, String.format(
+                    Locale.US, "Platform key generation %d exists already.", generationId));
+            return;
+        }
+        if (generationId == 1) {
+            Log.i(TAG, "Generating initial platform ID.");
+        } else {
+            Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+                    + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
+        }
+
+        generateAndLoadKey(generationId);
+    }
+
+    /**
+     * Returns the alias of the encryption key with the specific {@code generationId} in the
+     * AndroidKeyStore.
+     *
+     * <p>These IDs look as follows:
+     * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
+     *
+     * @param generationId The generation ID.
+     * @return The alias.
+     */
+    private String getEncryptAlias(int generationId) {
+        return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
+    }
+
+    /**
+     * Returns the alias of the decryption key with the specific {@code generationId} in the
+     * AndroidKeyStore.
+     *
+     * <p>These IDs look as follows:
+     * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
+     *
+     * @param generationId The generation ID.
+     * @return The alias.
+     */
+    private String getDecryptAlias(int generationId) {
+        return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
+    }
+
+    /**
+     * Sets the current generation ID to {@code generationId}.
+     */
+    private void setGenerationId(int generationId) {
+        mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
+    }
+
+    /**
+     * Returns {@code true} if a key has been loaded with the given {@code generationId} into
+     * AndroidKeyStore.
+     *
+     * @throws KeyStoreException if there was an error checking AndroidKeyStore.
+     */
+    private boolean isKeyLoaded(int generationId) throws KeyStoreException {
+        return mKeyStore.containsAlias(getEncryptAlias(generationId))
+                && mKeyStore.containsAlias(getDecryptAlias(generationId));
+    }
+
+    /**
+     * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
+     * {@code generationId} determining its aliases.
+     *
+     * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is
+     *     available since API version 1.
+     * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
+     */
+    private void generateAndLoadKey(int generationId)
+            throws NoSuchAlgorithmException, KeyStoreException {
+        String encryptAlias = getEncryptAlias(generationId);
+        String decryptAlias = getDecryptAlias(generationId);
+        SecretKey secretKey = generateAesKey();
+
+        mKeyStore.setEntry(
+                encryptAlias,
+                new KeyStore.SecretKeyEntry(secretKey),
+                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                    .build());
+        mKeyStore.setEntry(
+                decryptAlias,
+                new KeyStore.SecretKeyEntry(secretKey),
+                new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+                    .setUserAuthenticationRequired(true)
+                    .setUserAuthenticationValidityDurationSeconds(
+                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                    .setBoundToSpecificSecureUserId(mUserId)
+                    .build());
+
+        try {
+            secretKey.destroy();
+        } catch (DestroyFailedException e) {
+            Log.w(TAG, "Failed to destroy in-memory platform key.", e);
+        }
+    }
+
+    /**
+     * Generates a new 256-bit AES key, in software.
+     *
+     * @return The software-generated AES key.
+     * @throws NoSuchAlgorithmException if AES key generation is not available. This should never
+     *     happen, as AES has been supported since API level 1.
+     */
+    private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(KEY_SIZE_BITS);
+        return keyGenerator.generateKey();
+    }
+
+    /**
+     * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
+     * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
+     *
+     * @throws KeyStoreException if there was a problem getting or initializing the key store.
+     */
+    private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
+        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
+        try {
+            keyStore.load(/*param=*/ null);
+        } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
+            // Should never happen.
+            throw new KeyStoreException("Unable to load keystore.", e);
+        }
+        return keyStore;
+    }
+
+    /**
+     * @hide
+     */
+    public interface Factory {
+        /**
+         * New PlatformKeyManager instance.
+         *
+         * @hide
+         */
+        PlatformKeyManager newInstance()
+                throws NoSuchAlgorithmException, InsecureUserException, KeyStoreException;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index 40c7889..8c23d9b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,20 +16,15 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import android.security.keystore.AndroidKeyStoreSecretKey;
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.util.Log;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
-import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
+import java.util.Locale;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
-import javax.security.auth.DestroyFailedException;
 
 /**
  * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
@@ -41,40 +36,34 @@
  * @hide
  */
 public class RecoverableKeyGenerator {
-    private static final String TAG = "RecoverableKeyGenerator";
+    private static final int RESULT_CANNOT_INSERT_ROW = -1;
     private static final String KEY_GENERATOR_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
 
     /**
      * A new {@link RecoverableKeyGenerator} instance.
      *
-     * @param platformKey Secret key used to wrap generated keys before persisting to disk.
-     * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
      * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
      *     unavailable. Should never happen.
      *
      * @hide
      */
-    public static RecoverableKeyGenerator newInstance(
-            AndroidKeyStoreSecretKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
+    public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
             throws NoSuchAlgorithmException {
         // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
         // material, so that it can be synced to disk in encrypted form.
         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
-        return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
+        return new RecoverableKeyGenerator(keyGenerator, database);
     }
 
     private final KeyGenerator mKeyGenerator;
-    private final RecoverableKeyStorage mRecoverableKeyStorage;
-    private final AndroidKeyStoreSecretKey mPlatformKey;
+    private final RecoverableKeyStoreDb mDatabase;
 
     private RecoverableKeyGenerator(
             KeyGenerator keyGenerator,
-            AndroidKeyStoreSecretKey platformKey,
-            RecoverableKeyStorage recoverableKeyStorage) {
+            RecoverableKeyStoreDb recoverableKeyStoreDb) {
         mKeyGenerator = keyGenerator;
-        mRecoverableKeyStorage = recoverableKeyStorage;
-        mPlatformKey = platformKey;
+        mDatabase = recoverableKeyStoreDb;
     }
 
     /**
@@ -84,50 +73,32 @@
      * persisted to disk so that it can be synced remotely, and then recovered on another device.
      * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
      *
-     * <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
-     * meaning that the caller is never able to access the raw, unencrypted key.
-     *
-     * @param alias The alias by which the key will be known in AndroidKeyStore.
+     * @param platformKey The user's platform key, with which to wrap the generated key.
+     * @param userId The user ID of the profile to which the calling app belongs.
+     * @param uid The uid of the application that will own the key.
+     * @param alias The alias by which the key will be known in the recoverable key store.
+     * @throws RecoverableKeyStorageException if there is some error persisting the key either to
+     *     the database.
+     * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
      * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
-     * @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
-     * @throws UnrecoverableEntryException if could not retrieve key after putting it in
-     *     AndroidKeyStore. This should not happen.
-     * @return A handle to the AndroidKeyStore key.
      *
      * @hide
      */
-    public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
-            InvalidKeyException, IOException, UnrecoverableEntryException {
+    public byte[] generateAndStoreKey(
+            PlatformEncryptionKey platformKey, int userId, int uid, String alias)
+            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
         mKeyGenerator.init(KEY_SIZE_BITS);
         SecretKey key = mKeyGenerator.generateKey();
 
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                alias,
-                key,
-                new KeyProtection.Builder(
-                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                        .build());
-        WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
+        long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
 
-        try {
-            // Keep raw key material in memory for minimum possible time.
-            key.destroy();
-        } catch (DestroyFailedException e) {
-            Log.w(TAG, "Could not destroy SecretKey.");
+        if (result == RESULT_CANNOT_INSERT_ROW) {
+            throw new RecoverableKeyStorageException(
+                    String.format(
+                            Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
         }
 
-        mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
-
-        try {
-            // Reload from the keystore, so that the caller is only provided with the handle of the
-            // key, not the raw key material.
-            return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(
-                    "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
-                            + "that has only just been stored in AndroidKeyStore.", e);
-        }
+        return key.getEncoded();
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
deleted file mode 100644
index 6a189ef..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
+++ /dev/null
@@ -1,80 +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.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
- *
- * @hide
- */
-public interface RecoverableKeyStorage {
-
-    /**
-     * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
-     *
-     * @throws IOException if an error occurred writing to disk.
-     *
-     * @hide
-     */
-    void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;
-
-    /**
-     * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
-     * the {@code alias}.
-     *
-     * @param alias The alias of the key.
-     * @param key The key.
-     * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
-     *                      Cipher modes, whether for encrpyt/decrypt or signing, etc.)
-     * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
-     *
-     * @hide
-     */
-    void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
-            KeyStoreException;
-
-    /**
-     * Loads a key handle from AndroidKeyStore.
-     *
-     * @param alias Alias of the key to load.
-     * @return The key handle.
-     * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
-            NoSuchAlgorithmException,
-            UnrecoverableEntryException;
-
-    /**
-     * Removes the entry with the given {@code alias} from AndroidKeyStore.
-     *
-     * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
new file mode 100644
index 0000000..f9d28f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when there was a problem writing or reading recoverable key information to or from
+ * storage.
+ *
+ * <p>Storage is typically
+ * {@link com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb} or
+ * AndroidKeyStore.
+ */
+public class RecoverableKeyStorageException extends Exception {
+    public RecoverableKeyStorageException(String message) {
+        super(message);
+    }
+
+    public RecoverableKeyStorageException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
deleted file mode 100644
index d4dede1..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
+++ /dev/null
@@ -1,116 +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.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link RecoverableKeyStorage} for a specific application.
- *
- * <p>Persists wrapped keys to disk, and loads raw keys into AndroidKeyStore.
- *
- * @hide
- */
-public class RecoverableKeyStorageImpl implements RecoverableKeyStorage {
-    private final KeyStore mKeyStore;
-
-    /**
-     * A new instance, storing recoverable keys for the given {@code userId}.
-     *
-     * @throws KeyStoreException if unable to load AndroidKeyStore.
-     * @throws NoSuchProviderException if AndroidKeyStore is not in this version of Android. Should
-     *     never occur.
-     *
-     * @hide
-     */
-    public static RecoverableKeyStorageImpl newInstance(int userId) throws KeyStoreException,
-            NoSuchProviderException {
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(userId);
-        return new RecoverableKeyStorageImpl(keyStore);
-    }
-
-    private RecoverableKeyStorageImpl(KeyStore keyStore) {
-        mKeyStore = keyStore;
-    }
-
-    /**
-     * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
-     *
-     * @throws IOException if an error occurred writing to disk.
-     *
-     * @hide
-     */
-    @Override
-    public void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException {
-        // TODO(robertberry) Add implementation.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Imports {@code key} into the application's AndroidKeyStore, keyed by {@code alias}.
-     *
-     * @param alias The alias of the key.
-     * @param key The key.
-     * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
-     *                      Cipher modes, whether for encrpyt/decrypt or signing, etc.)
-     * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection)
-            throws KeyStoreException {
-        mKeyStore.setEntry(alias, new KeyStore.SecretKeyEntry(key), keyProtection);
-    }
-
-    /**
-     * Loads a key handle from the application's AndroidKeyStore.
-     *
-     * @param alias Alias of the key to load.
-     * @return The key handle.
-     * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public SecretKey loadFromAndroidKeyStore(String alias)
-            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
-        return ((SecretKey) mKeyStore.getKey(alias, /*password=*/ null));
-    }
-
-    /**
-     * Removes the entry with the given {@code alias} from the application's AndroidKeyStore.
-     *
-     * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public void removeFromAndroidKeyStore(String alias) throws KeyStoreException {
-        mKeyStore.deleteEntry(alias);
-    }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
new file mode 100644
index 0000000..426dc8a
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -0,0 +1,506 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.crypto.AEADBadTagException;
+
+/**
+ * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
+ * with {@code LockSettingsService}.
+ *
+ * @hide
+ */
+public class RecoverableKeyStoreManager {
+    private static final String TAG = "RecoverableKeyStoreMgr";
+
+    private static final int ERROR_INSECURE_USER = 1;
+    private static final int ERROR_KEYSTORE_INTERNAL_ERROR = 2;
+    private static final int ERROR_DATABASE_ERROR = 3;
+
+    private static RecoverableKeyStoreManager mInstance;
+
+    private final Context mContext;
+    private final RecoverableKeyStoreDb mDatabase;
+    private final RecoverySessionStorage mRecoverySessionStorage;
+    private final ExecutorService mExecutorService;
+    private final ListenersStorage mListenersStorage;
+    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
+
+    /**
+     * Returns a new or existing instance.
+     *
+     * @hide
+     */
+    public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
+        if (mInstance == null) {
+            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+            mInstance = new RecoverableKeyStoreManager(
+                    mContext.getApplicationContext(),
+                    db,
+                    new RecoverySessionStorage(),
+                    Executors.newSingleThreadExecutor(),
+                    ListenersStorage.getInstance());
+        }
+        return mInstance;
+    }
+
+    @VisibleForTesting
+    RecoverableKeyStoreManager(
+            Context context,
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            RecoverySessionStorage recoverySessionStorage,
+            ExecutorService executorService,
+            ListenersStorage listenersStorage) {
+        mContext = context;
+        mDatabase = recoverableKeyStoreDb;
+        mRecoverySessionStorage = recoverySessionStorage;
+        mExecutorService = executorService;
+        mListenersStorage = listenersStorage;
+        try {
+            mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible: all AOSP implementations must support AES.
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void initRecoveryService(
+            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
+        PublicKey publicKey;
+        try {
+            KeyFactory kf = KeyFactory.getInstance("EC");
+            // TODO: Randomly choose a key from the list -- right now we just use the whole input
+            X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
+            publicKey = kf.generatePublic(pkSpec);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen
+            throw new RuntimeException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new RemoteException("Invalid public key for the recovery service");
+        }
+        mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
+    }
+
+    /**
+     * Gets all data necessary to recover application keys on new device.
+     *
+     * @return recovery data
+     * @hide
+     */
+    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
+        final int callingUserId = UserHandle.getCallingUserId();
+        final long callingIdentiy = Binder.clearCallingIdentity();
+        try {
+            // TODO: Return the latest snapshot for the calling recovery agent.
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentiy);
+        }
+
+        // KeyStoreRecoveryData without application keys and empty recovery blob.
+        KeyStoreRecoveryData recoveryData =
+                new KeyStoreRecoveryData(
+                        /*snapshotVersion=*/ 1,
+                        new ArrayList<KeyStoreRecoveryMetadata>(),
+                        new ArrayList<KeyEntryRecoveryData>(),
+                        /*encryptedRecoveryKeyBlob=*/ new byte[] {});
+        throw new ServiceSpecificException(
+                RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
+    }
+
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        final int recoveryAgentUid = Binder.getCallingUid();
+        mListenersStorage.setSnapshotListener(recoveryAgentUid, intent);
+    }
+
+    /**
+     * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
+     * keys, but it still needs to be synced, if previous versions were not empty.
+     *
+     * @return Map from Recovery agent account to snapshot version.
+     */
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+        checkRecoverKeyStorePermission();
+        mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
+    }
+
+    /**
+     * Updates recovery status for the application given its {@code packageName}.
+     *
+     * @param packageName which recoverable key statuses will be returned
+     * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
+     * @param status - new status
+     */
+    public void setRecoveryStatus(
+            @NonNull String packageName, @Nullable String[] aliases, int status, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        int uid = Binder.getCallingUid();
+        if (packageName != null) {
+            // TODO: get uid for package name, when many apps are supported.
+        }
+        if (aliases == null) {
+            // Get all keys for the app.
+            Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
+            aliases = new String[allKeys.size()];
+            allKeys.keySet().toArray(aliases);
+        }
+        for (String alias: aliases) {
+            mDatabase.setRecoveryStatus(uid, alias, status);
+        }
+    }
+
+    /**
+     * Gets recovery status for caller or other application {@code packageName}.
+     * @param packageName which recoverable keys statuses will be returned.
+     *
+     * @return {@code Map} from KeyStore alias to recovery status.
+     */
+    public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+            throws RemoteException {
+        // Any application should be able to check status for its own keys.
+        // If caller is a recovery agent it can check statuses for other packages, but
+        // only for recoverable keys it manages.
+        checkRecoverKeyStorePermission();
+        return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
+    }
+
+    /**
+     * Sets recovery secrets list used by all recovery agents for given {@code userId}
+     *
+     * @hide
+     */
+    public void setRecoverySecretTypes(
+            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Gets secret types necessary to create Recovery Data.
+     *
+     * @return secret types
+     * @hide
+     */
+    public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data.
+     *
+     * @return secret types
+     * @hide
+     */
+    public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    public void recoverySecretAvailable(
+            @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException {
+        final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
+        if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
+            throw new SecurityException(
+                    "Caller " + callingUid + "is not allowed to set lock screen secret");
+        }
+        checkRecoverKeyStorePermission();
+        // TODO: add hook from LockSettingsService to set lock screen secret.
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Initializes recovery session.
+     *
+     * @param sessionId A unique ID to identify the recovery session.
+     * @param verifierPublicKey X509-encoded public key.
+     * @param vaultParams Additional params associated with vault.
+     * @param vaultChallenge Challenge issued by vault service.
+     * @param secrets Lock-screen hashes. For now only a single secret is supported.
+     * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
+     *
+     * @hide
+     */
+    public @NonNull byte[] startRecoverySession(
+            @NonNull String sessionId,
+            @NonNull byte[] verifierPublicKey,
+            @NonNull byte[] vaultParams,
+            @NonNull byte[] vaultChallenge,
+            @NonNull List<KeyStoreRecoveryMetadata> secrets,
+            int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+
+        if (secrets.size() != 1) {
+            // TODO: support multiple secrets
+            throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported");
+        }
+
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] kfHash = secrets.get(0).getSecret();
+        mRecoverySessionStorage.add(
+                userId,
+                new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
+
+        try {
+            byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
+            PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
+            return KeySyncUtils.encryptRecoveryClaim(
+                    publicKey,
+                    vaultParams,
+                    vaultChallenge,
+                    thmKfHash,
+                    keyClaimant);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen: all the algorithms used are required by AOSP implementations.
+            throw new RemoteException(
+                    "Missing required algorithm",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        } catch (InvalidKeySpecException | InvalidKeyException e) {
+            throw new RemoteException(
+                    "Not a valid X509 key",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        }
+    }
+
+    /**
+     * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
+     * service.
+     *
+     * @param sessionId The session ID used to generate the claim. See
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}.
+     * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
+     *     service.
+     * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
+     *     were wrapped with the recovery key.
+     * @param uid The uid of the recovery agent.
+     * @return Map from alias to raw key material.
+     * @throws RemoteException if an error occurred recovering the keys.
+     */
+    public Map<String, byte[]> recoverKeys(
+            @NonNull String sessionId,
+            @NonNull byte[] encryptedRecoveryKey,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys,
+            int uid)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+
+        RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
+        if (sessionEntry == null) {
+            throw new RemoteException(String.format(Locale.US,
+                    "User %d does not have pending session '%s'", uid, sessionId));
+        }
+
+        try {
+            byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
+            return recoverApplicationKeys(recoveryKey, applicationKeys);
+        } finally {
+            sessionEntry.destroy();
+            mRecoverySessionStorage.remove(uid);
+        }
+    }
+
+    /**
+     * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
+     * returns the raw key material.
+     *
+     * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
+     *
+     * @hide
+     */
+    public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+
+        PlatformEncryptionKey encryptionKey;
+
+        try {
+            PlatformKeyManager platformKeyManager = PlatformKeyManager.getInstance(
+                    mContext, mDatabase, userId);
+            encryptionKey = platformKeyManager.getEncryptKey();
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible: all algorithms must be supported by AOSP
+            throw new RuntimeException(e);
+        } catch (KeyStoreException | UnrecoverableKeyException e) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
+        }
+
+        try {
+            return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
+        } catch (KeyStoreException | InvalidKeyException e) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+        } catch (RecoverableKeyStorageException e) {
+            throw new ServiceSpecificException(ERROR_DATABASE_ERROR, e.getMessage());
+        }
+    }
+
+    private byte[] decryptRecoveryKey(
+            RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
+            throws RemoteException {
+        try {
+            byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
+                    sessionEntry.getKeyClaimant(),
+                    sessionEntry.getVaultParams(),
+                    encryptedClaimResponse);
+            return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
+        } catch (InvalidKeyException | AEADBadTagException e) {
+            throw new RemoteException(
+                    "Failed to decrypt recovery key",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen: all the algorithms used are required by AOSP implementations
+            throw new RemoteException(
+                    "Missing required algorithm",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        }
+    }
+
+    /**
+     * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
+     *
+     * @return Map from alias to raw key material.
+     * @throws RemoteException if an error occurred decrypting the keys.
+     */
+    private Map<String, byte[]> recoverApplicationKeys(
+            @NonNull byte[] recoveryKey,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+        HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
+        for (KeyEntryRecoveryData applicationKey : applicationKeys) {
+            String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8);
+            byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
+
+            try {
+                byte[] keyMaterial =
+                        KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
+                keyMaterialByAlias.put(alias, keyMaterial);
+            } catch (NoSuchAlgorithmException e) {
+                // Should never happen: all the algorithms used are required by AOSP implementations
+                throw new RemoteException(
+                        "Missing required algorithm",
+                        e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+            } catch (InvalidKeyException | AEADBadTagException e) {
+                throw new RemoteException(
+                        "Failed to recover key with alias '" + alias + "'",
+                        e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+            }
+        }
+        return keyMaterialByAlias;
+    }
+
+    /**
+     * This function can only be used inside LockSettingsService.
+     *
+     * @param storedHashType from {@Code CredentialHash}
+     * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
+     *     mPasswordMaxLength}
+     * @param userId for user who just unlocked the device.
+     * @hide
+     */
+    public void lockScreenSecretAvailable(
+            int storedHashType, @NonNull String credential, int userId) {
+        // So as not to block the critical path unlocking the phone, defer to another thread.
+        try {
+            mExecutorService.execute(KeySyncTask.newInstance(
+                    mContext, mDatabase, userId, storedHashType, credential));
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
+        } catch (KeyStoreException e) {
+            Log.e(TAG, "Key store error encountered during recoverable key sync", e);
+        } catch (InsecureUserException e) {
+            Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
+        }
+    }
+
+    /** This function can only be used inside LockSettingsService. */
+    public void lockScreenSecretChanged(
+            @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
+            @Nullable String credential,
+            int userId) {
+        throw new UnsupportedOperationException();
+    }
+
+    private void checkRecoverKeyStorePermission() {
+        mContext.enforceCallingOrSelfPermission(
+                RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
+                "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 457fdc14..801d4de 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -16,24 +16,450 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import android.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
- * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.
+ * Implementation of the SecureBox v2 crypto functions.
+ *
+ * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
+ * credential types:
+ *
+ * <ul>
+ *   <li>A public key owned by the recipient,
+ *   <li>A secret shared between the sender and the recipient, or
+ *   <li>Both a recipient's public key and a shared secret.
+ * </ul>
  *
  * @hide
  */
 public class SecureBox {
+
+    private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
+    private static final byte[] HKDF_SALT =
+            concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+    private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
+            "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
+            "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] CONSTANT_01 = {(byte) 0x01};
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+    private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
+
+    private static final String CIPHER_ALG = "AES";
+    private static final String EC_ALG = "EC";
+    private static final String EC_P256_COMMON_NAME = "secp256r1";
+    private static final String EC_P256_OPENSSL_NAME = "prime256v1";
+    private static final String ENC_ALG = "AES/GCM/NoPadding";
+    private static final String KA_ALG = "ECDH";
+    private static final String MAC_ALG = "HmacSHA256";
+
+    private static final int EC_COORDINATE_LEN_BYTES = 32;
+    private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
+    private static final int GCM_NONCE_LEN_BYTES = 12;
+    private static final int GCM_KEY_LEN_BYTES = 16;
+    private static final int GCM_TAG_LEN_BYTES = 16;
+
+    private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
+
+    private enum AesGcmOperation {
+        ENCRYPT,
+        DECRYPT
+    }
+
+    // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
+    private static final BigInteger EC_PARAM_P =
+            new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+    private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
+    private static final BigInteger EC_PARAM_B =
+            new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+
+    @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
+
+    static {
+        EllipticCurve curveSpec =
+                new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
+        ECPoint generator =
+                new ECPoint(
+                        new BigInteger(
+                                "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+                                16),
+                        new BigInteger(
+                                "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+                                16));
+        BigInteger generatorOrder =
+                new BigInteger(
+                        "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+        EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
+    }
+
+    private SecureBox() {}
+
     /**
-     * TODO(b/69056040) Add implementation of encrypt.
+     * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
+     * {@link #decrypt}.
      *
+     * @return the randomly generated public-key pair
+     * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
+     * @hide
+     */
+    public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
+        try {
+            // Try using the OpenSSL provider first
+            keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
+            return keyPairGenerator.generateKeyPair();
+        } catch (InvalidAlgorithmParameterException ex) {
+            // Try another name for NIST P-256
+        }
+        try {
+            keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
+            return keyPairGenerator.generateKeyPair();
+        } catch (InvalidAlgorithmParameterException ex) {
+            throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
+        }
+    }
+
+    /**
+     * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
+     * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
+     * {@code sharedSecret} is equivalent to null.
+     *
+     * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
+     * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
+     *
+     * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
+     *     only with the shared secret
+     * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+     *     payload is to be encrypted only with the recipient's public key
+     * @param header the data that will be authenticated with {@code payload} but not encrypted, or
+     *     null if the data is empty
+     * @param payload the data to be encrypted, or null if the data is empty
+     * @return the encrypted payload
+     * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+     * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
      * @hide
      */
     public static byte[] encrypt(
-            PublicKey theirPublicKey, byte[] sharedSecret, byte[] header, byte[] payload)
+            @Nullable PublicKey theirPublicKey,
+            @Nullable byte[] sharedSecret,
+            @Nullable byte[] header,
+            @Nullable byte[] payload)
             throws NoSuchAlgorithmException, InvalidKeyException {
-        throw new UnsupportedOperationException("Needs to be implemented.");
+        sharedSecret = emptyByteArrayIfNull(sharedSecret);
+        if (theirPublicKey == null && sharedSecret.length == 0) {
+            throw new IllegalArgumentException("Both the public key and shared secret are empty");
+        }
+        header = emptyByteArrayIfNull(header);
+        payload = emptyByteArrayIfNull(payload);
+
+        KeyPair senderKeyPair;
+        byte[] dhSecret;
+        byte[] hkdfInfo;
+        if (theirPublicKey == null) {
+            senderKeyPair = null;
+            dhSecret = EMPTY_BYTE_ARRAY;
+            hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+        } else {
+            senderKeyPair = genKeyPair();
+            dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
+            hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+        }
+
+        byte[] randNonce = genRandomNonce();
+        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+        byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
+        if (senderKeyPair == null) {
+            return concat(VERSION, randNonce, ciphertext);
+        } else {
+            return concat(
+                    VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
+        }
+    }
+
+    /**
+     * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
+     * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
+     * {@code sharedSecret} is equivalent to null.
+     *
+     * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
+     * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
+     * AEADBadTagException} will be thrown.
+     *
+     * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
+     *     with the shared secret
+     * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+     *     payload was encrypted only with the recipient's public key
+     * @param header the data that was authenticated with the original payload but not encrypted, or
+     *     null if the data is empty
+     * @param encryptedPayload the data to be decrypted
+     * @return the original payload that was encrypted
+     * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+     * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+     * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
+     *     cannot be validated, or if the payload is not a valid SecureBox V2 payload.
+     * @hide
+     */
+    public static byte[] decrypt(
+            @Nullable PrivateKey ourPrivateKey,
+            @Nullable byte[] sharedSecret,
+            @Nullable byte[] header,
+            byte[] encryptedPayload)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        sharedSecret = emptyByteArrayIfNull(sharedSecret);
+        if (ourPrivateKey == null && sharedSecret.length == 0) {
+            throw new IllegalArgumentException("Both the private key and shared secret are empty");
+        }
+        header = emptyByteArrayIfNull(header);
+        if (encryptedPayload == null) {
+            throw new NullPointerException("Encrypted payload must not be null.");
+        }
+
+        ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
+        byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
+        if (!Arrays.equals(version, VERSION)) {
+            throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
+        }
+
+        byte[] senderPublicKeyBytes;
+        byte[] dhSecret;
+        byte[] hkdfInfo;
+        if (ourPrivateKey == null) {
+            dhSecret = EMPTY_BYTE_ARRAY;
+            hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+        } else {
+            senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
+            dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
+            hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+        }
+
+        byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
+        byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
+        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+        return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
+    }
+
+    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
+            throws AEADBadTagException {
+        byte[] output = new byte[length];
+        try {
+            buffer.get(output);
+        } catch (BufferUnderflowException ex) {
+            throw new AEADBadTagException("The encrypted payload is too short");
+        }
+        return output;
+    }
+
+    private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
+        try {
+            agreement.init(ourPrivateKey);
+        } catch (RuntimeException ex) {
+            // Rethrow the RuntimeException as InvalidKeyException
+            throw new InvalidKeyException(ex);
+        }
+        agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
+        return agreement.generateSecret();
+    }
+
+    /** Derives a 128-bit AES key. */
+    private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
+            throws NoSuchAlgorithmException {
+        Mac mac = Mac.getInstance(MAC_ALG);
+        try {
+            mac.init(new SecretKeySpec(salt, MAC_ALG));
+        } catch (InvalidKeyException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        byte[] pseudorandomKey = mac.doFinal(secret);
+
+        try {
+            mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
+        } catch (InvalidKeyException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        mac.update(info);
+        // Hashing just one block will yield 256 bits, which is enough to construct the AES key
+        byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
+
+        return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
+    }
+
+    private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        try {
+            return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
+        } catch (AEADBadTagException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
+    }
+
+    private static byte[] aesGcmInternal(
+            AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(ENC_ALG);
+        } catch (NoSuchPaddingException ex) {
+            // This should never happen because AES-GCM doesn't use padding
+            throw new RuntimeException(ex);
+        }
+        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
+        try {
+            if (operation == AesGcmOperation.DECRYPT) {
+                cipher.init(Cipher.DECRYPT_MODE, key, spec);
+            } else {
+                cipher.init(Cipher.ENCRYPT_MODE, key, spec);
+            }
+        } catch (InvalidAlgorithmParameterException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        try {
+            cipher.updateAAD(aad);
+            return cipher.doFinal(text);
+        } catch (AEADBadTagException ex) {
+            // Catch and rethrow AEADBadTagException first because it's a subclass of
+            // BadPaddingException
+            throw ex;
+        } catch (IllegalBlockSizeException | BadPaddingException ex) {
+            // This should never happen because AES-GCM can handle inputs of any length without
+            // padding
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @VisibleForTesting
+    static byte[] encodePublicKey(PublicKey publicKey) {
+        ECPoint point = ((ECPublicKey) publicKey).getW();
+        byte[] x = point.getAffineX().toByteArray();
+        byte[] y = point.getAffineY().toByteArray();
+
+        byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
+        // The order of arraycopy() is important, because the coordinates may have a one-byte
+        // leading 0 for the sign bit of two's complement form
+        System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
+        System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
+        output[0] = EC_PUBLIC_KEY_PREFIX;
+        return output;
+    }
+
+    @VisibleForTesting
+    static PublicKey decodePublicKey(byte[] keyBytes)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        BigInteger x =
+                new BigInteger(
+                        /*signum=*/ 1,
+                        Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
+        BigInteger y =
+                new BigInteger(
+                        /*signum=*/ 1,
+                        Arrays.copyOfRange(
+                                keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
+
+        // Checks if the point is indeed on the P-256 curve for security considerations
+        validateEcPoint(x, y);
+
+        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
+        try {
+            return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
+        } catch (InvalidKeySpecException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
+        if (x.compareTo(EC_PARAM_P) >= 0
+                || y.compareTo(EC_PARAM_P) >= 0
+                || x.signum() == -1
+                || y.signum() == -1) {
+            throw new InvalidKeyException("Point lies outside of the expected curve");
+        }
+
+        // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
+        BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
+        BigInteger rhs =
+                x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
+                        .add(EC_PARAM_A) // x^2 + a
+                        .mod(EC_PARAM_P) // This will speed up the next multiplication
+                        .multiply(x) // (x^2 + a) * x = x^3 + ax
+                        .add(EC_PARAM_B) // x^3 + ax + b
+                        .mod(EC_PARAM_P);
+        if (!lhs.equals(rhs)) {
+            throw new InvalidKeyException("Point lies outside of the expected curve");
+        }
+    }
+
+    private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
+        byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
+        new SecureRandom().nextBytes(nonce);
+        return nonce;
+    }
+
+    @VisibleForTesting
+    static byte[] concat(byte[]... inputs) {
+        int length = 0;
+        for (int i = 0; i < inputs.length; i++) {
+            if (inputs[i] == null) {
+                inputs[i] = EMPTY_BYTE_ARRAY;
+            }
+            length += inputs[i].length;
+        }
+
+        byte[] output = new byte[length];
+        int outputPos = 0;
+        for (byte[] input : inputs) {
+            System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
+            outputPos += input.length;
+        }
+        return output;
+    }
+
+    private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
+        return input == null ? EMPTY_BYTE_ARRAY : input;
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index 9002292..54aa9f0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings.recoverablekeystore;
 
 import android.util.Log;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -44,6 +45,8 @@
     private static final String APPLICATION_KEY_ALGORITHM = "AES";
     private static final int GCM_TAG_LENGTH_BITS = 128;
 
+    private final int mPlatformKeyGenerationId;
+    private final int mRecoveryStatus;
     private final byte[] mNonce;
     private final byte[] mKeyMaterial;
 
@@ -55,8 +58,8 @@
      *     {@link android.security.keystore.AndroidKeyStoreKey} for an example of a key that does
      *     not expose its key material.
      */
-    public static WrappedKey fromSecretKey(
-            SecretKey wrappingKey, SecretKey key) throws InvalidKeyException, KeyStoreException {
+    public static WrappedKey fromSecretKey(PlatformEncryptionKey wrappingKey, SecretKey key)
+            throws InvalidKeyException, KeyStoreException {
         if (key.getEncoded() == null) {
             throw new InvalidKeyException(
                     "key does not expose encoded material. It cannot be wrapped.");
@@ -70,7 +73,7 @@
                     "Android does not support AES/GCM/NoPadding. This should never happen.");
         }
 
-        cipher.init(Cipher.WRAP_MODE, wrappingKey);
+        cipher.init(Cipher.WRAP_MODE, wrappingKey.getKey());
         byte[] encryptedKeyMaterial;
         try {
             encryptedKeyMaterial = cipher.wrap(key);
@@ -90,7 +93,28 @@
             }
         }
 
-        return new WrappedKey(/*mNonce=*/ cipher.getIV(), /*mKeyMaterial=*/ encryptedKeyMaterial);
+        return new WrappedKey(
+                /*nonce=*/ cipher.getIV(),
+                /*keyMaterial=*/ encryptedKeyMaterial,
+                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
+                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+    }
+
+    /**
+     * A new instance with default recovery status.
+     *
+     * @param nonce The nonce with which the key material was encrypted.
+     * @param keyMaterial The encrypted bytes of the key material.
+     * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
+     *
+     * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS
+     * @hide
+     */
+    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
+        mNonce = nonce;
+        mKeyMaterial = keyMaterial;
+        mPlatformKeyGenerationId = platformKeyGenerationId;
+        mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS;
     }
 
     /**
@@ -98,12 +122,17 @@
      *
      * @param nonce The nonce with which the key material was encrypted.
      * @param keyMaterial The encrypted bytes of the key material.
+     * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
+     * @param recoveryStatus recovery status of the key.
      *
      * @hide
      */
-    public WrappedKey(byte[] nonce, byte[] keyMaterial) {
+    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId,
+            int recoveryStatus) {
         mNonce = nonce;
         mKeyMaterial = keyMaterial;
+        mPlatformKeyGenerationId = platformKeyGenerationId;
+        mRecoveryStatus = recoveryStatus;
     }
 
     /**
@@ -125,26 +154,57 @@
     }
 
     /**
+     * Returns the generation ID of the platform key, with which this key was wrapped.
+     *
+     * @hide
+     */
+    public int getPlatformKeyGenerationId() {
+        return mPlatformKeyGenerationId;
+    }
+
+    /**
+     * Returns recovery status of the key.
+     *
+     * @hide
+     */
+    public int getRecoveryStatus() {
+        return mRecoveryStatus;
+    }
+
+    /**
      * Unwraps the {@code wrappedKeys} with the {@code platformKey}.
      *
      * @return The unwrapped keys, indexed by alias.
      * @throws NoSuchAlgorithmException if AES/GCM/NoPadding Cipher or AES key type is unavailable.
+     * @throws BadPlatformKeyException if the {@code platformKey} has a different generation ID to
+     *     any of the {@code wrappedKeys}.
      *
      * @hide
      */
     public static Map<String, SecretKey> unwrapKeys(
-            SecretKey platformKey,
+            PlatformDecryptionKey platformKey,
             Map<String, WrappedKey> wrappedKeys)
-            throws NoSuchAlgorithmException, NoSuchPaddingException {
+            throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
         HashMap<String, SecretKey> unwrappedKeys = new HashMap<>();
         Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
+        int platformKeyGenerationId = platformKey.getGenerationId();
 
         for (String alias : wrappedKeys.keySet()) {
             WrappedKey wrappedKey = wrappedKeys.get(alias);
+            if (wrappedKey.getPlatformKeyGenerationId() != platformKeyGenerationId) {
+                throw new BadPlatformKeyException(String.format(
+                        Locale.US,
+                        "WrappedKey with alias '%s' was wrapped with platform key %d, not "
+                                + "platform key %d",
+                        alias,
+                        wrappedKey.getPlatformKeyGenerationId(),
+                        platformKey.getGenerationId()));
+            }
+
             try {
                 cipher.init(
                         Cipher.UNWRAP_MODE,
-                        platformKey,
+                        platformKey.getKey(),
                         new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
             } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
                 Log.e(TAG,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
new file mode 100644
index 0000000..d213115
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -0,0 +1,499 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.util.Log;
+
+import com.android.server.locksettings.recoverablekeystore.WrappedKey;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+
+
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Database of recoverable key information.
+ *
+ * @hide
+ */
+public class RecoverableKeyStoreDb {
+    private static final String TAG = "RecoverableKeyStoreDb";
+    private static final int IDLE_TIMEOUT_SECONDS = 30;
+    private static final int LAST_SYNCED_AT_UNSYNCED = -1;
+
+    private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
+
+    /**
+     * A new instance, storing the database in the user directory of {@code context}.
+     *
+     * @hide
+     */
+    public static RecoverableKeyStoreDb newInstance(Context context) {
+        RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context);
+        helper.setWriteAheadLoggingEnabled(true);
+        helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
+        return new RecoverableKeyStoreDb(helper);
+    }
+
+    private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
+        this.mKeyStoreDbHelper = keyStoreDbHelper;
+    }
+
+    /**
+     * Inserts a key into the database.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid Uid of the application to whom the key belongs.
+     * @param alias The alias of the key in the AndroidKeyStore.
+     * @param wrappedKey The wrapped key.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(KeysEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(KeysEntry.COLUMN_NAME_UID, uid);
+        values.put(KeysEntry.COLUMN_NAME_ALIAS, alias);
+        values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
+        values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
+        values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
+        values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
+        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus());
+        return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+    }
+
+    /**
+     * Gets the key with {@code alias} for the app with {@code uid}.
+     *
+     * @hide
+     */
+    @Nullable public WrappedKey getKey(int uid, String alias) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                KeysEntry._ID,
+                KeysEntry.COLUMN_NAME_NONCE,
+                KeysEntry.COLUMN_NAME_WRAPPED_KEY,
+                KeysEntry.COLUMN_NAME_GENERATION_ID,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
+        String selection =
+                KeysEntry.COLUMN_NAME_UID + " = ? AND "
+                + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
+        String[] selectionArguments = { Integer.toString(uid), alias };
+
+        try (
+            Cursor cursor = db.query(
+                KeysEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d WrappedKey entries found for uid=%d alias='%s'. "
+                                        + "Should only ever be 0 or 1.", count, uid, alias));
+                return null;
+            }
+            cursor.moveToFirst();
+            byte[] nonce = cursor.getBlob(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
+            byte[] keyMaterial = cursor.getBlob(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
+            int generationId = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
+            int recoveryStatus = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+            return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus);
+        }
+    }
+
+    /**
+     * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
+     *
+     * @param uid of the application
+     *
+     * @return Map from Aliases to status.
+     *
+     * @hide
+     */
+    public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                KeysEntry._ID,
+                KeysEntry.COLUMN_NAME_ALIAS,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
+        String selection =
+                KeysEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(uid)};
+
+        try (
+            Cursor cursor = db.query(
+                KeysEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            HashMap<String, Integer> statuses = new HashMap<>();
+            while (cursor.moveToNext()) {
+                String alias = cursor.getString(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
+                int recoveryStatus = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+                statuses.put(alias, recoveryStatus);
+            }
+            return statuses;
+        }
+    }
+
+    /**
+     * Updates status for given key.
+     * @param uid of the application
+     * @param alias of the key
+     * @param status - new status
+     * @return number of updated entries.
+     * @hide
+     **/
+    public int setRecoveryStatus(int uid, String alias, int status) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status);
+        String selection =
+                KeysEntry.COLUMN_NAME_UID + " = ? AND "
+                + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
+        return db.update(KeysEntry.TABLE_NAME, values, selection,
+            new String[] {String.valueOf(uid), alias});
+    }
+
+    /**
+     * Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}.
+     *
+     * @param userId User id of the profile to which all the keys are associated.
+     * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
+     *     (i.e., this should be the most recent generation ID, as older platform keys are not
+     *     usable.)
+     *
+     * @hide
+     */
+    public Map<String, WrappedKey> getAllKeys(int userId, int platformKeyGenerationId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                KeysEntry._ID,
+                KeysEntry.COLUMN_NAME_NONCE,
+                KeysEntry.COLUMN_NAME_WRAPPED_KEY,
+                KeysEntry.COLUMN_NAME_ALIAS,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
+        String selection =
+                KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
+        String[] selectionArguments = {
+                Integer.toString(userId), Integer.toString(platformKeyGenerationId) };
+
+        try (
+            Cursor cursor = db.query(
+                KeysEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            HashMap<String, WrappedKey> keys = new HashMap<>();
+            while (cursor.moveToNext()) {
+                byte[] nonce = cursor.getBlob(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
+                byte[] keyMaterial = cursor.getBlob(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
+                String alias = cursor.getString(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
+                int recoveryStatus = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId,
+                        recoveryStatus));
+            }
+            return keys;
+        }
+    }
+
+    /**
+     * Sets the {@code generationId} of the platform key for the account owned by {@code userId}.
+     *
+     * @return The primary key ID of the relation.
+     */
+    public long setPlatformKeyGenerationId(int userId, int generationId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
+        return db.replace(
+                UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+    }
+
+    /**
+     * Returns the generation ID associated with the platform key of the user with {@code userId}.
+     */
+    public int getPlatformKeyGenerationId(int userId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
+        String selection =
+                UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = {
+                Integer.toString(userId)};
+
+        try (
+            Cursor cursor = db.query(
+                UserMetadataEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            if (cursor.getCount() == 0) {
+                return -1;
+            }
+            cursor.moveToFirst();
+            return cursor.getInt(
+                    cursor.getColumnIndexOrThrow(
+                            UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
+        }
+    }
+
+    /**
+     * Updates the public key of the recovery service into the database.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid The uid of the application to whom the key belongs.
+     * @param publicKey The public key of the recovery service.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(
+                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+    }
+
+    /**
+     * Returns the public key of the recovery service.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid The uid of the application who initializes the local recovery components.
+     *
+     * @hide
+     */
+    public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+                Cursor cursor = db.query(
+                        RecoveryServiceMetadataEntry.TABLE_NAME,
+                        projection,
+                        selection,
+                        selectionArguments,
+                        /*groupBy=*/ null,
+                        /*having=*/ null,
+                        /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d PublicKey entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return null;
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(
+                    RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+            if (cursor.isNull(idx)) {
+                return null;
+            }
+            byte[] keyBytes = cursor.getBlob(idx);
+            X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(keyBytes);
+            try {
+                return KeyFactory.getInstance("EC").generatePublic(pkSpec);
+            } catch (NoSuchAlgorithmException e) {
+                // Should never happen
+                throw new RuntimeException(e);
+            } catch (InvalidKeySpecException e) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "Recovery service public key entry cannot be decoded for "
+                                        + "userId=%d uid=%d.",
+                                userId, uid));
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Updates the server parameters given by the application initializing the local recovery
+     * components.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param serverParameters The server parameters.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setServerParameters(int userId, int uid, long serverParameters) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(
+                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+    }
+
+    /**
+     * Returns the server paramters that was previously set by the application who initialized the
+     * local recovery service components.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return The server parameters that were previously set, or null if there's none.
+     *
+     * @hide
+     */
+    public Long getServerParameters(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+                Cursor cursor = db.query(
+                        RecoveryServiceMetadataEntry.TABLE_NAME,
+                        projection,
+                        selection,
+                        selectionArguments,
+                        /*groupBy=*/ null,
+                        /*having=*/ null,
+                        /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d deviceId entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return null;
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(
+                    RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS);
+            if (cursor.isNull(idx)) {
+                return null;
+            } else {
+                return cursor.getLong(idx);
+            }
+        }
+    }
+
+    /**
+     * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
+     * the given userId and uid, so db.update will succeed.
+     */
+    private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
+        db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+                values, SQLiteDatabase.CONFLICT_IGNORE);
+    }
+
+    /**
+     * Closes all open connections to the database.
+     */
+    public void close() {
+        mKeyStoreDbHelper.close();
+    }
+
+    // TODO: Add method for updating the 'last synced' time.
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
new file mode 100644
index 0000000..a232771
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -0,0 +1,116 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import android.provider.BaseColumns;
+
+/**
+ * Contract for recoverable key database. Describes the tables present.
+ */
+class RecoverableKeyStoreDbContract {
+    /**
+     * Table holding wrapped keys, and information about when they were last synced.
+     */
+    static class KeysEntry implements BaseColumns {
+        static final String TABLE_NAME = "keys";
+
+        /**
+         * The user id of the profile the application is running under.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * The uid of the application that generated the key.
+         */
+        static final String COLUMN_NAME_UID = "uid";
+
+        /**
+         * The alias of the key, as set in AndroidKeyStore.
+         */
+        static final String COLUMN_NAME_ALIAS = "alias";
+
+        /**
+         * Nonce with which the key was encrypted.
+         */
+        static final String COLUMN_NAME_NONCE = "nonce";
+
+        /**
+         * Encrypted bytes of the key.
+         */
+        static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key";
+
+        /**
+         * Generation ID of the platform key that was used to encrypt this key.
+         */
+        static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id";
+
+        /**
+         * Timestamp of when this key was last synced with remote storage, or -1 if never synced.
+         */
+        static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
+
+        /**
+         * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus}
+         */
+        static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status";
+    }
+
+    /**
+     * Recoverable KeyStore metadata for a specific user profile.
+     */
+    static class UserMetadataEntry implements BaseColumns {
+        static final String TABLE_NAME = "user_metadata";
+
+        /**
+         * User ID of the profile.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * Every time a new platform key is generated for a user, this increments. The platform key
+         * is used to wrap recoverable keys on disk.
+         */
+        static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
+    }
+
+    /**
+     * Table holding metadata of the recovery service.
+     */
+    static class RecoveryServiceMetadataEntry implements BaseColumns {
+        static final String TABLE_NAME = "recovery_service_metadata";
+
+        /**
+         * The user id of the profile the application is running under.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * The uid of the application that initializes the local recovery components.
+         */
+        static final String COLUMN_NAME_UID = "uid";
+
+        /**
+         * The public key of the recovery service.
+         */
+        static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
+
+        /**
+         * The server parameters of the recovery service.
+         */
+        static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
new file mode 100644
index 0000000..80fa20a
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -0,0 +1,76 @@
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+
+/**
+ * Helper for creating the recoverable key database.
+ */
+class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
+    private static final int DATABASE_VERSION = 1;
+    private static final String DATABASE_NAME = "recoverablekeystore.db";
+
+    private static final String SQL_CREATE_KEYS_ENTRY =
+            "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+                    + KeysEntry._ID + " INTEGER PRIMARY KEY,"
+                    + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+                    + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+                    + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+                    + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
+                    + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+                    + KeysEntry.COLUMN_NAME_ALIAS + "))";
+
+    private static final String SQL_CREATE_USER_METADATA_ENTRY =
+            "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+                    + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+                    + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+                    + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+    private static final String SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
+            "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+                    + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+                    + "UNIQUE("
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
+
+    private static final String SQL_DELETE_KEYS_ENTRY =
+            "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
+
+    private static final String SQL_DELETE_USER_METADATA_ENTRY =
+            "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
+
+    private static final String SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
+            "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
+
+    RecoverableKeyStoreDbHelper(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(SQL_CREATE_KEYS_ENTRY);
+        db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
+        db.execSQL(SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        db.execSQL(SQL_DELETE_KEYS_ENTRY);
+        db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+        db.execSQL(SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
+        onCreate(db);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
new file mode 100644
index 0000000..f7633e4
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -0,0 +1,184 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * Stores pending recovery sessions in memory. We do not write these to disk, as it contains hashes
+ * of the user's lock screen.
+ *
+ * @hide
+ */
+public class RecoverySessionStorage implements Destroyable {
+
+    private final SparseArray<ArrayList<Entry>> mSessionsByUid = new SparseArray<>();
+
+    /**
+     * Returns the session for the given user with the given id.
+     *
+     * @param uid The uid of the recovery agent who created the session.
+     * @param sessionId The unique identifier for the session.
+     * @return The session info.
+     *
+     * @hide
+     */
+    @Nullable
+    public Entry get(int uid, String sessionId) {
+        ArrayList<Entry> userEntries = mSessionsByUid.get(uid);
+        if (userEntries == null) {
+            return null;
+        }
+        for (Entry entry : userEntries) {
+            if (sessionId.equals(entry.mSessionId)) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a pending session for the given user.
+     *
+     * @param uid The uid of the recovery agent who created the session.
+     * @param entry The session info.
+     *
+     * @hide
+     */
+    public void add(int uid, Entry entry) {
+        if (mSessionsByUid.get(uid) == null) {
+            mSessionsByUid.put(uid, new ArrayList<>());
+        }
+        mSessionsByUid.get(uid).add(entry);
+    }
+
+    /**
+     * Removes all sessions associated with the given recovery agent uid.
+     *
+     * @param uid The uid of the recovery agent whose sessions to remove.
+     *
+     * @hide
+     */
+    public void remove(int uid) {
+        ArrayList<Entry> entries = mSessionsByUid.get(uid);
+        if (entries == null) {
+            return;
+        }
+        for (Entry entry : entries) {
+            entry.destroy();
+        }
+        mSessionsByUid.remove(uid);
+    }
+
+    /**
+     * Returns the total count of pending sessions.
+     *
+     * @hide
+     */
+    public int size() {
+        int size = 0;
+        int numberOfUsers = mSessionsByUid.size();
+        for (int i = 0; i < numberOfUsers; i++) {
+            ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+            size += entries.size();
+        }
+        return size;
+    }
+
+    /**
+     * Wipes the memory of any sensitive information (i.e., lock screen hashes and key claimants).
+     *
+     * @hide
+     */
+    @Override
+    public void destroy() {
+        int numberOfUids = mSessionsByUid.size();
+        for (int i = 0; i < numberOfUids; i++) {
+            ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+            for (Entry entry : entries) {
+                entry.destroy();
+            }
+        }
+    }
+
+    /**
+     * Information about a recovery session.
+     *
+     * @hide
+     */
+    public static class Entry implements Destroyable {
+        private final byte[] mLskfHash;
+        private final byte[] mKeyClaimant;
+        private final byte[] mVaultParams;
+        private final String mSessionId;
+
+        /**
+         * @hide
+         */
+        public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) {
+            mLskfHash = lskfHash;
+            mSessionId = sessionId;
+            mKeyClaimant = keyClaimant;
+            mVaultParams = vaultParams;
+        }
+
+        /**
+         * Returns the hash of the lock screen associated with the recovery attempt.
+         *
+         * @hide
+         */
+        public byte[] getLskfHash() {
+            return mLskfHash;
+        }
+
+        /**
+         * Returns the key generated for this recovery attempt (used to decrypt data returned by
+         * the server).
+         *
+         * @hide
+         */
+        public byte[] getKeyClaimant() {
+            return mKeyClaimant;
+        }
+
+        /**
+         * Returns the vault params associated with the session.
+         *
+         * @hide
+         */
+        public byte[] getVaultParams() {
+            return mVaultParams;
+        }
+
+        /**
+         * Overwrites the memory for the lskf hash and key claimant.
+         *
+         * @hide
+         */
+        @Override
+        public void destroy() {
+            Arrays.fill(mLskfHash, (byte) 0);
+            Arrays.fill(mKeyClaimant, (byte) 0);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 0b089fb..384efdd 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -21,6 +21,9 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -96,21 +99,23 @@
     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
             new ArrayMap<IBinder, ClientRecord>();
     private int mCurrentUserId = -1;
-    private boolean mGlobalBluetoothA2dpOn = false;
     private final IAudioService mAudioService;
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final Handler mHandler = new Handler();
-    private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
     private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
     private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
 
+    private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
+    BluetoothDevice mBluetoothDevice;
+    int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
+    boolean mGlobalBluetoothA2dpOn = false;
+
     public MediaRouterService(Context context) {
         mContext = context;
         Watchdog.getInstance().addMonitor(this);
 
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
-
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
                 new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
@@ -170,44 +175,30 @@
                 @Override
                 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
                     synchronized (mLock) {
-                        if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
+                        if (newRoutes.mainType != mAudioRouteMainType) {
                             if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
                                     | AudioRoutesInfo.MAIN_HEADPHONES
                                     | AudioRoutesInfo.MAIN_USB)) == 0) {
                                 // headset was plugged out.
-                                mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null;
+                                mGlobalBluetoothA2dpOn = mBluetoothDevice != null;
                             } else {
                                 // headset was plugged in.
                                 mGlobalBluetoothA2dpOn = false;
                             }
-                            mAudioRoutesInfo.mainType = newRoutes.mainType;
+                            mAudioRouteMainType = newRoutes.mainType;
                         }
-                        if (!TextUtils.equals(
-                                newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
-                            if (newRoutes.bluetoothName == null) {
-                                // BT was disconnected.
-                                mGlobalBluetoothA2dpOn = false;
-                            } else {
-                                // BT was connected or changed.
-                                mGlobalBluetoothA2dpOn = true;
-                            }
-                            mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
-                        }
-                        // Although a Bluetooth device is connected before a new audio playback is
-                        // started, dispatchAudioRoutChanged() can be called after
-                        // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
-                        // is called before mGlobalBluetoothA2dpOn is updated.
-                        // Calling restoreBluetoothA2dp() here could prevent that.
-                        restoreBluetoothA2dp();
+                        // The new audio routes info could be delivered with several seconds delay.
+                        // In order to avoid such delay, Bluetooth device info will be updated
+                        // via MediaRouterServiceBroadcastReceiver.
                     }
                 }
             });
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in the audio service.");
         }
-        synchronized (mLock) {
-            mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null);
-        }
+
+        IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
     }
 
     public void systemRunning() {
@@ -415,14 +406,12 @@
 
     void restoreBluetoothA2dp() {
         try {
-            boolean btConnected = false;
             boolean a2dpOn = false;
             synchronized (mLock) {
-                btConnected = mAudioRoutesInfo.bluetoothName != null;
                 a2dpOn = mGlobalBluetoothA2dpOn;
             }
             // We don't need to change a2dp status when bluetooth is not connected.
-            if (btConnected) {
+            if (mBluetoothDevice != null) {
                 Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
                 mAudioService.setBluetoothA2dpOn(a2dpOn);
             }
@@ -661,6 +650,25 @@
         return false;
     }
 
+    final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                        BluetoothProfile.STATE_DISCONNECTED);
+                if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                    mGlobalBluetoothA2dpOn = false;
+                    mBluetoothDevice = null;
+                } else if (state == BluetoothProfile.STATE_CONNECTED) {
+                    mGlobalBluetoothA2dpOn = true;
+                    mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    // To ensure that BT A2DP is on, call restoreBluetoothA2dp().
+                    restoreBluetoothA2dp();
+                }
+            }
+        }
+    }
+
     /**
      * Information about a particular client of the media router.
      * The contents of this object is guarded by mLock.
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 3af5265..db61ef5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -873,6 +873,21 @@
         }
     }
 
+    @Override
+    public long getUidStats(int uid, int type) {
+        return nativeGetUidStat(uid, type);
+    }
+
+    @Override
+    public long getIfaceStats(String iface, int type) {
+        return nativeGetIfaceStat(iface, type);
+    }
+
+    @Override
+    public long getTotalStats(int type) {
+        return nativeGetTotalStat(type);
+    }
+
     /**
      * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
      * reflect current {@link #mPersistThreshold} value. Always defers to
@@ -1626,4 +1641,15 @@
             return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
         }
     }
+
+    private static int TYPE_RX_BYTES;
+    private static int TYPE_RX_PACKETS;
+    private static int TYPE_TX_BYTES;
+    private static int TYPE_TX_PACKETS;
+    private static int TYPE_TCP_RX_PACKETS;
+    private static int TYPE_TCP_TX_PACKETS;
+
+    private static native long nativeGetTotalStat(int type);
+    private static native long nativeGetIfaceStat(String iface, int type);
+    private static native long nativeGetUidStat(int uid, int type);
 }
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 5b3d1ec..2a2ff06 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -178,14 +178,21 @@
             }
         }
 
+        /** Currently MasterClearConfirm will call isOemUnlockAllowed()
+         * to sync PersistentDataBlockOemUnlockAllowedBit which
+         * is needed before factory reset
+         * TODO: Figure out better place to run sync e.g. adding new API
+         */
         @Override
         public boolean isOemUnlockAllowed() {
             enforceOemUnlockReadPermission();
 
             final long token = Binder.clearCallingIdentity();
             try {
-                return mOemLock.isOemUnlockAllowedByCarrier() &&
-                        mOemLock.isOemUnlockAllowedByDevice();
+                boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
+                        && mOemLock.isOemUnlockAllowedByDevice();
+                setPersistentDataBlockOemUnlockAllowedBit(allowed);
+                return allowed;
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -213,7 +220,8 @@
         final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
                 mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
         // if mOemLock is PersistentDataBlockLock, then the bit should have already been set
-        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)
+                && pdbm.getOemUnlockEnabled() != allowed) {
             Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
             pdbm.setOemUnlockEnabled(allowed);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 03cd4f1..768eb8f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,6 +27,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
@@ -38,6 +39,7 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -386,7 +388,7 @@
     /**
      * Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
      *
-     * @see {@link #trySetQuietModeDisabled(int, IntentSender)}
+     * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
      */
     private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
         private final IntentSender mTarget;
@@ -784,48 +786,114 @@
     }
 
     @Override
-    public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) {
-        checkManageUsersPermission("silence profile");
-        boolean changed = false;
-        UserInfo profile, parent;
-        synchronized (mPackagesLock) {
-            synchronized (mUsersLock) {
-                profile = getUserInfoLU(userHandle);
-                parent = getProfileParentLU(userHandle);
+    public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+            int userHandle, @Nullable IntentSender target) {
+        Preconditions.checkNotNull(callingPackage);
 
+        if (enableQuietMode && target != null) {
+            throw new IllegalArgumentException(
+                    "target should only be specified when we are disabling quiet mode.");
+        }
+
+        if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
+            throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
+                    + "caller is foreground default launcher "
+                    + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (enableQuietMode) {
+                setQuietModeEnabled(userHandle, true /* enableQuietMode */, target);
+                return true;
+            } else {
+                boolean needToShowConfirmCredential =
+                        mLockPatternUtils.isSecure(userHandle)
+                                && !StorageManager.isUserKeyUnlocked(userHandle);
+                if (needToShowConfirmCredential) {
+                    showConfirmCredentialToDisableQuietMode(userHandle, target);
+                    return false;
+                } else {
+                    setQuietModeEnabled(userHandle, false /* enableQuietMode */, target);
+                    return true;
+                }
             }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * An app can modify quiet mode if the caller meets one of the condition:
+     * <ul>
+     *     <li>Has system UID or root UID</li>
+     *     <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
+     *     <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
+     * </ul>
+     */
+    private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+        if (hasManageUsersPermission()) {
+            return true;
+        }
+
+        final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
+                Manifest.permission.MODIFY_QUIET_MODE,
+                callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+        if (hasModifyQuietModePermission) {
+            return true;
+        }
+
+        final ShortcutServiceInternal shortcutInternal =
+                LocalServices.getService(ShortcutServiceInternal.class);
+        if (shortcutInternal != null) {
+            boolean isForegroundLauncher =
+                    shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
+            if (isForegroundLauncher) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void setQuietModeEnabled(
+            int userHandle, boolean enableQuietMode, IntentSender target) {
+        final UserInfo profile, parent;
+        final UserData profileUserData;
+        synchronized (mUsersLock) {
+            profile = getUserInfoLU(userHandle);
+            parent = getProfileParentLU(userHandle);
+
             if (profile == null || !profile.isManagedProfile()) {
                 throw new IllegalArgumentException("User " + userHandle + " is not a profile");
             }
-            if (profile.isQuietModeEnabled() != enableQuietMode) {
-                profile.flags ^= UserInfo.FLAG_QUIET_MODE;
-                writeUserLP(getUserDataLU(profile.id));
-                changed = true;
+            if (profile.isQuietModeEnabled() == enableQuietMode) {
+                Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
+                return;
             }
+            profile.flags ^= UserInfo.FLAG_QUIET_MODE;
+            profileUserData = getUserDataLU(profile.id);
         }
-        if (changed) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                if (enableQuietMode) {
-                    ActivityManager.getService().stopUser(userHandle, /* force */true, null);
-                    LocalServices.getService(ActivityManagerInternal.class)
-                            .killForegroundAppsForUser(userHandle);
-                } else {
-                    IProgressListener callback = target != null
-                            ? new DisableQuietModeUserUnlockedCallback(target)
-                            : null;
-                    ActivityManager.getService().startUserInBackgroundWithListener(
-                            userHandle, callback);
-                }
-            } catch (RemoteException e) {
-                Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+        synchronized (mPackagesLock) {
+            writeUserLP(profileUserData);
+        }
+        try {
+            if (enableQuietMode) {
+                ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .killForegroundAppsForUser(userHandle);
+            } else {
+                IProgressListener callback = target != null
+                        ? new DisableQuietModeUserUnlockedCallback(target)
+                        : null;
+                ActivityManager.getService().startUserInBackgroundWithListener(
+                        userHandle, callback);
             }
-
-            broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
-                    enableQuietMode);
+        } catch (RemoteException e) {
+            // Should not happen, same process.
+            e.rethrowAsRuntimeException();
         }
+        broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
+                enableQuietMode);
     }
 
     @Override
@@ -842,54 +910,42 @@
         }
     }
 
-    @Override
-    public boolean trySetQuietModeDisabled(
+    /**
+     * Show confirm credential screen to unlock user in order to turn off quiet mode.
+     */
+    private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userHandle, @Nullable IntentSender target) {
-        checkManageUsersPermission("silence profile");
-        if (StorageManager.isUserKeyUnlocked(userHandle)
-                || !mLockPatternUtils.isSecure(userHandle)) {
-            // if the user is already unlocked, no need to show a profile challenge
-            setQuietModeEnabled(userHandle, false, target);
-            return true;
+        // otherwise, we show a profile challenge to trigger decryption of the user
+        final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
+                Context.KEYGUARD_SERVICE);
+        // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+        // lock, confirm screenlock page will know and show personal challenge, and unlock
+        // work profile when personal challenge is correct
+        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
+                userHandle);
+        if (unlockIntent == null) {
+            return;
         }
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            // otherwise, we show a profile challenge to trigger decryption of the user
-            final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
-                    Context.KEYGUARD_SERVICE);
-            // We should use userHandle not credentialOwnerUserId here, as even if it is unified
-            // lock, confirm screenlock page will know and show personal challenge, and unlock
-            // work profile when personal challenge is correct
-            final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
-                    userHandle);
-            if (unlockIntent == null) {
-                return false;
-            }
-            final Intent callBackIntent = new Intent(
-                    ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
-            if (target != null) {
-                callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
-            }
-            callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
-            callBackIntent.setPackage(mContext.getPackageName());
-            callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                    mContext,
-                    0,
-                    callBackIntent,
-                    PendingIntent.FLAG_CANCEL_CURRENT |
-                            PendingIntent.FLAG_ONE_SHOT |
-                            PendingIntent.FLAG_IMMUTABLE);
-            // After unlocking the challenge, it will disable quiet mode and run the original
-            // intentSender
-            unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
-            unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            mContext.startActivity(unlockIntent);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        final Intent callBackIntent = new Intent(
+                ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
+        if (target != null) {
+            callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        return false;
+        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+        callBackIntent.setPackage(mContext.getPackageName());
+        callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext,
+                0,
+                callBackIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT |
+                        PendingIntent.FLAG_ONE_SHOT |
+                        PendingIntent.FLAG_IMMUTABLE);
+        // After unlocking the challenge, it will disable quiet mode and run the original
+        // intentSender
+        unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
+        unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        mContext.startActivity(unlockIntent);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4564988..9240843 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -115,6 +115,8 @@
             UserManager.DISALLOW_AUTOFILL,
             UserManager.DISALLOW_USER_SWITCH,
             UserManager.DISALLOW_UNIFIED_PASSWORD,
+            UserManager.DISALLOW_CONFIG_LOCATION_MODE,
+            UserManager.DISALLOW_AIRPLANE_MODE
     });
 
     /**
@@ -142,7 +144,8 @@
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.DISALLOW_CREATE_WINDOWS,
-            UserManager.DISALLOW_DATA_ROAMING
+            UserManager.DISALLOW_DATA_ROAMING,
+            UserManager.DISALLOW_AIRPLANE_MODE
     );
 
     /**
@@ -197,7 +200,8 @@
      * Special user restrictions that are always applied to all users no matter who sets them.
      */
     private static final Set<String> PROFILE_GLOBAL_RESTRICTIONS = Sets.newArraySet(
-            UserManager.ENSURE_VERIFY_APPS
+            UserManager.ENSURE_VERIFY_APPS,
+            UserManager.DISALLOW_AIRPLANE_MODE
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
index 854b704..d6281c5 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -74,8 +75,6 @@
     public void startActivityAsUser(
             String callingPackage,
             ComponentName component,
-            Rect sourceBounds,
-            Bundle startActivityOptions,
             UserHandle user) throws RemoteException {
         Preconditions.checkNotNull(callingPackage);
         Preconditions.checkNotNull(component);
@@ -103,7 +102,6 @@
         // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
         final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
         launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        launchIntent.setSourceBounds(sourceBounds);
         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         // Only package name is set here, as opposed to component name, because intent action and
@@ -114,7 +112,8 @@
         final long ident = mInjector.clearCallingIdentity();
         try {
             launchIntent.setComponent(component);
-            mContext.startActivityAsUser(launchIntent, startActivityOptions, user);
+            mContext.startActivityAsUser(launchIntent,
+                    ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user);
         } finally {
             mInjector.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index c40d1fa..01f3c57 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -640,9 +640,9 @@
             if (globalSearchPickerPackage != null
                     && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
                 grantRuntimePermissions(globalSearchPickerPackage,
-                    MICROPHONE_PERMISSIONS, true, userId);
+                    MICROPHONE_PERMISSIONS, false, userId);
                 grantRuntimePermissions(globalSearchPickerPackage,
-                    LOCATION_PERMISSIONS, true, userId);
+                    LOCATION_PERMISSIONS, false, userId);
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9752a57..076c0e4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2126,14 +2126,14 @@
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
             public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                    IBinder closeToken,
-                    Animation openAnimation, Animation closeAnimation) {
-                return handleStartTransitionForKeyguardLw(transit, openAnimation);
+                    IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                    long statusBarAnimationDuration) {
+                return handleStartTransitionForKeyguardLw(transit, duration);
             }
 
             @Override
             public void onAppTransitionCancelledLocked(int transit) {
-                handleStartTransitionForKeyguardLw(transit, null /* transit */);
+                handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
             }
         });
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
@@ -3989,7 +3989,7 @@
         }
     }
 
-    private int handleStartTransitionForKeyguardLw(int transit, @Nullable Animation anim) {
+    private int handleStartTransitionForKeyguardLw(int transit, long duration) {
         if (mKeyguardOccludedChanged) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                     + mPendingKeyguardOccluded);
@@ -4000,13 +4000,7 @@
         }
         if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
-            final long startTime = anim != null
-                    ? SystemClock.uptimeMillis() + anim.getStartOffset()
-                    : SystemClock.uptimeMillis();
-            final long duration = anim != null
-                    ? anim.getDuration()
-                    : 0;
-            startKeyguardExitAnimation(startTime, duration);
+            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
         return 0;
     }
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index af7e91c..e6e4d7f 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -37,8 +37,6 @@
  */
 public class StatusBarController extends BarController {
 
-    private static final long TRANSITION_DURATION = 120L;
-
     private final AppTransitionListener mAppTransitionListener
             = new AppTransitionListener() {
 
@@ -57,17 +55,15 @@
 
         @Override
         public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                IBinder closeToken, final Animation openAnimation, final Animation closeAnimation) {
+                IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                long statusBarAnimationDuration) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     StatusBarManagerInternal statusbar = getStatusBarInternal();
                     if (statusbar != null) {
-                        long startTime = calculateStatusBarTransitionStartTime(openAnimation,
-                                closeAnimation);
-                        long duration = closeAnimation != null || openAnimation != null
-                                ? TRANSITION_DURATION : 0;
-                        statusbar.appTransitionStarting(startTime, duration);
+                        statusbar.appTransitionStarting(statusBarAnimationStartTime,
+                                statusBarAnimationDuration);
                     }
                 }
             });
@@ -128,72 +124,4 @@
     public AppTransitionListener getAppTransitionListener() {
         return mAppTransitionListener;
     }
-
-    /**
-     * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
-     * calculates the timings for the corresponding status bar transition.
-     *
-     * @return the desired start time of the status bar transition, in uptime millis
-     */
-    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
-            Animation closeAnimation) {
-        if (openAnimation != null && closeAnimation != null) {
-            TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
-            TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
-            if (openTranslateAnimation != null) {
-
-                // Some interpolators are extremely quickly mostly finished, but not completely. For
-                // our purposes, we need to find the fraction for which ther interpolator is mostly
-                // there, and use that value for the calculation.
-                float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
-                return SystemClock.uptimeMillis()
-                        + openTranslateAnimation.getStartOffset()
-                        + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
-            } else if (closeTranslateAnimation != null) {
-                return SystemClock.uptimeMillis();
-            } else {
-                return SystemClock.uptimeMillis();
-            }
-        } else {
-            return SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
-     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
-     *
-     * @return the found animation, {@code null} otherwise
-     */
-    private static TranslateAnimation findTranslateAnimation(Animation animation) {
-        if (animation instanceof TranslateAnimation) {
-            return (TranslateAnimation) animation;
-        } else if (animation instanceof AnimationSet) {
-            AnimationSet set = (AnimationSet) animation;
-            for (int i = 0; i < set.getAnimations().size(); i++) {
-                Animation a = set.getAnimations().get(i);
-                if (a instanceof TranslateAnimation) {
-                    return (TranslateAnimation) a;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
-     * {@code interpolator(t + eps) > 0.99}.
-     */
-    private static float findAlmostThereFraction(Interpolator interpolator) {
-        float val = 0.5f;
-        float adj = 0.25f;
-        while (adj >= 0.01f) {
-            if (interpolator.getInterpolation(val) < 0.99f) {
-                val += adj;
-            } else {
-                val -= adj;
-            }
-            adj /= 2;
-        }
-        return val;
-    }
 }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 8ee26f29..e5a23ea 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -16,6 +16,7 @@
 
 package com.android.server.power;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 
@@ -27,6 +28,7 @@
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 
+import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -82,6 +84,7 @@
     private static final int MSG_BROADCAST = 2;
     private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
     private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4;
+    private static final int MSG_PROFILE_TIMED_OUT = 5;
 
     private final Object mLock = new Object();
 
@@ -93,6 +96,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final TrustManager mTrustManager;
 
     private final NotifierHandler mHandler;
     private final Intent mScreenOnIntent;
@@ -138,6 +142,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+        mTrustManager = mContext.getSystemService(TrustManager.class);
 
         mHandler = new NotifierHandler(looper);
         mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
@@ -559,6 +564,16 @@
         mHandler.sendMessage(msg);
     }
 
+    /**
+     * Called when profile screen lock timeout has expired.
+     */
+    public void onProfileTimeout(@UserIdInt int userId) {
+        final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
+        msg.setAsynchronous(true);
+        msg.arg1 = userId;
+        mHandler.sendMessage(msg);
+    }
+
     private void updatePendingBroadcastLocked() {
         if (!mBroadcastInProgress
                 && mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN
@@ -710,28 +725,33 @@
         mSuspendBlocker.release();
     }
 
+    private void lockProfile(@UserIdInt int userId) {
+        mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
+    }
+
     private final class NotifierHandler extends Handler {
+
         public NotifierHandler(Looper looper) {
             super(looper, null, true /*async*/);
         }
-
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_USER_ACTIVITY:
                     sendUserActivity();
                     break;
-
                 case MSG_BROADCAST:
                     sendNextBroadcast();
                     break;
-
                 case MSG_WIRELESS_CHARGING_STARTED:
                     playWirelessChargingStartedSound();
                     break;
                 case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
                     sendBrightnessBoostChangedBroadcast();
                     break;
+                case MSG_PROFILE_TIMED_OUT:
+                    lockProfile(msg.arg1);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7f1a534..02c8f68 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,9 +16,10 @@
 
 package com.android.server.power;
 
-import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -410,12 +411,12 @@
     private boolean mDozeAfterScreenOffConfig;
 
     // The minimum screen off timeout, in milliseconds.
-    private int mMinimumScreenOffTimeoutConfig;
+    private long mMinimumScreenOffTimeoutConfig;
 
     // The screen dim duration, in milliseconds.
     // This is subtracted from the end of the screen off timeout so the
     // minimum screen off timeout should be longer than this.
-    private int mMaximumScreenDimDurationConfig;
+    private long mMaximumScreenDimDurationConfig;
 
     // The maximum screen dim time expressed as a ratio relative to the screen
     // off timeout.  If the screen off timeout is very short then we want the
@@ -427,14 +428,14 @@
     private boolean mSupportsDoubleTapWakeConfig;
 
     // The screen off timeout setting value in milliseconds.
-    private int mScreenOffTimeoutSetting;
+    private long mScreenOffTimeoutSetting;
 
     // The sleep timeout setting value in milliseconds.
-    private int mSleepTimeoutSetting;
+    private long mSleepTimeoutSetting;
 
     // The maximum allowable screen off timeout according to the device
     // administration policy.  Overrides other settings.
-    private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE;
+    private long mMaximumScreenOffTimeoutFromDeviceAdmin = Long.MAX_VALUE;
 
     // The stay on while plugged in setting.
     // A bitfield of battery conditions under which to make the screen stay on.
@@ -555,6 +556,46 @@
     // True if we are currently in VR Mode.
     private boolean mIsVrModeEnabled;
 
+    private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
+        @Override
+        public void onUserSwitching(int newUserId) throws RemoteException {}
+
+        @Override
+        public void onForegroundProfileSwitch(@UserIdInt int newProfileId) throws RemoteException {
+            final long now = SystemClock.uptimeMillis();
+            synchronized(mLock) {
+                mForegroundProfile = newProfileId;
+                maybeUpdateForegroundProfileLastActivityLocked(now);
+            }
+        }
+    }
+
+    // User id corresponding to activity the user is currently interacting with.
+    private @UserIdInt int mForegroundProfile;
+
+    // Per-profile state to track when a profile should be locked.
+    private final SparseArray<ProfilePowerState> mProfilePowerState = new SparseArray<>();
+
+    private static final class ProfilePowerState {
+        // Profile user id.
+        final @UserIdInt int mUserId;
+        // Maximum time to lock set by admin.
+        long mScreenOffTimeout;
+        // Like top-level mWakeLockSummary, but only for wake locks that affect current profile.
+        int mWakeLockSummary;
+        // Last user activity that happened in an app running in the profile.
+        long mLastUserActivityTime;
+        // Whether profile has been locked last time it timed out.
+        boolean mLockingNotified;
+
+        public ProfilePowerState(@UserIdInt int userId, long screenOffTimeout) {
+            mUserId = userId;
+            mScreenOffTimeout = screenOffTimeout;
+            // Not accurate but at least won't cause immediate locking of the profile.
+            mLastUserActivityTime = SystemClock.uptimeMillis();
+        }
+    }
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -752,6 +793,12 @@
             mDisplayManagerInternal.initPowerManagement(
                     mDisplayPowerCallbacks, mHandler, sensorManager);
 
+            try {
+                final ForegroundProfileObserver observer = new ForegroundProfileObserver();
+                ActivityManager.getService().registerUserSwitchObserver(observer, TAG);
+            } catch (RemoteException e) {
+                // Shouldn't happen since in-process.
+            }
 
             // Go.
             readConfigurationLocked();
@@ -1333,6 +1380,8 @@
                 return false;
             }
 
+            maybeUpdateForegroundProfileLastActivityLocked(eventTime);
+
             if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
                 if (eventTime > mLastUserActivityTimeNoChangeLights
                         && eventTime > mLastUserActivityTime) {
@@ -1360,6 +1409,13 @@
         return false;
     }
 
+    private void maybeUpdateForegroundProfileLastActivityLocked(long eventTime) {
+        final ProfilePowerState profile = mProfilePowerState.get(mForegroundProfile);
+        if (profile != null && eventTime > profile.mLastUserActivityTime) {
+            profile.mLastUserActivityTime = eventTime;
+        }
+    }
+
     private void wakeUpInternal(long eventTime, String reason, int uid, String opPackageName,
             int opUid) {
         synchronized (mLock) {
@@ -1648,16 +1704,19 @@
                 }
             }
 
-            // Phase 2: Update display power state.
-            boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
+            // Phase 2: Lock profiles that became inactive/not kept awake.
+            updateProfilesLocked(now);
 
-            // Phase 3: Update dream state (depends on display ready signal).
+            // Phase 3: Update display power state.
+            final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
+
+            // Phase 4: Update dream state (depends on display ready signal).
             updateDreamLocked(dirtyPhase2, displayBecameReady);
 
-            // Phase 4: Send notifications, if needed.
+            // Phase 5: Send notifications, if needed.
             finishWakefulnessChangeIfNeededLocked();
 
-            // Phase 5: Update suspend blocker.
+            // Phase 6: Update suspend blocker.
             // Because we might release the last suspend blocker here, we need to make sure
             // we finished everything else first!
             updateSuspendBlockerLocked();
@@ -1667,6 +1726,29 @@
     }
 
     /**
+     * Check profile timeouts and notify profiles that should be locked.
+     */
+    private void updateProfilesLocked(long now) {
+        final int numProfiles = mProfilePowerState.size();
+        for (int i = 0; i < numProfiles; i++) {
+            final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+            if (isProfileBeingKeptAwakeLocked(profile, now)) {
+                profile.mLockingNotified = false;
+            } else if (!profile.mLockingNotified) {
+                profile.mLockingNotified = true;
+                mNotifier.onProfileTimeout(profile.mUserId);
+            }
+        }
+    }
+
+    private boolean isProfileBeingKeptAwakeLocked(ProfilePowerState profile, long now) {
+        return (profile.mLastUserActivityTime + profile.mScreenOffTimeout > now)
+                || (profile.mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0
+                || (mProximityPositive &&
+                    (profile.mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0);
+    }
+
+    /**
      * Updates the value of mIsPowered.
      * Sets DIRTY_IS_POWERED if a change occurred.
      */
@@ -1800,60 +1882,28 @@
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) {
             mWakeLockSummary = 0;
 
+            final int numProfiles = mProfilePowerState.size();
+            for (int i = 0; i < numProfiles; i++) {
+                mProfilePowerState.valueAt(i).mWakeLockSummary = 0;
+            }
+
             final int numWakeLocks = mWakeLocks.size();
             for (int i = 0; i < numWakeLocks; i++) {
                 final WakeLock wakeLock = mWakeLocks.get(i);
-                switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
-                    case PowerManager.PARTIAL_WAKE_LOCK:
-                        if (!wakeLock.mDisabled) {
-                            // We only respect this if the wake lock is not disabled.
-                            mWakeLockSummary |= WAKE_LOCK_CPU;
-                        }
-                        break;
-                    case PowerManager.FULL_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
-                        break;
-                    case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT;
-                        break;
-                    case PowerManager.SCREEN_DIM_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_SCREEN_DIM;
-                        break;
-                    case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF;
-                        break;
-                    case PowerManager.DOZE_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_DOZE;
-                        break;
-                    case PowerManager.DRAW_WAKE_LOCK:
-                        mWakeLockSummary |= WAKE_LOCK_DRAW;
-                        break;
+                final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock);
+                mWakeLockSummary |= wakeLockFlags;
+                for (int j = 0; j < numProfiles; j++) {
+                    final ProfilePowerState profile = mProfilePowerState.valueAt(j);
+                    if (wakeLockAffectsUser(wakeLock, profile.mUserId)) {
+                        profile.mWakeLockSummary |= wakeLockFlags;
+                    }
                 }
             }
 
-            // Cancel wake locks that make no sense based on the current state.
-            if (mWakefulness != WAKEFULNESS_DOZING) {
-                mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
-            }
-            if (mWakefulness == WAKEFULNESS_ASLEEP
-                    || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
-                mWakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
-                        | WAKE_LOCK_BUTTON_BRIGHT);
-                if (mWakefulness == WAKEFULNESS_ASLEEP) {
-                    mWakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
-                }
-            }
-
-            // Infer implied wake locks where necessary based on the current state.
-            if ((mWakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
-                if (mWakefulness == WAKEFULNESS_AWAKE) {
-                    mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
-                } else if (mWakefulness == WAKEFULNESS_DREAMING) {
-                    mWakeLockSummary |= WAKE_LOCK_CPU;
-                }
-            }
-            if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
-                mWakeLockSummary |= WAKE_LOCK_CPU;
+            mWakeLockSummary = adjustWakeLockSummaryLocked(mWakeLockSummary);
+            for (int i = 0; i < numProfiles; i++) {
+                final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+                profile.mWakeLockSummary = adjustWakeLockSummaryLocked(profile.mWakeLockSummary);
             }
 
             if (DEBUG_SPEW) {
@@ -1864,6 +1914,72 @@
         }
     }
 
+    private int adjustWakeLockSummaryLocked(int wakeLockSummary) {
+        // Cancel wake locks that make no sense based on the current state.
+        if (mWakefulness != WAKEFULNESS_DOZING) {
+            wakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
+        }
+        if (mWakefulness == WAKEFULNESS_ASLEEP
+                || (wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
+            wakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
+                    | WAKE_LOCK_BUTTON_BRIGHT);
+            if (mWakefulness == WAKEFULNESS_ASLEEP) {
+                wakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
+            }
+        }
+
+        // Infer implied wake locks where necessary based on the current state.
+        if ((wakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
+            if (mWakefulness == WAKEFULNESS_AWAKE) {
+                wakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
+            } else if (mWakefulness == WAKEFULNESS_DREAMING) {
+                wakeLockSummary |= WAKE_LOCK_CPU;
+            }
+        }
+        if ((wakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+            wakeLockSummary |= WAKE_LOCK_CPU;
+        }
+
+        return wakeLockSummary;
+    }
+
+    /** Get wake lock summary flags that correspond to the given wake lock. */
+    private int getWakeLockSummaryFlags(WakeLock wakeLock) {
+        switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+            case PowerManager.PARTIAL_WAKE_LOCK:
+                if (!wakeLock.mDisabled) {
+                    // We only respect this if the wake lock is not disabled.
+                    return WAKE_LOCK_CPU;
+                }
+                break;
+            case PowerManager.FULL_WAKE_LOCK:
+                return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
+            case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                return WAKE_LOCK_SCREEN_BRIGHT;
+            case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                return WAKE_LOCK_SCREEN_DIM;
+            case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                return WAKE_LOCK_PROXIMITY_SCREEN_OFF;
+            case PowerManager.DOZE_WAKE_LOCK:
+                return WAKE_LOCK_DOZE;
+            case PowerManager.DRAW_WAKE_LOCK:
+                return WAKE_LOCK_DRAW;
+        }
+        return 0;
+    }
+
+    private boolean wakeLockAffectsUser(WakeLock wakeLock, @UserIdInt int userId) {
+        if (wakeLock.mWorkSource != null) {
+            for (int k = 0; k < wakeLock.mWorkSource.size(); k++) {
+                final int uid = wakeLock.mWorkSource.get(k);
+                if (userId == UserHandle.getUserId(uid)) {
+                    return true;
+                }
+            }
+        }
+        return userId == UserHandle.getUserId(wakeLock.mOwnerUid);
+    }
+
     void checkForLongWakeLocks() {
         synchronized (mLock) {
             final long now = SystemClock.uptimeMillis();
@@ -1917,10 +2033,11 @@
             if (mWakefulness == WAKEFULNESS_AWAKE
                     || mWakefulness == WAKEFULNESS_DREAMING
                     || mWakefulness == WAKEFULNESS_DOZING) {
-                final int sleepTimeout = getSleepTimeoutLocked();
-                final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
-                final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+                final long sleepTimeout = getSleepTimeoutLocked();
+                final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+                final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
                 final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
+                final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
 
                 mUserActivitySummary = 0;
                 if (mLastUserActivityTime >= mLastWakeTime) {
@@ -1977,10 +2094,12 @@
                     nextTimeout = -1;
                 }
 
+                if (nextProfileTimeout > 0) {
+                    nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
+                }
+
                 if (mUserActivitySummary != 0 && nextTimeout >= 0) {
-                    Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
-                    msg.setAsynchronous(true);
-                    mHandler.sendMessageAtTime(msg, nextTimeout);
+                    scheduleUserInactivityTimeout(nextTimeout);
                 }
             } else {
                 mUserActivitySummary = 0;
@@ -1995,6 +2114,28 @@
         }
     }
 
+    private void scheduleUserInactivityTimeout(long timeMs) {
+        final Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
+        msg.setAsynchronous(true);
+        mHandler.sendMessageAtTime(msg, timeMs);
+    }
+
+    /**
+     * Finds the next profile timeout time or returns -1 if there are no profiles to be locked.
+     */
+    private long getNextProfileTimeoutLocked(long now) {
+        long nextTimeout = -1;
+        final int numProfiles = mProfilePowerState.size();
+        for (int i = 0; i < numProfiles; i++) {
+            final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+            final long timeout = profile.mLastUserActivityTime + profile.mScreenOffTimeout;
+            if (timeout > now && (nextTimeout == -1 || timeout < nextTimeout)) {
+                nextTimeout = timeout;
+            }
+        }
+        return nextTimeout;
+    }
+
     /**
      * Called when a user activity timeout has occurred.
      * Simply indicates that something about user activity has changed so that the new
@@ -2014,21 +2155,21 @@
         }
     }
 
-    private int getSleepTimeoutLocked() {
-        int timeout = mSleepTimeoutSetting;
+    private long getSleepTimeoutLocked() {
+        final long timeout = mSleepTimeoutSetting;
         if (timeout <= 0) {
             return -1;
         }
         return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
     }
 
-    private int getScreenOffTimeoutLocked(int sleepTimeout) {
-        int timeout = mScreenOffTimeoutSetting;
+    private long getScreenOffTimeoutLocked(long sleepTimeout) {
+        long timeout = mScreenOffTimeoutSetting;
         if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
             timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
         }
         if (mUserActivityTimeoutOverrideFromWindowManager >= 0) {
-            timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
+            timeout = Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
         }
         if (sleepTimeout >= 0) {
             timeout = Math.min(timeout, sleepTimeout);
@@ -2036,9 +2177,9 @@
         return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
     }
 
-    private int getScreenDimDurationLocked(int screenOffTimeout) {
+    private long getScreenDimDurationLocked(long screenOffTimeout) {
         return Math.min(mMaximumScreenDimDurationConfig,
-                (int)(screenOffTimeout * mMaximumScreenDimRatioConfig));
+                (long)(screenOffTimeout * mMaximumScreenDimRatioConfig));
     }
 
     /**
@@ -2781,9 +2922,27 @@
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val);
     }
 
-    void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) {
+    void setMaximumScreenOffTimeoutFromDeviceAdminInternal(@UserIdInt int userId, long timeMs) {
+        if (userId < 0) {
+            Slog.wtf(TAG, "Attempt to set screen off timeout for invalid user: " + userId);
+            return;
+        }
         synchronized (mLock) {
-            mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs;
+            // System-wide timeout
+            if (userId == UserHandle.USER_SYSTEM) {
+                mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs;
+            } else if (timeMs == Long.MAX_VALUE || timeMs == 0) {
+                mProfilePowerState.delete(userId);
+            } else {
+                final ProfilePowerState profile = mProfilePowerState.get(userId);
+                if (profile != null) {
+                    profile.mScreenOffTimeout = timeMs;
+                } else {
+                    mProfilePowerState.put(userId, new ProfilePowerState(userId, timeMs));
+                    // We need to recalculate wake locks for the new profile state.
+                    mDirty |= DIRTY_WAKE_LOCKS;
+                }
+            }
             mDirty |= DIRTY_SETTINGS;
             updatePowerStateLocked();
         }
@@ -2981,7 +3140,7 @@
 
     private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() {
         return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0
-                && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE;
+                && mMaximumScreenOffTimeoutFromDeviceAdmin < Long.MAX_VALUE;
     }
 
     private void setAttentionLightInternal(boolean on, int color) {
@@ -3325,10 +3484,11 @@
             pw.println("  mScreenBrightnessForVrSetting=" + mScreenBrightnessForVrSetting);
             pw.println("  mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
             pw.println("  mIsVrModeEnabled=" + mIsVrModeEnabled);
+            pw.println("  mForegroundProfile=" + mForegroundProfile);
 
-            final int sleepTimeout = getSleepTimeoutLocked();
-            final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
-            final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+            final long sleepTimeout = getSleepTimeoutLocked();
+            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+            final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
             pw.println();
             pw.println("Sleep timeout: " + sleepTimeout + " ms");
             pw.println("Screen off timeout: " + screenOffTimeout + " ms");
@@ -3373,6 +3533,23 @@
 
             mBatterySaverPolicy.dump(pw);
 
+            pw.println();
+            final int numProfiles = mProfilePowerState.size();
+            pw.println("Profile power states: size=" + numProfiles);
+            for (int i = 0; i < numProfiles; i++) {
+                final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+                pw.print("  mUserId=");
+                pw.print(profile.mUserId);
+                pw.print(" mScreenOffTimeout=");
+                pw.print(profile.mScreenOffTimeout);
+                pw.print(" mWakeLockSummary=");
+                pw.print(profile.mWakeLockSummary);
+                pw.print(" mLastUserActivityTime=");
+                pw.print(profile.mLastUserActivityTime);
+                pw.print(" mLockingNotified=");
+                pw.println(profile.mLockingNotified);
+            }
+
             wcd = mWirelessChargerDetector;
         }
 
@@ -3590,7 +3767,8 @@
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .MAXIMUM_SCREEN_OFF_TIMEOUT_FROM_DEVICE_ADMIN_MS,
-                    mMaximumScreenOffTimeoutFromDeviceAdmin);
+                    // Clamp to int32
+                    Math.min(mMaximumScreenOffTimeoutFromDeviceAdmin, Integer.MAX_VALUE));
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .IS_MAXIMUM_SCREEN_OFF_TIMEOUT_FROM_DEVICE_ADMIN_ENFORCED_LOCKED,
@@ -3686,9 +3864,9 @@
                     mIsVrModeEnabled);
             proto.end(settingsAndConfigurationToken);
 
-            final int sleepTimeout = getSleepTimeoutLocked();
-            final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
-            final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+            final long sleepTimeout = getSleepTimeoutLocked();
+            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+            final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
             proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
             proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
             proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
@@ -4146,7 +4324,7 @@
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.DEVICE_POWER, null);
             }
-            if (ws != null && ws.size() != 0) {
+            if (ws != null && !ws.isEmpty()) {
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS, null);
             } else {
@@ -4201,7 +4379,7 @@
             }
 
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
-            if (ws != null && ws.size() != 0) {
+            if (ws != null && !ws.isEmpty()) {
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS, null);
             } else {
@@ -4697,8 +4875,8 @@
         }
 
         @Override
-        public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) {
-            setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs);
+        public void setMaximumScreenOffTimeoutFromDeviceAdmin(@UserIdInt int userId, long timeMs) {
+            setMaximumScreenOffTimeoutFromDeviceAdminInternal(userId, timeMs);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 1ce1400..b31f4b3 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -455,7 +455,7 @@
             Slog.d(TAG, "Pulling " + tagId);
 
         switch (tagId) {
-            case StatsLog.WIFI_BYTES_TRANSFERRED: {
+            case StatsLog.WIFI_BYTES_TRANSFER: {
                 long token = Binder.clearCallingIdentity();
                 try {
                     // TODO: Consider caching the following call to get BatteryStatsInternal.
@@ -476,7 +476,7 @@
                 }
                 break;
             }
-            case StatsLog.MOBILE_BYTES_TRANSFERRED: {
+            case StatsLog.MOBILE_BYTES_TRANSFER: {
                 long token = Binder.clearCallingIdentity();
                 try {
                     BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
@@ -496,7 +496,7 @@
                 }
                 break;
             }
-            case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: {
+            case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
                 long token = Binder.clearCallingIdentity();
                 try {
                     BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
@@ -516,7 +516,7 @@
                 }
                 break;
             }
-            case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: {
+            case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
                 long token = Binder.clearCallingIdentity();
                 try {
                     BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
@@ -536,7 +536,7 @@
                 }
                 break;
             }
-            case StatsLog.KERNEL_WAKELOCK_PULLED: {
+            case StatsLog.KERNEL_WAKELOCK: {
                 final KernelWakelockStats wakelockStats =
                         mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
                 List<StatsLogEventWrapper> ret = new ArrayList();
@@ -552,7 +552,7 @@
                 }
                 return ret.toArray(new StatsLogEventWrapper[ret.size()]);
             }
-            case StatsLog.CPU_TIME_PER_FREQ_PULLED: {
+            case StatsLog.CPU_TIME_PER_FREQ: {
                 List<StatsLogEventWrapper> ret = new ArrayList();
                 for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
                     long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
@@ -568,7 +568,7 @@
                 }
                 return ret.toArray(new StatsLogEventWrapper[ret.size()]);
             }
-            case StatsLog.WIFI_ACTIVITY_ENERGY_INFO_PULLED: {
+            case StatsLog.WIFI_ACTIVITY_ENERGY_INFO: {
                 List<StatsLogEventWrapper> ret = new ArrayList();
                 long token = Binder.clearCallingIdentity();
                 if (mWifiManager == null) {
@@ -596,7 +596,7 @@
                 }
                 break;
             }
-            case StatsLog.MODEM_ACTIVITY_INFO_PULLED: {
+            case StatsLog.MODEM_ACTIVITY_INFO: {
                 List<StatsLogEventWrapper> ret = new ArrayList();
                 long token = Binder.clearCallingIdentity();
                 if (mTelephony == null) {
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 1382894..ca0a450 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -512,7 +512,7 @@
                 } else {
                     mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null);
                 }
-                final long maxTimeToLock = dpm.getMaximumTimeToLockForUserAndProfiles(mUserId);
+                final long maxTimeToLock = dpm.getMaximumTimeToLock(null, mUserId);
                 if (maxTimeToLock != mMaximumTimeToLock) {
                     // If the timeout changes, cancel the alarm and send a timeout event to have
                     // the agent re-evaluate trust.
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index 7b1e12e..35b6ad3 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -59,13 +59,6 @@
             int userId, int processId, @NonNull ComponentName calling);
 
     /**
-     * Set whether the system has acquired a sleep token.
-     *
-     * @param isAsleep is {@code true} if the device is asleep, or {@code false} otherwise.
-     */
-    public abstract void onSleepStateChanged(boolean isAsleep);
-
-    /**
      * Set whether the display used for VR output is on.
      *
      * @param isScreenOn is {@code true} if the display is on and can receive commands,
@@ -74,13 +67,6 @@
     public abstract void onScreenStateChanged(boolean isScreenOn);
 
     /**
-     * Set whether the keyguard is currently active/showing.
-     *
-     * @param isShowing is {@code true} if the keyguard is active/showing.
-     */
-    public abstract void onKeyguardStateChanged(boolean isShowing);
-
-    /**
      * Return NO_ERROR if the given package is installed on the device and enabled as a
      * VrListenerService for the given current user, or a negative error code indicating a failure.
      *
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 7d55b68..de723c6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.ScreenObserver;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.INotificationManager;
@@ -58,6 +59,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+
+import com.android.server.FgThread;
 import com.android.server.wm.WindowManagerInternal;
 import android.view.inputmethod.InputMethodManagerInternal;
 
@@ -105,7 +108,8 @@
  *
  * @hide
  */
-public class VrManagerService extends SystemService implements EnabledComponentChangeListener{
+public class VrManagerService extends SystemService
+        implements EnabledComponentChangeListener, ScreenObserver {
 
     public static final String TAG = "VrManagerService";
     static final boolean DBG = false;
@@ -237,15 +241,17 @@
         }
     }
 
-    private void setSleepState(boolean isAsleep) {
-        setSystemState(FLAG_AWAKE, !isAsleep);
-    }
-
     private void setScreenOn(boolean isScreenOn) {
         setSystemState(FLAG_SCREEN_ON, isScreenOn);
     }
 
-    private void setKeyguardShowing(boolean isShowing) {
+    @Override
+    public void onAwakeStateChanged(boolean isAwake) {
+        setSystemState(FLAG_AWAKE, isAwake);
+    }
+
+    @Override
+    public void onKeyguardStateChanged(boolean isShowing) {
         setSystemState(FLAG_KEYGUARD_UNLOCKED, !isShowing);
     }
 
@@ -706,21 +712,11 @@
         }
 
         @Override
-        public void onSleepStateChanged(boolean isAsleep) {
-            VrManagerService.this.setSleepState(isAsleep);
-        }
-
-        @Override
         public void onScreenStateChanged(boolean isScreenOn) {
             VrManagerService.this.setScreenOn(isScreenOn);
         }
 
         @Override
-        public void onKeyguardStateChanged(boolean isShowing) {
-            VrManagerService.this.setKeyguardShowing(isShowing);
-        }
-
-        @Override
         public boolean isCurrentVrListener(String packageName, int userId) {
             return VrManagerService.this.isCurrentVrListener(packageName, userId);
         }
@@ -773,6 +769,9 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            LocalServices.getService(ActivityManagerInternal.class)
+                    .registerScreenObserver(this);
+
             mNotificationManager = INotificationManager.Stub.asInterface(
                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
             synchronized (mLock) {
@@ -828,9 +827,11 @@
 
     @Override
     public void onSwitchUser(int userHandle) {
-        synchronized (mLock) {
-            mComponentObserver.onUsersChanged();
-        }
+        FgThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                mComponentObserver.onUsersChanged();
+            }
+        });
 
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7b0ed0d..8e916ad 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -675,6 +675,7 @@
 
     final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>();
     int mCurrentUserId;
+    boolean mInAmbientMode;
 
     static class WallpaperData {
 
@@ -953,6 +954,13 @@
                     }
                     mPaddingChanged = false;
                 }
+                if (mInfo != null && mInfo.getSupportsAmbientMode()) {
+                    try {
+                        mEngine.setInAmbientMode(mInAmbientMode);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to set ambient mode state", e);
+                    }
+                }
                 try {
                     // This will trigger onComputeColors in the wallpaper engine.
                     // It's fine to be locked in here since the binder is oneway.
@@ -1743,6 +1751,28 @@
         }
     }
 
+    public void setInAmbientMode(boolean inAmbienMode) {
+        final IWallpaperEngine engine;
+        synchronized (mLock) {
+            mInAmbientMode = inAmbienMode;
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null && data.connection != null && data.connection.mInfo != null
+                    && data.connection.mInfo.getSupportsAmbientMode()) {
+                engine = data.connection.mEngine;
+            } else {
+                engine = null;
+            }
+        }
+
+        if (engine != null) {
+            try {
+                engine.setInAmbientMode(inAmbienMode);
+            } catch (RemoteException e) {
+                // Cannot talk to wallpaper engine.
+            }
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2bda80d..163b160 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 
@@ -1068,8 +1069,11 @@
                         continue;
                     }
 
-                    // If the window is not touchable - ignore.
-                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                    // Ignore non-touchable windows, except the split-screen divider, which is
+                    // occasionally non-touchable but still useful for identifying split-screen
+                    // mode.
+                    if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                            && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
                         continue;
                     }
 
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 84d47b4..ed4543e 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -30,6 +30,8 @@
  */
 interface AnimationAdapter {
 
+    long STATUS_BAR_TRANSITION_DURATION = 120L;
+
     /**
      * @return Whether we should detach the wallpaper during the animation.
      * @see Animation#setDetachWallpaper
@@ -66,4 +68,13 @@
      * @return The approximate duration of the animation, in milliseconds.
      */
     long getDurationHint();
+
+    /**
+     * If this animation is run as an app opening animation, this calculates the start time for all
+     * status bar transitions that happen as part of the app opening animation, which will be
+     * forwarded to SystemUI.
+     *
+     * @return the desired start time of the status bar transition, in uptime millis
+     */
+    long getStatusBarTransitionsStartTime();
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c2cbced..2ac7583 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -38,23 +38,22 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.proto.AppTransitionProto.APP_TRANSITION_STATE;
 import static com.android.server.wm.proto.AppTransitionProto.LAST_USED_APP_TRANSITION;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -63,7 +62,9 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -199,6 +200,13 @@
     private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
     private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
     private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
+
+    /**
+     * Refers to the transition to activity started by using {@link
+     * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+     * }.
+     */
+    private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
 
     // These are the possible states for the enter/exit activities during a thumbnail transition
@@ -407,17 +415,23 @@
      * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
      *         layout pass needs to be done
      */
-    int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator,
-            AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps,
+    int goodToGo(int transit, AppWindowToken topOpeningApp,
+            AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
             ArraySet<AppWindowToken> closingApps) {
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
+        final AnimationAdapter topOpeningAnim = topOpeningApp != null
+                ? topOpeningApp.getAnimation()
+                : null;
         int redoLayout = notifyAppTransitionStartingLocked(transit,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
+                topOpeningApp != null ? topOpeningApp.token : null,
+                topClosingApp != null ? topClosingApp.token : null,
+                topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
+                topOpeningAnim != null
+                        ? topOpeningAnim.getStatusBarTransitionsStartTime()
+                        : SystemClock.uptimeMillis(),
+                AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         mService.getDefaultDisplayContentLocked().getDockedDividerController()
                 .notifyAppTransitionStarting(openingApps, transit);
 
@@ -425,8 +439,8 @@
         // ended it already then we don't need to wait.
         if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) {
             for (int i = openingApps.size() - 1; i >= 0; i--) {
-                final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator;
-                appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+                final AppWindowToken app = openingApps.valueAt(i);
+                app.startDelayingAnimationStart();
             }
         }
         return redoLayout;
@@ -496,11 +510,12 @@
     }
 
     private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
-            IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+            IBinder closeToken, long duration, long statusBarAnimationStartTime,
+            long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
             redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
-                    closeToken, openAnimation, closeAnimation);
+                    closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
         }
         return redoLayout;
     }
@@ -1605,6 +1620,17 @@
                         + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                         + " Callers=" + Debug.getCallers(3));
             }
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS
+                && (transit == TRANSIT_ACTIVITY_OPEN
+                        || transit == TRANSIT_TASK_OPEN
+                        || transit == TRANSIT_TASK_TO_FRONT)) {
+            a = loadAnimationRes("android", enter
+                    ? com.android.internal.R.anim.activity_open_enter
+                    : com.android.internal.R.anim.activity_open_exit);
+            Slog.v(TAG,
+                    "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
+                            + " anim=" + a + " transit=" + appTransitionToString(transit)
+                            + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
         } else {
             int animAttr = 0;
             switch (transit) {
@@ -1833,6 +1859,17 @@
     }
 
     /**
+     * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
+     */
+    void overridePendingAppTransitionStartCrossProfileApps() {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
+            postAnimationCallback();
+        }
+    }
+
+    /**
      * If a future is set for the app transition specs, fetch it in another thread.
      */
     private void fetchAppTransitionSpecsFromFuture() {
@@ -1855,9 +1892,6 @@
                             mNextAppTransitionFutureCallback, null /* finishedCallback */,
                             mNextAppTransitionScaleUp);
                     mNextAppTransitionFutureCallback = null;
-                    if (specs != null) {
-                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
-                    }
                 }
                 mService.requestTraversal();
             });
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
deleted file mode 100644
index 5c1d5b2..0000000
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright (C) 2014 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 com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
-
-import android.graphics.Matrix;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class AppWindowAnimator {
-    static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM;
-
-    private static final int PROLONG_ANIMATION_DISABLED = 0;
-    static final int PROLONG_ANIMATION_AT_END = 1;
-    static final int PROLONG_ANIMATION_AT_START = 2;
-
-    final AppWindowToken mAppToken;
-    final WindowManagerService mService;
-    final WindowAnimator mAnimator;
-
-    boolean animating;
-    boolean wasAnimating;
-    Animation animation;
-    boolean hasTransformation;
-    final Transformation transformation = new Transformation();
-
-    // Have we been asked to have this token keep the screen frozen?
-    // Protect with mAnimator.
-    boolean freezingScreen;
-
-    /**
-     * How long we last kept the screen frozen.
-     */
-    int lastFreezeDuration;
-
-    // Offset to the window of all layers in the token, for use by
-    // AppWindowToken animations.
-    int animLayerAdjustment;
-
-    // Propagated from AppWindowToken.allDrawn, to determine when
-    // the state changes.
-    boolean allDrawn;
-
-    // Special surface for thumbnail animation.  If deferThumbnailDestruction is enabled, then we
-    // will make sure that the thumbnail is destroyed after the other surface is completed.  This
-    // requires that the duration of the two animations are the same.
-    SurfaceControl thumbnail;
-    int thumbnailTransactionSeq;
-    private int mThumbnailLayer;
-
-    Animation thumbnailAnimation;
-    final Transformation thumbnailTransformation = new Transformation();
-    // This flag indicates that the destruction of the thumbnail surface is synchronized with
-    // another animation, so defer the destruction of this thumbnail surface for a single frame
-    // after the secondary animation completes.
-    boolean deferThumbnailDestruction;
-    // This flag is set if the animator has deferThumbnailDestruction set and has reached the final
-    // frame of animation.  It will extend the animation by one frame and then clean up afterwards.
-    boolean deferFinalFrameCleanup;
-    // If true when the animation hits the last frame, it will keep running on that last frame.
-    // This is used to synchronize animation with Recents and we wait for Recents to tell us to
-    // finish or for a new animation be set as fail-safe mechanism.
-    private int mProlongAnimation;
-    // Whether the prolong animation can be removed when animation is set. The purpose of this is
-    // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
-    // when new animation is set.
-    private boolean mClearProlongedAnimation;
-    private int mTransit;
-    private int mTransitFlags;
-
-    /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */
-    ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<>();
-
-    /** True if the current animation was transferred from another AppWindowAnimator.
-     *  See {@link #transferCurrentAnimation}*/
-    boolean usingTransferredAnimation = false;
-
-    private boolean mSkipFirstFrame = false;
-    private int mStackClip = STACK_CLIP_BEFORE_ANIM;
-
-    static final Animation sDummyAnimation = new DummyAnimation();
-
-    public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) {
-        mAppToken = atoken;
-        mService = service;
-        mAnimator = mService.mAnimator;
-    }
-
-    public void setAnimation(Animation anim, int width, int height, int parentWidth,
-            int parentHeight, boolean skipFirstFrame, int stackClip, int transit,
-            int transitFlags) {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
-                + ": " + anim + " wxh=" + width + "x" + height
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = anim;
-        animating = false;
-        if (!anim.isInitialized()) {
-            anim.initialize(width, height, parentWidth, parentHeight);
-        }
-        anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-        int zorder = anim.getZAdjustment();
-        int adj = 0;
-        if (zorder == Animation.ZORDER_TOP) {
-            adj = TYPE_LAYER_OFFSET;
-        } else if (zorder == Animation.ZORDER_BOTTOM) {
-            adj = -TYPE_LAYER_OFFSET;
-        }
-
-        if (animLayerAdjustment != adj) {
-            animLayerAdjustment = adj;
-            updateLayers();
-        }
-        // Start out animation gone if window is gone, or visible if window is visible.
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-        hasTransformation = true;
-        mStackClip = stackClip;
-
-        mSkipFirstFrame = skipFirstFrame;
-        mTransit = transit;
-        mTransitFlags = transitFlags;
-
-        if (!mAppToken.fillsParent()) {
-            anim.setBackgroundColor(0);
-        }
-        if (mClearProlongedAnimation) {
-            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-        } else {
-            mClearProlongedAnimation = true;
-        }
-    }
-
-    public void setDummyAnimation() {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = sDummyAnimation;
-        hasTransformation = true;
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-    }
-
-    void setNullAnimation() {
-        animation = null;
-        usingTransferredAnimation = false;
-    }
-
-    public void clearAnimation() {
-        if (animation != null) {
-            animating = true;
-        }
-        clearThumbnail();
-        setNullAnimation();
-        if (mAppToken.deferClearAllDrawn) {
-            mAppToken.clearAllDrawn();
-        }
-        mStackClip = STACK_CLIP_BEFORE_ANIM;
-        mTransit = TRANSIT_UNSET;
-        mTransitFlags = 0;
-    }
-
-    public boolean isAnimating() {
-        return animation != null || mAppToken.inPendingTransaction;
-    }
-
-    /**
-     * @return whether an animation is about to start, i.e. the animation is set already but we
-     *         haven't processed the first frame yet.
-     */
-    boolean isAnimationStarting() {
-        return animation != null && !animating;
-    }
-
-    public int getTransit() {
-        return mTransit;
-    }
-
-    int getTransitFlags() {
-        return mTransitFlags;
-    }
-
-    public void clearThumbnail() {
-        if (thumbnail != null) {
-            thumbnail.hide();
-            mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail);
-            thumbnail = null;
-        }
-        deferThumbnailDestruction = false;
-    }
-
-    int getStackClip() {
-        return mStackClip;
-    }
-
-    void transferCurrentAnimation(
-            AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
-
-        if (animation != null) {
-            toAppAnimator.animation = animation;
-            toAppAnimator.animating = animating;
-            toAppAnimator.animLayerAdjustment = animLayerAdjustment;
-            setNullAnimation();
-            animLayerAdjustment = 0;
-            toAppAnimator.updateLayers();
-            updateLayers();
-            toAppAnimator.usingTransferredAnimation = true;
-            toAppAnimator.mTransit = mTransit;
-        }
-        if (transferWinAnimator != null) {
-            mAllAppWinAnimators.remove(transferWinAnimator);
-            toAppAnimator.mAllAppWinAnimators.add(transferWinAnimator);
-            toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation;
-            if (toAppAnimator.hasTransformation) {
-                toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation);
-            } else {
-                toAppAnimator.transformation.clear();
-            }
-            transferWinAnimator.mAppAnimator = toAppAnimator;
-        }
-    }
-
-    private void updateLayers() {
-        mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-    }
-
-    private void stepThumbnailAnimation(long currentTime) {
-        thumbnailTransformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
-        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
-
-        ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
-        final boolean screenAnimation = screenRotationAnimation != null
-                && screenRotationAnimation.isAnimating();
-        if (screenAnimation) {
-            thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
-        }
-        // cache often used attributes locally
-        final float tmpFloats[] = mService.mTmpFloats;
-        thumbnailTransformation.getMatrix().getValues(tmpFloats);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
-                + ", " + tmpFloats[Matrix.MTRANS_Y]);
-        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + mThumbnailLayer
-                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
-                + "," + tmpFloats[Matrix.MSKEW_Y]
-                + "][" + tmpFloats[Matrix.MSKEW_X]
-                + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
-        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
-                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
-        thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
-    }
-
-    /**
-     * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
-     * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
-     * and keep producing the first frame of the animation.
-     */
-    private long getAnimationFrameTime(Animation animation, long currentTime) {
-        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
-            animation.setStartTime(currentTime);
-            return currentTime + 1;
-        }
-        return currentTime;
-    }
-
-    private boolean stepAnimation(long currentTime) {
-        if (animation == null) {
-            return false;
-        }
-        transformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
-        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
-        if (!hasMoreFrames) {
-            if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
-                // We are deferring the thumbnail destruction, so extend the animation for one more
-                // (dummy) frame before we clean up
-                deferFinalFrameCleanup = true;
-                hasMoreFrames = true;
-            } else {
-                if (false && DEBUG_ANIM) Slog.v(TAG,
-                        "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
-                        ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
-                deferFinalFrameCleanup = false;
-                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
-                    hasMoreFrames = true;
-                } else {
-                    setNullAnimation();
-                    clearThumbnail();
-                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
-                            + currentTime);
-                }
-            }
-        }
-        hasTransformation = hasMoreFrames;
-        return hasMoreFrames;
-    }
-
-    private long getStartTimeCorrection() {
-        if (mSkipFirstFrame) {
-
-            // If the transition is an animation in which the first frame doesn't change the screen
-            // contents at all, we can just skip it and start at the second frame. So we shift the
-            // start time of the animation forward by minus the frame duration.
-            return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS;
-        } else {
-            return 0;
-        }
-    }
-
-    // This must be called while inside a transaction.
-    boolean stepAnimationLocked(long currentTime) {
-        if (mAppToken.okToAnimate()) {
-            // We will run animations as long as the display isn't frozen.
-
-            if (animation == sDummyAnimation) {
-                // This guy is going to animate, but not yet.  For now count
-                // it as not animating for purposes of scheduling transactions;
-                // when it is really time to animate, this will be set to
-                // a real animation and the next call will execute normally.
-                return false;
-            }
-
-            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
-                    && animation != null) {
-                if (!animating) {
-                    if (DEBUG_ANIM) Slog.v(TAG,
-                        "Starting animation in " + mAppToken +
-                        " @ " + currentTime + " scale="
-                        + mService.getTransitionAnimationScaleLocked()
-                        + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
-                    long correction = getStartTimeCorrection();
-                    animation.setStartTime(currentTime + correction);
-                    animating = true;
-                    if (thumbnail != null) {
-                        thumbnail.show();
-                        thumbnailAnimation.setStartTime(currentTime + correction);
-                    }
-                    mSkipFirstFrame = false;
-                }
-                if (stepAnimation(currentTime)) {
-                    // animation isn't over, step any thumbnail and that's
-                    // it for now.
-                    if (thumbnail != null) {
-                        stepThumbnailAnimation(currentTime);
-                    }
-                    return true;
-                }
-            }
-        } else if (animation != null) {
-            // If the display is frozen, and there is a pending animation,
-            // clear it and make sure we run the cleanup code.
-            animating = true;
-            animation = null;
-        }
-
-        hasTransformation = false;
-
-        if (!animating && animation == null) {
-            return false;
-        }
-
-        mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
-
-        clearAnimation();
-        animating = false;
-        if (animLayerAdjustment != 0) {
-            animLayerAdjustment = 0;
-            updateLayers();
-        }
-        if (mService.mInputMethodTarget != null
-                && mService.mInputMethodTarget.mAppToken == mAppToken) {
-            mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
-        }
-
-        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
-                + ": reportedVisible=" + mAppToken.reportedVisible
-                + " okToDisplay=" + mAppToken.okToDisplay()
-                + " okToAnimate=" + mAppToken.okToAnimate()
-                + " startingDisplayed=" + mAppToken.startingDisplayed);
-
-        transformation.clear();
-
-        final int numAllAppWinAnimators = mAllAppWinAnimators.size();
-        for (int i = 0; i < numAllAppWinAnimators; i++) {
-            mAllAppWinAnimators.get(i).mWin.onExitAnimationDone();
-        }
-        mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
-        return false;
-    }
-
-    // This must be called while inside a transaction.
-    boolean showAllWindowsLocked() {
-        boolean isAnimating = false;
-        final int NW = mAllAppWinAnimators.size();
-        for (int i=0; i<NW; i++) {
-            WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
-            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator);
-            winAnimator.mWin.performShowLocked();
-            isAnimating |= winAnimator.isAnimationSet();
-        }
-        return isAnimating;
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
-        pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
-        pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
-                pw.print(" allDrawn="); pw.print(allDrawn);
-                pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
-        if (lastFreezeDuration != 0) {
-            pw.print(prefix); pw.print("lastFreezeDuration=");
-                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
-        }
-        if (animating || animation != null) {
-            pw.print(prefix); pw.print("animating="); pw.println(animating);
-            pw.print(prefix); pw.print("animation="); pw.println(animation);
-            pw.print(prefix); pw.print("mTransit="); pw.println(mTransit);
-            pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags);
-        }
-        if (hasTransformation) {
-            pw.print(prefix); pw.print("XForm: ");
-                    transformation.printShortString(pw);
-                    pw.println();
-        }
-        if (thumbnail != null) {
-            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    pw.print(" layer="); pw.println(mThumbnailLayer);
-            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
-            pw.print(prefix); pw.print("thumbnailTransformation=");
-                    pw.println(thumbnailTransformation.toShortString());
-        }
-        for (int i=0; i<mAllAppWinAnimators.size(); i++) {
-            WindowStateAnimator wanim = mAllAppWinAnimators.get(i);
-            pw.print(prefix); pw.print("App Win Anim #"); pw.print(i);
-                    pw.print(": "); pw.println(wanim);
-        }
-    }
-
-    void startProlongAnimation(int prolongType) {
-        mProlongAnimation = prolongType;
-        mClearProlongedAnimation = false;
-    }
-
-    void endProlongedAnimation() {
-        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-    }
-
-    // This is an animation that does nothing: it just immediately finishes
-    // itself every time it is called.  It is used as a stub animation in cases
-    // where we want to synchronize multiple things that may be animating.
-    static final class DummyAnimation extends Animation {
-        @Override
-        public boolean getTransformation(long currentTime, Transformation outTransformation) {
-            return false;
-        }
-    }
-
-}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 00a0d3d..ae9f28b 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
@@ -25,7 +24,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -34,16 +32,13 @@
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
 import android.util.Slog;
-import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -339,7 +334,7 @@
 
             if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
                     + mToken + ", visible=" + visible + "): " + mService.mAppTransition
-                    + " hidden=" + wtoken.hidden + " hiddenRequested="
+                    + " hidden=" + wtoken.isHidden() + " hiddenRequested="
                     + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
 
             mService.mOpeningApps.remove(wtoken);
@@ -364,11 +359,11 @@
                 wtoken.startingMoved = false;
                 // If the token is currently hidden (should be the common case), or has been
                 // stopped, then we need to set up to wait for its windows to be ready.
-                if (wtoken.hidden || wtoken.mAppStopped) {
+                if (wtoken.isHidden() || wtoken.mAppStopped) {
                     wtoken.clearAllDrawn();
 
                     // If the app was already visible, don't reset the waitingToShow state.
-                    if (wtoken.hidden) {
+                    if (wtoken.isHidden()) {
                         wtoken.waitingToShow = true;
                     }
 
@@ -389,21 +384,6 @@
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
-                // A dummy animation is a placeholder animation which informs others that an
-                // animation is going on (in this case an application transition). If the animation
-                // was transferred from another application/animator, no dummy animator should be
-                // created since an animation is already in progress.
-                if (wtoken.mAppAnimator.usingTransferredAnimation
-                        && wtoken.mAppAnimator.animation == null) {
-                    Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
-                            + ", using null transferred animation!");
-                }
-                if (!wtoken.mAppAnimator.usingTransferredAnimation &&
-                        (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
-                    if (DEBUG_APP_TRANSITIONS) Slog.v(
-                            TAG_WM, "Setting dummy animation on: " + wtoken);
-                    wtoken.mAppAnimator.setDummyAnimation();
-                }
                 wtoken.inPendingTransaction = true;
                 if (visible) {
                     mService.mOpeningApps.add(wtoken);
@@ -423,7 +403,7 @@
                             if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
                                     + " adding " + focusedToken + " to mOpeningApps");
                             // Force animation to be loaded.
-                            focusedToken.hidden = true;
+                            focusedToken.setHidden(true);
                             mService.mOpeningApps.add(focusedToken);
                         }
                     }
@@ -710,7 +690,7 @@
                 return;
             }
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
-                    + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
+                    + mContainer.isHidden() + " freezing=" + mContainer.isFreezingScreen());
             mContainer.stopFreezingScreen(true, force);
         }
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
new file mode 100644
index 0000000..b86cd50
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -0,0 +1,164 @@
+/*
+ * 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 com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
+
+import com.android.server.wm.SurfaceAnimator.Animatable;
+
+/**
+ * Represents a surface that is displayed over an {@link AppWindowToken}
+ */
+class AppWindowThumbnail implements Animatable {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowThumbnail" : TAG_WM;
+
+    private final AppWindowToken mAppToken;
+    private final SurfaceControl mSurfaceControl;
+    private final SurfaceAnimator mSurfaceAnimator;
+    private final int mWidth;
+    private final int mHeight;
+
+    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+        mAppToken = appToken;
+        mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, appToken.mService);
+        mWidth = thumbnailHeader.getWidth();
+        mHeight = thumbnailHeader.getHeight();
+
+        // Create a new surface for the thumbnail
+        WindowState window = appToken.findMainWindow();
+
+        // TODO: This should be attached as a child to the app token, once the thumbnail animations
+        // use relative coordinates. Once we start animating task we can also consider attaching
+        // this to the task.
+        mSurfaceControl = appToken.makeSurface()
+                .setName("thumbnail anim: " + appToken.toString())
+                .setSize(mWidth, mHeight)
+                .setFormat(PixelFormat.TRANSLUCENT)
+                .setMetadata(appToken.windowType,
+                        window != null ? window.mOwnerUid : Binder.getCallingUid())
+                .build();
+
+        if (SHOW_TRANSACTIONS) {
+            Slog.i(TAG, "  THUMBNAIL " + mSurfaceControl + ": CREATE");
+        }
+
+        // Transfer the thumbnail to the surface
+        Surface drawSurface = new Surface();
+        drawSurface.copyFrom(mSurfaceControl);
+        drawSurface.attachAndQueueBuffer(thumbnailHeader);
+        drawSurface.release();
+        t.show(mSurfaceControl);
+
+        // We parent the thumbnail to the task, and just place it on top of anything else in the
+        // task.
+        t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+    }
+
+    void startAnimation(Transaction t, Animation anim) {
+        anim.restrictDuration(MAX_ANIMATION_DURATION);
+        anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked());
+        mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
+                new WindowAnimationSpec(anim, null /* position */,
+                        mAppToken.mService.mAppTransition.canSkipFirstFrame()),
+                mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */);
+    }
+
+    private void onAnimationFinished() {
+    }
+
+    void setShowing(Transaction pendingTransaction, boolean show) {
+        // TODO: Not needed anymore once thumbnail is attached to the app.
+        if (show) {
+            pendingTransaction.show(mSurfaceControl);
+        } else {
+            pendingTransaction.hide(mSurfaceControl);
+        }
+    }
+
+    void destroy() {
+        mSurfaceAnimator.cancelAnimation();
+        mSurfaceControl.destroy();
+    }
+
+    @Override
+    public Transaction getPendingTransaction() {
+        return mAppToken.getPendingTransaction();
+    }
+
+    @Override
+    public void commitPendingTransaction() {
+        mAppToken.commitPendingTransaction();
+    }
+
+    @Override
+    public void destroyAfterPendingTransaction(SurfaceControl surface) {
+        mAppToken.destroyAfterPendingTransaction(surface);
+    }
+
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+        t.setLayer(leash, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public void onAnimationLeashDestroyed(Transaction t) {
+
+        // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail
+        // became visible.
+        t.hide(mSurfaceControl);
+    }
+
+    @Override
+    public Builder makeAnimationLeash() {
+        return mAppToken.makeSurface().setParent(mAppToken.getAppAnimationLayer());
+    }
+
+    @Override
+    public SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    @Override
+    public SurfaceControl getParentSurfaceControl() {
+        return mAppToken.getParentSurfaceControl();
+    }
+
+    @Override
+    public int getSurfaceWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getSurfaceHeight() {
+        return mHeight;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 94a0cb7..b24a8ca 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -20,6 +20,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -51,22 +52,25 @@
 import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
 
 import android.annotation.CallSuper;
-import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
 import android.view.IApplicationToken;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.animation.Transformation;
 
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputApplicationHandle;
@@ -88,11 +92,14 @@
 class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM;
 
+    /**
+     * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
+     */
+    private static final int Z_BOOST_BASE = 800570000;
+
     // Non-null only for application tokens.
     final IApplicationToken appToken;
 
-    @NonNull final AppWindowAnimator mAppAnimator;
-
     final boolean mVoiceInteraction;
 
     /** @see WindowContainer#fillsParent() */
@@ -120,6 +127,8 @@
     private int mNumDrawnWindows;
     boolean inPendingTransaction;
     boolean allDrawn;
+    private boolean mLastAllDrawn;
+
     // Set to true when this app creates a surface while in the middle of an animation. In that
     // case do not clear allDrawn until the animation completes.
     boolean deferClearAllDrawn;
@@ -189,6 +198,32 @@
      */
     private boolean mCanTurnScreenOn = true;
 
+    /**
+     * If we are running an animation, this determines the transition type. Must be one of
+     * AppTransition.TRANSIT_* constants.
+     */
+    private int mTransit;
+
+    /**
+     * If we are running an animation, this determines the flags during this animation. Must be a
+     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
+     */
+    private int mTransitFlags;
+
+    /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
+    private boolean mLastSurfaceShowing;
+
+    private AppWindowThumbnail mThumbnail;
+
+    /** Have we been asked to have this token keep the screen frozen? */
+    private boolean mFreezingScreen;
+
+    /** Whether this token should be boosted at the top of all app window tokens. */
+    private boolean mNeedsZBoost;
+
+    private final Point mTmpPoint = new Point();
+    private final Rect mTmpRect = new Rect();
+
     AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
             DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
             boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
@@ -206,7 +241,7 @@
         mRotationAnimationHint = rotationAnimationHint;
 
         // Application tokens start out hidden.
-        hidden = true;
+        setHidden(true);
         hiddenRequested = true;
     }
 
@@ -218,7 +253,6 @@
         mVoiceInteraction = voiceInteraction;
         mFillsParent = fillsParent;
         mInputApplicationHandle = new InputApplicationHandle(this);
-        mAppAnimator = new AppWindowAnimator(this, service);
     }
 
     void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
@@ -262,7 +296,7 @@
         boolean nowGone = mReportedVisibilityResults.nowGone;
 
         boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
-        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden();
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
@@ -325,19 +359,16 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         boolean visibilityChanged = false;
-        if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) {
+        if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) {
             final AccessibilityController accessibilityController = mService.mAccessibilityController;
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout);
+                    "Changing app " + this + " hidden=" + isHidden() + " performLayout=" + performLayout);
 
             boolean runningAppAnimation = false;
 
-            if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                mAppAnimator.setNullAnimation();
-            }
             if (transit != AppTransition.TRANSIT_UNSET) {
-                if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
+                if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) {
                     delayed = runningAppAnimation = true;
                 }
                 final WindowState window = findMainWindow();
@@ -355,7 +386,8 @@
                 changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
             }
 
-            hidden = hiddenRequested = !visible;
+            setHidden(!visible);
+            hiddenRequested = !visible;
             visibilityChanged = true;
             if (!visible) {
                 stopFreezingScreen(true, true);
@@ -373,7 +405,7 @@
             }
 
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
-                    + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested);
+                    + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested);
 
             if (changed) {
                 mService.mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -386,7 +418,7 @@
             }
         }
 
-        if (mAppAnimator.animation != null) {
+        if (isReallyAnimating()) {
             delayed = true;
         }
 
@@ -414,7 +446,7 @@
             // no animation but there will still be a transition set.
             // We still need to delay hiding the surface such that it
             // can be synchronized with showing the next surface in the transition.
-            if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) {
+            if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) {
                 SurfaceControl.openTransaction();
                 for (int i = mChildren.size() - 1; i >= 0; i--) {
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
@@ -487,7 +519,7 @@
     boolean isVisible() {
         // If the app token isn't hidden then it is considered visible and there is no need to check
         // its children windows to see if they are visible.
-        return !hidden;
+        return !isHidden();
     }
 
     @Override
@@ -533,7 +565,7 @@
         }
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed
-                + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating);
+                + " animation=" + getAnimation() + " animating=" + isSelfAnimating());
 
         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
                 + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
@@ -545,7 +577,7 @@
         // If this window was animating, then we need to ensure that the app transition notifies
         // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
         // add to that list now
-        if (mAppAnimator.animating) {
+        if (isSelfAnimating()) {
             mService.mNoAnimationNotifyOnTransitionFinished.add(token);
         }
 
@@ -561,8 +593,7 @@
         } else {
             // Make sure there is no animation running on this token, so any windows associated
             // with it will be removed as soon as their animations are complete
-            mAppAnimator.clearAnimation();
-            mAppAnimator.animating = false;
+            cancelAnimation();
             if (stack != null) {
                 stack.mExitingAppTokens.remove(this);
             }
@@ -710,7 +741,7 @@
                 // We set the hidden state to false for the token from a transferred starting window.
                 // We now reset it back to true since the starting window was the last window in the
                 // token.
-                hidden = true;
+                setHidden(true);
             }
         } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
             // If this is the last window except for a starting transition window,
@@ -756,14 +787,6 @@
             final WindowState w = mChildren.get(i);
             w.setWillReplaceWindow(animate);
         }
-        if (animate) {
-            // Set-up dummy animation so we can start treating windows associated with this
-            // token like they are in transition before the new app window is ready for us to
-            // run the real transition animation.
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "setWillReplaceWindow() Setting dummy animation on: " + this);
-            mAppAnimator.setDummyAnimation();
-        }
     }
 
     void setWillReplaceChildWindows() {
@@ -1007,13 +1030,12 @@
 
     void startFreezingScreen() {
         if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden="
-                + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
+                + isHidden() + " freezing=" + mFreezingScreen + " hiddenRequested="
                 + hiddenRequested);
         if (!hiddenRequested) {
-            if (!mAppAnimator.freezingScreen) {
-                mAppAnimator.freezingScreen = true;
+            if (!mFreezingScreen) {
+                mFreezingScreen = true;
                 mService.registerAppFreezeListener(this);
-                mAppAnimator.lastFreezeDuration = 0;
                 mService.mAppsFreezingScreen++;
                 if (mService.mAppsFreezingScreen == 1) {
                     mService.startFreezingDisplayLocked(false, 0, 0, getDisplayContent());
@@ -1030,7 +1052,7 @@
     }
 
     void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
-        if (!mAppAnimator.freezingScreen) {
+        if (!mFreezingScreen) {
             return;
         }
         if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + this + " force=" + force);
@@ -1042,10 +1064,8 @@
         }
         if (force || unfrozeWindows) {
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this);
-            mAppAnimator.freezingScreen = false;
+            mFreezingScreen = false;
             mService.unregisterAppFreezeListener(this);
-            mAppAnimator.lastFreezeDuration =
-                    (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime);
             mService.mAppsFreezingScreen--;
             mService.mLastFinishedFreezeSource = this;
         }
@@ -1111,14 +1131,19 @@
                 if (fromToken.firstWindowDrawn) {
                     firstWindowDrawn = true;
                 }
-                if (!fromToken.hidden) {
-                    hidden = false;
+                if (!fromToken.isHidden()) {
+                    setHidden(false);
                     hiddenRequested = false;
                     mHiddenSetFromTransferredStartingWindow = true;
                 }
                 setClientHidden(fromToken.mClientHidden);
-                fromToken.mAppAnimator.transferCurrentAnimation(
-                        mAppAnimator, tStartingWindow.mWinAnimator);
+
+                transferAnimation(fromToken);
+
+                // When transferring an animation, we no longer need to apply an animation to the
+                // the token we transfer the animation over. Thus, remove the animation from
+                // pending opening apps.
+                mService.mOpeningApps.remove(this);
 
                 mService.updateFocusedWindowLocked(
                         UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
@@ -1142,17 +1167,8 @@
             return true;
         }
 
-        final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator;
-        final AppWindowAnimator wAppAnimator = mAppAnimator;
-        if (tAppAnimator.thumbnail != null) {
-            // The old token is animating with a thumbnail, transfer that to the new token.
-            if (wAppAnimator.thumbnail != null) {
-                wAppAnimator.thumbnail.destroy();
-            }
-            wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
-            tAppAnimator.thumbnail = null;
-        }
+        // TODO: Transfer thumbnail
+
         return false;
     }
 
@@ -1160,16 +1176,6 @@
         return mChildren.size() == 1 && mChildren.get(0) == win;
     }
 
-    void setAllAppWinAnimators() {
-        final ArrayList<WindowStateAnimator> allAppWinAnimators = mAppAnimator.mAllAppWinAnimators;
-        allAppWinAnimators.clear();
-
-        final int windowsCount = mChildren.size();
-        for (int j = 0; j < windowsCount; j++) {
-            (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
-        }
-    }
-
     @Override
     void onAppTransitionDone() {
         sendingToBottom = false;
@@ -1206,18 +1212,18 @@
 
     @Override
     void checkAppWindowsReadyToShow() {
-        if (allDrawn == mAppAnimator.allDrawn) {
+        if (allDrawn == mLastAllDrawn) {
             return;
         }
 
-        mAppAnimator.allDrawn = allDrawn;
+        mLastAllDrawn = allDrawn;
         if (!allDrawn) {
             return;
         }
 
         // The token has now changed state to having all windows shown...  what to do, what to do?
-        if (mAppAnimator.freezingScreen) {
-            mAppAnimator.showAllWindowsLocked();
+        if (mFreezingScreen) {
+            showAllWindowsLocked();
             stopFreezingScreen(false, true);
             if (DEBUG_ORIENTATION) Slog.i(TAG,
                     "Setting mOrientationChangeComplete=true because wtoken " + this
@@ -1230,7 +1236,7 @@
 
             // We can now show all of the drawn windows!
             if (!mService.mOpeningApps.contains(this)) {
-                mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked());
+                showAllWindowsLocked();
             }
         }
     }
@@ -1300,10 +1306,10 @@
 
         if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
             Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
-                    + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
+                    + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
         }
 
-        if (allDrawn && !mAppAnimator.freezingScreen) {
+        if (allDrawn && !mFreezingScreen) {
             return false;
         }
 
@@ -1320,13 +1326,13 @@
         if (!allDrawn && w.mightAffectAllDrawn()) {
             if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
                 Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
-                        + ", isAnimationSet=" + winAnimator.isAnimationSet());
+                        + ", isAnimationSet=" + isSelfAnimating());
                 if (!w.isDrawnLw()) {
                     Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
                             + " pv=" + w.mPolicyVisibility
                             + " mDrawState=" + winAnimator.drawStateToString()
                             + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
-                            + " a=" + winAnimator.isAnimationSet());
+                            + " a=" + isSelfAnimating());
                 }
             }
 
@@ -1338,7 +1344,7 @@
 
                         if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: "
                                 + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
-                                + " freezingScreen=" + mAppAnimator.freezingScreen
+                                + " freezingScreen=" + mFreezingScreen
                                 + " mAppFreezing=" + w.mAppFreezing);
 
                         isInterestingAndDrawn = true;
@@ -1356,21 +1362,6 @@
     }
 
     @Override
-    void stepAppWindowsAnimation(long currentTime) {
-        mAppAnimator.wasAnimating = mAppAnimator.animating;
-        if (mAppAnimator.stepAnimationLocked(currentTime)) {
-            mAppAnimator.animating = true;
-            mService.mAnimator.setAnimating(true);
-            mService.mAnimator.mAppWindowAnimating = true;
-        } else if (mAppAnimator.wasAnimating) {
-            // stopped animating, do one more pass through the layout
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                    DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null);
-            if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
-        }
-    }
-
-    @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
         // before the non-exiting app tokens. So, we skip the exiting app tokens here.
@@ -1521,18 +1512,269 @@
     }
 
     @Override
-    int getAnimLayerAdjustment() {
-        return mAppAnimator.animLayerAdjustment;
+    public SurfaceControl.Builder makeAnimationLeash() {
+        return super.makeAnimationLeash()
+                .setParent(getAppAnimationLayer());
+    }
+
+    boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+
+        if (mService.mDisableTransitionAnimation) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG_WM, "applyAnimation: transition animation is disabled. atoken=" + this);
+            }
+            cancelAnimation();
+            return false;
+        }
+
+        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
+        // to animate and it can cause strange artifacts when we unfreeze the display if some
+        // different animation is running.
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
+        if (okToAnimate()) {
+            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+            if (a != null) {
+                final TaskStack stack = getStack();
+                mTmpPoint.set(0, 0);
+                mTmpRect.setEmpty();
+                if (stack != null) {
+                    stack.getRelativePosition(mTmpPoint);
+                    stack.getBounds(mTmpRect);
+                }
+                final AnimationAdapter adapter = new LocalAnimationAdapter(
+                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
+                                mService.mAppTransition.canSkipFirstFrame(),
+                                mService.mAppTransition.getAppStackClipMode()),
+                        mService.mSurfaceAnimationRunner);
+                if (a.getZAdjustment() == Animation.ZORDER_TOP) {
+                    mNeedsZBoost = true;
+                }
+                startAnimation(getPendingTransaction(), adapter, !isVisible());
+                mTransit = transit;
+                mTransitFlags = mService.mAppTransition.getTransitFlags();
+            }
+        } else {
+            cancelAnimation();
+        }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+        return isReallyAnimating();
+    }
+
+    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+        final DisplayContent displayContent = getTask().getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int width = displayInfo.appWidth;
+        final int height = displayInfo.appHeight;
+        if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
+                "applyAnimation: atoken=" + this);
+
+        // Determine the visible rect to calculate the thumbnail clip
+        final WindowState win = findMainWindow();
+        final Rect frame = new Rect(0, 0, width, height);
+        final Rect displayFrame = new Rect(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        final Rect insets = new Rect();
+        final Rect stableInsets = new Rect();
+        Rect surfaceInsets = null;
+        final boolean freeform = win != null && win.inFreeformWindowingMode();
+        if (win != null) {
+            // Containing frame will usually cover the whole screen, including dialog windows.
+            // For freeform workspace windows it will not cover the whole screen and it also
+            // won't exactly match the final freeform window frame (e.g. when overlapping with
+            // the status bar). In that case we need to use the final frame.
+            if (freeform) {
+                frame.set(win.mFrame);
+            } else {
+                frame.set(win.mContainingFrame);
+            }
+            surfaceInsets = win.getAttrs().surfaceInsets;
+            insets.set(win.mContentInsets);
+            stableInsets.set(win.mStableInsets);
+        }
+
+        if (mLaunchTaskBehind) {
+            // Differentiate the two animations. This one which is briefly on the screen
+            // gets the !enter animation, and the other activity which remains on the
+            // screen gets the enter animation. Both appear in the mOpeningApps set.
+            enter = false;
+        }
+        if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+                + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+        final Configuration displayConfig = displayContent.getConfiguration();
+        final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter,
+                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
+                surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId);
+        if (a != null) {
+            if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+            final int containingWidth = frame.width();
+            final int containingHeight = frame.height();
+            a.initialize(containingWidth, containingHeight, width, height);
+            a.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+        }
+        return a;
+    }
+
+    @Override
+    protected void setLayer(Transaction t, int layer) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    @Override
+    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
+        }
+    }
+
+    @Override
+    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.reparent(mSurfaceControl, newParent.getHandle());
+        }
+    }
+
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+
+        // The leash is parented to the animation layer. We need to preserve the z-order by using
+        // the prefix order index, but we boost if necessary.
+        int layer = getPrefixOrderIndex();
+        if (mNeedsZBoost) {
+            layer += Z_BOOST_BASE;
+        }
+        leash.setLayer(layer);
+    }
+
+    /**
+     * This must be called while inside a transaction.
+     */
+    void showAllWindowsLocked() {
+        forAllWindows(windowState -> {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState);
+            windowState.performShowLocked();
+        }, false /* traverseTopToBottom */);
+    }
+
+    @Override
+    protected void onAnimationFinished() {
+        super.onAnimationFinished();
+
+        mTransit = TRANSIT_UNSET;
+        mTransitFlags = 0;
+        mNeedsZBoost = false;
+
+        setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
+                "AppWindowToken");
+
+        clearThumbnail();
+
+        if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
+            getDisplayContent().computeImeTarget(true /* updateImeTarget */);
+        }
+
+        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
+                + ": reportedVisible=" + reportedVisible
+                + " okToDisplay=" + okToDisplay()
+                + " okToAnimate=" + okToAnimate()
+                + " startingDisplayed=" + startingDisplayed);
+
+        // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
+        // traverse the copy.
+        final ArrayList<WindowState> children = new ArrayList<>(mChildren);
+        children.forEach(WindowState::onExitAnimationDone);
+
+        mService.mAppTransition.notifyAppTransitionFinishedLocked(token);
+        scheduleAnimation();
+    }
+
+    @Override
+    boolean isAppAnimating() {
+        return isSelfAnimating();
     }
 
     @Override
     boolean isSelfAnimating() {
-        return mAppAnimator.isAnimating();
+        // If we are about to start a transition, we also need to be considered animating.
+        return isWaitingForTransitionStart() || isReallyAnimating();
+    }
+
+    /**
+     * @return True if and only if we are actually running an animation. Note that
+     *         {@link #isSelfAnimating} also returns true if we are waiting for an animation to
+     *         start.
+     */
+    private boolean isReallyAnimating() {
+        return super.isSelfAnimating();
     }
 
     @Override
-    void dump(PrintWriter pw, String prefix) {
-        super.dump(pw, prefix);
+    void cancelAnimation() {
+        super.cancelAnimation();
+        clearThumbnail();
+    }
+
+    boolean isWaitingForTransitionStart() {
+        return mService.mAppTransition.isTransitionSet()
+                && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this));
+    }
+
+    public int getTransit() {
+        return mTransit;
+    }
+
+    int getTransitFlags() {
+        return mTransitFlags;
+    }
+
+    void attachThumbnailAnimation() {
+        if (!isReallyAnimating()) {
+            return;
+        }
+        final int taskId = getTask().mTaskId;
+        final GraphicBuffer thumbnailHeader =
+                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
+        if (thumbnailHeader == null) {
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
+            return;
+        }
+        clearThumbnail();
+        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader);
+        mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+    }
+
+    private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+
+        // If this is a multi-window scenario, we use the windows frame as
+        // destination of the thumbnail header animation. If this is a full screen
+        // window scenario, we use the whole display as the target.
+        WindowState win = findMainWindow();
+        Rect appRect = win != null ? win.getContentFrameLw() :
+                new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+        Rect insets = win != null ? win.mContentInsets : null;
+        final Configuration displayConfig = mDisplayContent.getConfiguration();
+        return mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(
+                appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode,
+                displayConfig.orientation);
+    }
+
+    private void clearThumbnail() {
+        if (mThumbnail == null) {
+            return;
+        }
+        mThumbnail.destroy();
+        mThumbnail = null;
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         if (appToken != null) {
             pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
         }
@@ -1549,13 +1791,13 @@
             pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
         }
         if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0
-                || allDrawn || mAppAnimator.allDrawn) {
+                || allDrawn || mLastAllDrawn) {
             pw.print(prefix); pw.print("mNumInterestingWindows=");
                     pw.print(mNumInterestingWindows);
                     pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows);
                     pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
                     pw.print(" allDrawn="); pw.print(allDrawn);
-                    pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
+                    pw.print(" lastAllDrawn="); pw.print(mLastAllDrawn);
                     pw.println(")");
         }
         if (inPendingTransaction) {
@@ -1590,9 +1832,39 @@
         if (mRemovingFromDisplay) {
             pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
         }
-        if (mAppAnimator.isAnimating()) {
-            mAppAnimator.dump(pw, prefix + "  ");
+    }
+
+    @Override
+    void setHidden(boolean hidden) {
+        super.setHidden(hidden);
+        scheduleAnimation();
+    }
+
+    @Override
+    void prepareSurfaces() {
+        // isSelfAnimating also returns true when we are about to start a transition, so we need
+        // to check super here.
+        final boolean reallyAnimating = super.isSelfAnimating();
+        final boolean show = !isHidden() || reallyAnimating;
+        if (show && !mLastSurfaceShowing) {
+            mPendingTransaction.show(mSurfaceControl);
+        } else if (!show && mLastSurfaceShowing) {
+            mPendingTransaction.hide(mSurfaceControl);
         }
+        if (mThumbnail != null) {
+            mThumbnail.setShowing(mPendingTransaction, show);
+        }
+        mLastSurfaceShowing = show;
+        super.prepareSurfaces();
+    }
+
+    boolean isFreezingScreen() {
+        return mFreezingScreen;
+    }
+
+    @Override
+    boolean needsZBoost() {
+        return mNeedsZBoost || super.needsZBoost();
     }
 
     @CallSuper
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f458457..d053015 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -69,7 +69,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -91,8 +90,6 @@
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -123,7 +120,6 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -350,7 +346,6 @@
     private boolean mDisplayReady = false;
 
     WallpaperController mWallpaperController;
-    int mInputMethodAnimLayerAdjustment;
 
     private final SurfaceSession mSession = new SurfaceSession();
 
@@ -398,14 +393,6 @@
                 }
             }
         }
-        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
-        if (appAnimator != null && appAnimator.thumbnail != null) {
-            if (appAnimator.thumbnailTransactionSeq
-                    != mTmpWindowAnimator.mAnimTransactionSequence) {
-                appAnimator.thumbnailTransactionSeq =
-                        mTmpWindowAnimator.mAnimTransactionSequence;
-            }
-        }
     };
 
     private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
@@ -436,15 +423,14 @@
 
         // If this window's app token is running a detached wallpaper animation, make a note so
         // we can ensure the wallpaper is displayed behind it.
-        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
-        if (appAnimator != null && appAnimator.animation != null
-                && appAnimator.animating) {
-            if ((flags & FLAG_SHOW_WALLPAPER) != 0
-                    && appAnimator.animation.getDetachWallpaper()) {
+        final AppWindowToken atoken = winAnimator.mWin.mAppToken;
+        final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null;
+        if (animation != null) {
+            if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) {
                 mTmpWindow = w;
             }
 
-            final int color = appAnimator.animation.getBackgroundColor();
+            final int color = animation.getBackgroundColor();
             if (color != 0) {
                 final TaskStack stack = w.getStack();
                 if (stack != null) {
@@ -527,11 +513,11 @@
                     + " screen changed=" + w.isConfigChanged());
             final AppWindowToken atoken = w.mAppToken;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
         }
@@ -706,7 +692,7 @@
                 }
             }
             final TaskStack stack = w.getStack();
-            if ((!winAnimator.isWaitingForOpening())
+            if (!winAnimator.isWaitingForOpening()
                     || (stack != null && stack.isAnimatingBounds())) {
                 // Updates the shown frame before we set up the surface. This is needed
                 // because the resizing could change the top-left position (in addition to
@@ -724,9 +710,13 @@
             winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
 
             // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
-            // to the surface of the window container as well. Use mTmpTransaction instead of
-            // mPendingTransaction to avoid committing any existing changes in there.
+            // to the surface of the window container and also the position of the stack window
+            // container as well. Use mTmpTransaction instead of mPendingTransaction to avoid
+            // committing any existing changes in there.
             w.updateSurfacePosition(mTmpTransaction);
+            if (stack != null) {
+                stack.updateSurfaceBounds(mTmpTransaction);
+            }
             SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
         }
 
@@ -2056,12 +2046,6 @@
         mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
     }
 
-    void setInputMethodAnimLayerAdjustment(int adj) {
-        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
-        mInputMethodAnimLayerAdjustment = adj;
-        assignWindowLayers(false /* relayoutNeeded */);
-    }
-
     /**
      * If a window that has an animation specifying a colored background and the current wallpaper
      * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
@@ -2168,7 +2152,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
         final String subPrefix = "  " + prefix;
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
@@ -2202,7 +2188,7 @@
         pw.println(prefix + "Application tokens in top down Z order:");
         for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-            stack.dump(prefix + "  ", pw);
+            stack.dump(pw, prefix + "  ", dumpAll);
         }
 
         pw.println();
@@ -2214,7 +2200,7 @@
                 pw.print("  Exiting #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
 
@@ -2239,11 +2225,6 @@
         pw.println();
         mPinnedStackControllerLocked.dump(prefix, pw);
 
-        if (mInputMethodAnimLayerAdjustment != 0) {
-            pw.println(subPrefix
-                    + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
-        }
-
         pw.println();
         mDisplayFrames.dump(prefix, pw);
     }
@@ -2403,7 +2384,7 @@
             if (updateImeTarget) {
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
                         + mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
             }
             return null;
         }
@@ -2452,7 +2433,7 @@
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
                         + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
                         + Debug.getCallers(4) : ""));
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
             }
 
             return null;
@@ -2466,7 +2447,7 @@
                 // to look at all windows below the current target that are in this app, finding the
                 // highest visible one in layering.
                 WindowState highestTarget = null;
-                if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+                if (token.isSelfAnimating()) {
                     highestTarget = token.getHighestAnimLayerWindow(curTarget);
                 }
 
@@ -2480,14 +2461,14 @@
                     if (appTransition.isTransitionSet()) {
                         // If we are currently setting up for an animation, hold everything until we
                         // can find out what will happen.
-                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        setInputMethodTarget(highestTarget, true);
                         return highestTarget;
                     } else if (highestTarget.mWinAnimator.isAnimationSet() &&
                             highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
                         // If the window we are currently targeting is involved with an animation,
                         // and it is on top of the next target we will be over, then hold off on
                         // moving until that is done.
-                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        setInputMethodTarget(highestTarget, true);
                         return highestTarget;
                     }
                 }
@@ -2495,23 +2476,20 @@
 
             if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
                     + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
-            setInputMethodTarget(target, false, target.mAppToken != null
-                    ? target.mAppToken.getAnimLayerAdjustment() : 0);
+            setInputMethodTarget(target, false);
         }
 
         return target;
     }
 
-    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         if (target == mService.mInputMethodTarget
-                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
-                && mInputMethodAnimLayerAdjustment == layerAdj) {
+                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
         }
 
         mService.mInputMethodTarget = target;
         mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
-        setInputMethodAnimLayerAdjustment(layerAdj);
         assignWindowLayers(false /* setLayoutNeeded */);
     }
 
@@ -2573,7 +2551,7 @@
             pw.print(token);
             if (dumpAll) {
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             } else {
                 pw.println();
             }
@@ -3197,7 +3175,7 @@
         /**
          * A control placed at the appropriate level for transitions to occur.
          */
-        SurfaceControl mAnimationLayer = null;
+        SurfaceControl mAppAnimationLayer = null;
 
         // Cached reference to some special stacks we tend to get a lot so we don't need to loop
         // through the list to find them.
@@ -3462,8 +3440,7 @@
                         // Make sure there is no animation running on this token, so any windows
                         // associated with it will be removed as soon as their animations are
                         // complete.
-                        token.mAppAnimator.clearAnimation();
-                        token.mAppAnimator.animating = false;
+                        cancelAnimation();
                         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
                                 "performLayout: App token exiting now removed" + token);
                         token.removeIfPossible();
@@ -3544,19 +3521,28 @@
             // The appropriate place for App-Transitions to occur is right
             // above all other animations but still below things in the Picture-and-Picture
             // windowing mode.
-            if (mAnimationLayer != null) {
-                t.setLayer(mAnimationLayer, layer++);
+            if (mAppAnimationLayer != null) {
+                t.setLayer(mAppAnimationLayer, layer++);
             }
         }
 
         @Override
+        SurfaceControl getAppAnimationLayer() {
+            return mAppAnimationLayer;
+        }
+
+        @Override
         void onParentSet() {
             super.onParentSet();
             if (getParent() != null) {
-                mAnimationLayer = makeSurface().build();
+                mAppAnimationLayer = makeChildSurface(null)
+                        .setName("animationLayer")
+                        .build();
+                getPendingTransaction().show(mAppAnimationLayer);
+                scheduleAnimation();
             } else {
-                mAnimationLayer.destroy();
-                mAnimationLayer = null;
+                mAppAnimationLayer.destroy();
+                mAppAnimationLayer = null;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 5fe4565..2173fa3 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -16,10 +16,9 @@
 
 package com.android.server.wm;
 
-import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
 
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -30,6 +29,7 @@
 class LocalAnimationAdapter implements AnimationAdapter {
 
     private final AnimationSpec mSpec;
+
     private final SurfaceAnimationRunner mAnimator;
 
     LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
@@ -64,6 +64,11 @@
         return mSpec.getDuration();
     }
 
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return mSpec.calculateStatusBarTransitionStartTime();
+    }
+
     /**
      * Describes how to apply an animation.
      */
@@ -84,6 +89,13 @@
         }
 
         /**
+         * @see AnimationAdapter#getStatusBarTransitionsStartTime
+         */
+        default long calculateStatusBarTransitionStartTime() {
+            return SystemClock.uptimeMillis();
+        }
+
+        /**
          * @return The duration of the animation.
          */
         long getDuration();
@@ -91,10 +103,17 @@
         /**
          * Called when the spec needs to apply the current animation state to the leash.
          *
-         * @param t The transaction to use to apply a transform.
-         * @param leash The leash to apply the state to.
+         * @param t               The transaction to use to apply a transform.
+         * @param leash           The leash to apply the state to.
          * @param currentPlayTime The current time of the animation.
          */
         void apply(Transaction t, SurfaceControl leash, long currentPlayTime);
+
+        /**
+         * @see AppTransition#canSkipFirstFrame
+         */
+        default boolean canSkipFirstFrame() {
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e653e7d..2a77c92 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -612,7 +612,7 @@
                         defaultDisplay.pendingLayoutChanges);
         }
 
-        if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
+        if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
             // We have finished the animation of an app transition. To do this, we have delayed a
             // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
             // token list reflects the correct Z-order, but the window list may now be out of sync
@@ -1035,7 +1035,7 @@
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
                 final DisplayContent displayContent = mChildren.get(i);
-                displayContent.dump("  ", pw);
+                displayContent.dump(pw, "  ", true /* dumpAll */);
             }
         } else {
             pw.println("  NO DISPLAY");
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 3ef9b3f..3a41eb0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.util.TimeUtils.NANOS_PER_MS;
 import static android.view.Choreographer.CALLBACK_TRAVERSAL;
 import static android.view.Choreographer.getSfInstance;
 
@@ -142,6 +143,7 @@
             // Transaction will be applied in the commit phase.
             scheduleApplyTransaction();
         });
+
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -158,6 +160,7 @@
                     mRunningAnimations.remove(a.mLeash);
                     synchronized (mCancelLock) {
                         if (!a.mCancelled) {
+
                             // Post on other thread that we can push final state without jank.
                             AnimationThread.getHandler().post(a.mFinishCallback);
                         }
@@ -166,6 +169,14 @@
             }
         });
         anim.start();
+        if (a.mAnimSpec.canSkipFirstFrame()) {
+            // If we can skip the first frame, we start one frame later.
+            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
+        }
+
+        // Immediately start the animation by manually applying an animation frame. Otherwise, the
+        // start time would only be set in the next frame, leading to a delay.
+        anim.doAnimationFrame(mChoreographer.getFrameTime());
         a.mAnim = anim;
         mRunningAnimations.put(a.mLeash, a);
     }
@@ -189,6 +200,7 @@
     }
 
     private void applyTransaction() {
+        mFrameTransaction.setAnimationTransaction();
         mFrameTransaction.apply();
         mApplyScheduled = false;
     }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index e165211..bda5bc9 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -22,10 +22,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 
 /**
@@ -41,7 +44,9 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
     private final WindowManagerService mService;
     private AnimationAdapter mAnimation;
-    private SurfaceControl mLeash;
+
+    @VisibleForTesting
+    SurfaceControl mLeash;
     private final Animatable mAnimatable;
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     private final Runnable mAnimationFinishedCallback;
@@ -62,6 +67,11 @@
     private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback) {
         return anim -> {
             synchronized (mService.mWindowMap) {
+                final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
+                if (target != null) {
+                    target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
+                    return;
+                }
                 if (anim != mAnimation) {
                     // Callback was from another animation - ignore.
                     return;
@@ -70,7 +80,7 @@
                 final Transaction t = new Transaction();
                 SurfaceControl.openTransaction();
                 try {
-                    reset(t);
+                    reset(t, true /* destroyLeash */);
                     animationFinishedCallback.run();
                 } finally {
                     // TODO: This should use pendingTransaction eventually, but right now things
@@ -95,7 +105,7 @@
      *               handing it to the component that is responsible to run the animation.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        cancelAnimation(t, true /* restarting */);
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         if (surface == null) {
@@ -158,7 +168,8 @@
      * Cancels any currently running animation.
      */
     void cancelAnimation() {
-        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */);
+        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */,
+                true /* forwardCancel */);
         mAnimatable.commitPendingTransaction();
     }
 
@@ -197,13 +208,47 @@
         return mLeash != null;
     }
 
-    private void cancelAnimation(Transaction t, boolean restarting) {
+    void transferAnimation(SurfaceAnimator from) {
+        if (from.mLeash == null) {
+            return;
+        }
+        final SurfaceControl surface = mAnimatable.getSurfaceControl();
+        final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
+        if (surface == null || parent == null) {
+            Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
+            cancelAnimation();
+            return;
+        }
+        endDelayingAnimationStart();
+        final Transaction t = mAnimatable.getPendingTransaction();
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
+        mLeash = from.mLeash;
+        mAnimation = from.mAnimation;
+
+        // Cancel source animation, but don't let animation runner cancel the animation.
+        from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
+        t.reparent(surface, mLeash.getHandle());
+        t.reparent(mLeash, parent.getHandle());
+        mAnimatable.onAnimationLeashCreated(t, mLeash);
+        mService.mAnimationTransferMap.put(mAnimation, this);
+    }
+
+    /**
+     * Cancels the animation, and resets the leash.
+     *
+     * @param t The transaction to use for all cancelling surface operations.
+     * @param restarting Whether we are restarting the animation.
+     * @param forwardCancel Whether to forward the cancel signal to the adapter executing the
+     *                      animation. This will be set to false when just transferring an animation
+     *                      to another animator.
+     */
+    private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
-        reset(t);
+        reset(t, forwardCancel);
         if (animation != null) {
-            if (!mAnimationStartDelayed) {
+            if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
             if (!restarting) {
@@ -215,7 +260,7 @@
         }
     }
 
-    private void reset(Transaction t) {
+    private void reset(Transaction t, boolean destroyLeash) {
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
 
@@ -225,7 +270,8 @@
             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
             t.reparent(surface, parent.getHandle());
         }
-        if (mLeash != null) {
+        mService.mAnimationTransferMap.remove(mAnimation);
+        if (mLeash != null && destroyLeash) {
             mAnimatable.destroyAfterPendingTransaction(mLeash);
         }
         mLeash = null;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 244eb66..3c96ca1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,14 +536,7 @@
     /** Cancels any running app transitions associated with the task. */
     void cancelTaskWindowTransition() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearAnimation();
-        }
-    }
-
-    /** Cancels any running thumbnail transitions associated with the task. */
-    void cancelTaskThumbnailTransition() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearThumbnail();
+            mChildren.get(i).cancelAnimation();
         }
     }
 
@@ -656,6 +649,9 @@
         mDimmer.resetDimStates();
         super.prepareSurfaces();
         getDimBounds(mTmpDimBoundsRect);
+
+        // Bounds need to be relative, as the dim layer is a child.
+        mTmpDimBoundsRect.offsetTo(0, 0);
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
@@ -677,7 +673,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
 
         pw.println(prefix + "taskId=" + mTaskId);
@@ -691,7 +689,7 @@
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken wtoken = mChildren.get(i);
             pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
-            wtoken.dump(pw, triplePrefix);
+            wtoken.dump(pw, triplePrefix, dumpAll);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c091157..f79719c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -93,6 +93,8 @@
     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
     private final Handler mHandler = new Handler();
 
+    private final Rect mTmpRect = new Rect();
+
     /**
      * Flag indicating whether we are running on an Android TV device.
      */
@@ -223,11 +225,11 @@
 
         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
-        final Rect taskFrame = new Rect();
-        task.getBounds(taskFrame);
+        task.getBounds(mTmpRect);
+        mTmpRect.offsetTo(0, 0);
 
         final GraphicBuffer buffer = SurfaceControl.captureLayers(
-                task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+                task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
 
         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
             if (DEBUG_SCREENSHOT) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9946c6a..3ffc7fa 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -31,9 +31,8 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
@@ -220,6 +219,8 @@
         }
         alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
         mDisplayContent.setLayoutNeeded();
+
+        updateSurfaceBounds();
     }
 
     private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -241,10 +242,11 @@
             return;
         }
         getRawBounds(mTmpRect);
-        // TODO: Should be in relative coordinates.
-        getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
-                mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
-                mTmpRect.top);
+        final Rect stackBounds = getBounds();
+        getPendingTransaction()
+                .setSize(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
+                .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
+                        mTmpRect.top - stackBounds.top);
         scheduleAnimation();
     }
 
@@ -297,6 +299,7 @@
 
         updateAdjustedBounds();
 
+        updateSurfaceBounds();
         return result;
     }
 
@@ -317,7 +320,7 @@
         if (matchParentBounds()
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getSplitScreenPrimaryStack() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null) {
             return true;
         }
         return false;
@@ -711,8 +714,12 @@
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWindowingMode = getWindowingMode();
+        // Only need to update surface size here since the super method will handle updating
+        // surface position.
+        updateSurfaceSize(getPendingTransaction());
         super.onConfigurationChanged(newParentConfig);
         final int windowingMode = getWindowingMode();
+
         if (mDisplayContent == null || prevWindowingMode == windowingMode) {
             return;
         }
@@ -720,6 +727,25 @@
         updateBoundsForWindowModeChange();
     }
 
+    private void updateSurfaceBounds() {
+        updateSurfaceBounds(getPendingTransaction());
+        scheduleAnimation();
+    }
+
+    void updateSurfaceBounds(SurfaceControl.Transaction transaction) {
+        updateSurfaceSize(transaction);
+        updateSurfacePosition(transaction);
+    }
+
+    private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        final Rect stackBounds = getBounds();
+        transaction.setSize(mSurfaceControl, stackBounds.width(), stackBounds.height());
+    }
+
     @Override
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null) {
@@ -1284,7 +1310,8 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "mStackId=" + mStackId);
         pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
         pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
@@ -1300,7 +1327,7 @@
             pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
         }
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).dump(prefix + "  ", pw);
+            mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
         }
         if (mAnimationBackgroundSurfaceIsShown) {
             pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
@@ -1313,7 +1340,7 @@
                 pw.print("  Exiting App #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
     }
@@ -1653,35 +1680,6 @@
         return super.checkCompleteDeferredRemoval();
     }
 
-    void stepAppWindowsAnimation(long currentTime) {
-        super.stepAppWindowsAnimation(currentTime);
-
-        // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
-        // below but is set in the loop above. See if it really matters...
-
-        // Clear before using.
-        mTmpAppTokens.clear();
-        // We copy the list as things can be removed from the exiting token list while we are
-        // processing.
-        mTmpAppTokens.addAll(mExitingAppTokens);
-        for (int i = 0; i < mTmpAppTokens.size(); i++) {
-            final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
-            appAnimator.wasAnimating = appAnimator.animating;
-            if (appAnimator.stepAnimationLocked(currentTime)) {
-                mService.mAnimator.setAnimating(true);
-                mService.mAnimator.mAppWindowAnimating = true;
-            } else if (appAnimator.wasAnimating) {
-                // stopped animating, do one more pass through the layout
-                appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                        "exiting appToken " + appAnimator.mAppToken + " done");
-                if (DEBUG_ANIM) Slog.v(TAG_WM,
-                        "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
-            }
-        }
-        // Clear to avoid holding reference to tokens.
-        mTmpAppTokens.clear();
-    }
-
     @Override
     int getOrientation() {
         return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
@@ -1705,6 +1703,9 @@
         mDimmer.resetDimStates();
         super.prepareSurfaces();
         getDimBounds(mTmpDimBoundsRect);
+
+        // Bounds need to be relative, as the dim layer is a child.
+        mTmpDimBoundsRect.offsetTo(0, 0);
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 5caae32..d83f28c 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -199,16 +199,6 @@
         }
     }
 
-    public void cancelThumbnailTransition() {
-        synchronized (mWindowMap) {
-            if (mContainer == null) {
-                Slog.w(TAG_WM, "cancelThumbnailTransition: taskId " + mTaskId + " not found.");
-                return;
-            }
-            mContainer.cancelTaskThumbnailTransition();
-        }
-    }
-
     public void setTaskDescription(TaskDescription taskDescription) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3ae4549..ac0919d 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -64,8 +64,6 @@
     // to another, and this is the previous wallpaper target.
     private WindowState mPrevWallpaperTarget = null;
 
-    private int mWallpaperAnimLayerAdjustment;
-
     private float mLastWallpaperX = -1;
     private float mLastWallpaperY = -1;
     private float mLastWallpaperXStep = -1;
@@ -112,7 +110,7 @@
         mFindResults.resetTopWallpaper = true;
         if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
             // If this window's app token is hidden and not animating, it is of no interest to us.
-            if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+            if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) {
                 if (DEBUG_WALLPAPER) Slog.v(TAG,
                         "Skipping hidden and not animating token: " + w);
                 return false;
@@ -130,10 +128,10 @@
         }
 
         final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
-                && AppTransition.isKeyguardGoingAwayTransit(
-                w.mAppToken.mAppAnimator.getTransit())
-                && (w.mAppToken.mAppAnimator.getTransitFlags()
-                & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
+                && w.mAppToken.isSelfAnimating()
+                && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit())
+                && (w.mAppToken.getTransitFlags()
+                        & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
 
         boolean needsShowWhenLockedWallpaper = false;
         if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
@@ -204,18 +202,19 @@
     private boolean isWallpaperVisible(WindowState wallpaperTarget) {
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
                 + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
-                + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
-                ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+                + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+                ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
                 + " prev=" + mPrevWallpaperTarget);
         return (wallpaperTarget != null
                 && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
-                && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+                && wallpaperTarget.mAppToken.isSelfAnimating())))
                 || mPrevWallpaperTarget != null;
     }
 
     boolean isWallpaperTargetAnimating() {
         return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet()
-                && !mWallpaperTarget.mWinAnimator.isDummyAnimation();
+                && (mWallpaperTarget.mAppToken == null
+                        || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart());
     }
 
     void updateWallpaperVisibility() {
@@ -250,7 +249,7 @@
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
             token.hideWallpaperToken(wasDeferred, "hideWallpapers");
-            if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+            if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token
                     + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
                     + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, "  "));
         }
@@ -441,10 +440,6 @@
         }
     }
 
-    int getAnimLayerAdjustment() {
-        return mWallpaperAnimLayerAdjustment;
-    }
-
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
         if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
@@ -546,7 +541,7 @@
     private void updateWallpaperTokens(boolean visible) {
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.updateWallpaperWindows(visible, mWallpaperAnimLayerAdjustment);
+            token.updateWallpaperWindows(visible);
             token.getDisplayContent().assignWindowLayers(false);
         }
     }
@@ -565,12 +560,6 @@
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible);
 
         if (visible) {
-            // If the wallpaper target is animating, we may need to copy its layer adjustment.
-            // Only do this if we are not transferring between two wallpaper targets.
-            mWallpaperAnimLayerAdjustment =
-                    (mPrevWallpaperTarget == null && mWallpaperTarget.mAppToken != null)
-                            ? mWallpaperTarget.mAppToken.getAnimLayerAdjustment() : 0;
-
             if (mWallpaperTarget.mWallpaperX >= 0) {
                 mLastWallpaperX = mWallpaperTarget.mWallpaperX;
                 mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
@@ -681,10 +670,6 @@
             pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
             pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
         }
-
-        if (mWallpaperAnimLayerAdjustment != 0) {
-            pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment);
-        }
     }
 
     /** Helper class for storing the results of a wallpaper target find operation. */
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3389f71..2ae5c7b 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,12 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -56,7 +53,7 @@
             final WindowState wallpaper = mChildren.get(j);
             wallpaper.hideWallpaperWindow(wasDeferred, reason);
         }
-        hidden = true;
+        setHidden(true);
     }
 
     void sendWindowWallpaperCommand(
@@ -92,8 +89,9 @@
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
 
-        if (hidden == visible) {
-            hidden = !visible;
+        if (isHidden() == visible) {
+            setHidden(!visible);
+
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
@@ -119,12 +117,12 @@
         }
     }
 
-    void updateWallpaperWindows(boolean visible, int animLayerAdj) {
+    void updateWallpaperWindows(boolean visible) {
 
-        if (hidden == visible) {
+        if (isHidden() == visible) {
             if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
                     "Wallpaper token " + token + " hidden=" + !visible);
-            hidden = !visible;
+            setHidden(!visible);
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index bb25297..9865293 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -16,11 +16,20 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+
 import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
 
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
@@ -32,10 +41,26 @@
     private Animation mAnimation;
     private final Point mPosition = new Point();
     private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+    private final boolean mCanSkipFirstFrame;
+    private final Rect mStackBounds = new Rect();
+    private int mStackClipMode;
+    private final Rect mTmpRect = new Rect();
 
-    public WindowAnimationSpec(Animation animation, Point position)  {
+    public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame)  {
+        this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE);
+    }
+
+    public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds,
+            boolean canSkipFirstFrame, int stackClipMode) {
         mAnimation = animation;
-        mPosition.set(position.x, position.y);
+        if (position != null) {
+            mPosition.set(position.x, position.y);
+        }
+        mCanSkipFirstFrame = canSkipFirstFrame;
+        mStackClipMode = stackClipMode;
+        if (stackBounds != null) {
+            mStackBounds.set(stackBounds);
+        }
     }
 
     @Override
@@ -61,6 +86,77 @@
         tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
         t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
         t.setAlpha(leash, tmp.transformation.getAlpha());
+        if (mStackClipMode == STACK_CLIP_NONE) {
+            t.setWindowCrop(leash, tmp.transformation.getClipRect());
+        } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) {
+            t.setFinalCrop(leash, mStackBounds);
+            t.setWindowCrop(leash, tmp.transformation.getClipRect());
+        } else {
+            mTmpRect.set(tmp.transformation.getClipRect());
+            mTmpRect.intersect(mStackBounds);
+            t.setWindowCrop(leash, mTmpRect);
+        }
+    }
+
+    @Override
+    public long calculateStatusBarTransitionStartTime() {
+        TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
+        if (openTranslateAnimation != null) {
+
+            // Some interpolators are extremely quickly mostly finished, but not completely. For
+            // our purposes, we need to find the fraction for which ther interpolator is mostly
+            // there, and use that value for the calculation.
+            float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+            return SystemClock.uptimeMillis()
+                    + openTranslateAnimation.getStartOffset()
+                    + (long)(openTranslateAnimation.getDuration() * t)
+                    - STATUS_BAR_TRANSITION_DURATION;
+        } else {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    @Override
+    public boolean canSkipFirstFrame() {
+        return mCanSkipFirstFrame;
+    }
+
+    /**
+     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
+     *
+     * @return the found animation, {@code null} otherwise
+     */
+    private static TranslateAnimation findTranslateAnimation(Animation animation) {
+        if (animation instanceof TranslateAnimation) {
+            return (TranslateAnimation) animation;
+        } else if (animation instanceof AnimationSet) {
+            AnimationSet set = (AnimationSet) animation;
+            for (int i = 0; i < set.getAnimations().size(); i++) {
+                Animation a = set.getAnimations().get(i);
+                if (a instanceof TranslateAnimation) {
+                    return (TranslateAnimation) a;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
+     * {@code interpolator(t + eps) > 0.99}.
+     */
+    private static float findAlmostThereFraction(Interpolator interpolator) {
+        float val = 0.5f;
+        float adj = 0.25f;
+        while (adj >= 0.01f) {
+            if (interpolator.getInterpolation(val) < 0.99f) {
+                val += adj;
+            } else {
+                val -= adj;
+            }
+            adj /= 2;
+        }
+        return val;
     }
 
     private static class TmpValues {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 7c56f00..8bceb64 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -50,16 +50,14 @@
 
     /** Is any window animating? */
     private boolean mAnimating;
-    private boolean mLastAnimating;
-
-    /** Is any app window animating? */
-    boolean mAppWindowAnimating;
+    private boolean mLastRootAnimating;
 
     final Choreographer.FrameCallback mAnimationFrameCallback;
 
     /** Time of current animation step. Reset on each iteration */
     long mCurrentTime;
 
+    boolean mAppWindowAnimating;
     /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
      * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
     int mAnimTransactionSequence;
@@ -142,21 +140,10 @@
             scheduleAnimation();
         }
 
-        // Simulate back-pressure by opening and closing an empty animation transaction. This makes
-        // sure that an animation frame is at least presented once on the screen. We do this outside
-        // of the regular transaction such that we can avoid holding the window manager lock in case
-        // we receive back-pressure from SurfaceFlinger. Since closing an animation transaction
-        // without the window manager locks leads to ordering issues (as the transaction will be
-        // processed only at the beginning of the next frame which may result in another transaction
-        // that was executed later in WM side gets executed first on SF side), we don't update any
-        // Surface properties here such that reordering doesn't cause issues.
-        mService.executeEmptyAnimationTransaction();
-
         synchronized (mService.mWindowMap) {
             mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
             mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
             mAnimating = false;
-            mAppWindowAnimating = false;
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
             }
@@ -170,7 +157,6 @@
                 for (int i = 0; i < numDisplays; i++) {
                     final int displayId = mDisplayContentsAnimators.keyAt(i);
                     final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
-                    dc.stepAppWindowsAnimation(mCurrentTime);
                     DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
 
                     final ScreenRotationAnimation screenRotationAnimation =
@@ -251,7 +237,8 @@
                 mWindowPlacerLocked.requestTraversal();
             }
 
-            if (mAnimating && !mLastAnimating) {
+            final boolean rootAnimating = mService.mRoot.isSelfOrChildAnimating();
+            if (rootAnimating && !mLastRootAnimating) {
 
                 // Usually app transitions but quite a load onto the system already (with all the
                 // things happening in app), so pause task snapshot persisting to not increase the
@@ -259,13 +246,13 @@
                 mService.mTaskSnapshotController.setPersisterPaused(true);
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
             }
-            if (!mAnimating && mLastAnimating) {
+            if (!rootAnimating && mLastRootAnimating) {
                 mWindowPlacerLocked.requestTraversal();
                 mService.mTaskSnapshotController.setPersisterPaused(false);
                 Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
             }
 
-            mLastAnimating = mAnimating;
+            mLastRootAnimating = rootAnimating;
 
             if (mRemoveReplacedWindows) {
                 mService.mRoot.removeReplacedWindows();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b2b6119..b251b53 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,7 +29,8 @@
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
-import android.graphics.PixelFormat.Opacity;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.Slog;
 import android.view.MagnificationSpec;
 import android.view.SurfaceControl;
@@ -93,6 +94,11 @@
     protected final SurfaceAnimator mSurfaceAnimator;
     protected final WindowManagerService mService;
 
+    private final Point mTmpPos = new Point();
+
+    /** Total number of elements in this subtree, including our own hierarchy element. */
+    private int mTreeWeight = 1;
+
     WindowContainer(WindowManagerService service) {
         mService = service;
         mPendingTransaction = service.mTransactionFactory.make();
@@ -114,6 +120,13 @@
         return mChildren.get(index);
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        super.onConfigurationChanged(newParentConfig);
+        updateSurfacePosition(getPendingTransaction());
+        scheduleAnimation();
+    }
+
     final protected void setParent(WindowContainer<WindowContainer> parent) {
         mParent = parent;
         // Removing parent usually means that we've detached this entity to destroy it or to attach
@@ -147,7 +160,7 @@
             // surface animator such that hierarchy is preserved when animating, i.e.
             // mSurfaceControl stays attached to the leash and we just reparent the leash to the
             // new parent.
-            mSurfaceAnimator.reparent(getPendingTransaction(), mParent.mSurfaceControl);
+            reparentSurfaceControl(getPendingTransaction(), mParent.mSurfaceControl);
         }
 
         // Either way we need to ask the parent to assign us a Z-order.
@@ -190,6 +203,8 @@
         } else {
             mChildren.add(positionToAdd, child);
         }
+        onChildAdded(child);
+
         // Set the parent after we've actually added a child in case a subclass depends on this.
         child.setParent(this);
     }
@@ -203,10 +218,21 @@
                     + " can't add to container=" + getName());
         }
         mChildren.add(index, child);
+        onChildAdded(child);
+
         // Set the parent after we've actually added a child in case a subclass depends on this.
         child.setParent(this);
     }
 
+    private void onChildAdded(WindowContainer child) {
+        mTreeWeight += child.mTreeWeight;
+        WindowContainer parent = getParent();
+        while (parent != null) {
+            parent.mTreeWeight += child.mTreeWeight;
+            parent = parent.getParent();
+        }
+    }
+
     /**
      * Removes the input child container from this container which is its parent.
      *
@@ -215,6 +241,7 @@
     @CallSuper
     void removeChild(E child) {
         if (mChildren.remove(child)) {
+            onChildRemoved(child);
             child.setParent(null);
         } else {
             throw new IllegalArgumentException("removeChild: container=" + child.getName()
@@ -222,6 +249,15 @@
         }
     }
 
+    private void onChildRemoved(WindowContainer child) {
+        mTreeWeight -= child.mTreeWeight;
+        WindowContainer parent = getParent();
+        while (parent != null) {
+            parent.mTreeWeight -= child.mTreeWeight;
+            parent = parent.getParent();
+        }
+    }
+
     /**
      * Removes this window container and its children with no regard for what else might be going on
      * in the system. For example, the container will be removed during animation if this method is
@@ -236,7 +272,9 @@
             // Need to do this after calling remove on the child because the child might try to
             // remove/detach itself from its parent which will cause an exception if we remove
             // it before calling remove on the child.
-            mChildren.remove(child);
+            if (mChildren.remove(child)) {
+                onChildRemoved(child);
+            }
         }
 
         if (mSurfaceControl != null) {
@@ -255,6 +293,34 @@
     }
 
     /**
+     * @return The index of this element in the hierarchy tree in prefix order.
+     */
+    int getPrefixOrderIndex() {
+        if (mParent == null) {
+            return 0;
+        }
+        return mParent.getPrefixOrderIndex(this);
+    }
+
+    private int getPrefixOrderIndex(WindowContainer child) {
+        int order = 0;
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowContainer childI = mChildren.get(i);
+            if (child == childI) {
+                break;
+            }
+            order += childI.mTreeWeight;
+        }
+        if (mParent != null) {
+            order += mParent.getPrefixOrderIndex(this);
+        }
+
+        // We also need to count ourselves.
+        order++;
+        return order;
+    }
+
+    /**
      * Removes this window container and its children taking care not to remove them during a
      * critical stage in the system. For example, some containers will not be removed during
      * animation if this method is called.
@@ -446,6 +512,20 @@
     }
 
     /**
+     * @return {@code true} if in this subtree of the hierarchy we have an {@link AppWindowToken}
+     *         that is {@link #isSelfAnimating}; {@code false} otherwise.
+     */
+    boolean isAppAnimating() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.isAppAnimating()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * @return Whether our own container running an animation at the moment.
      */
     boolean isSelfAnimating() {
@@ -532,14 +612,6 @@
         }
     }
 
-    /** Step currently ongoing animation for App window containers. */
-    void stepAppWindowsAnimation(long currentTime) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            wc.stepAppWindowsAnimation(currentTime);
-        }
-    }
-
     void onAppTransitionDone() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
@@ -815,10 +887,7 @@
     void assignLayer(Transaction t, int layer) {
         final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
         if (mSurfaceControl != null && changed) {
-
-            // Route through surface animator to accommodate that our surface control might be
-            // attached to the leash, and leash is attached to parent container.
-            mSurfaceAnimator.setLayer(t, layer);
+            setLayer(t, layer);
             mLastLayer = layer;
             mLastRelativeToLayer = null;
         }
@@ -827,15 +896,30 @@
     void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
         final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
         if (mSurfaceControl != null && changed) {
-
-            // Route through surface animator to accommodate that our surface control might be
-            // attached to the leash, and leash is attached to parent container.
-            mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+            setRelativeLayer(t, relativeTo, layer);
             mLastLayer = layer;
             mLastRelativeToLayer = relativeTo;
         }
     }
 
+    protected void setLayer(Transaction t, int layer) {
+
+        // Route through surface animator to accommodate that our surface control might be
+        // attached to the leash, and leash is attached to parent container.
+        mSurfaceAnimator.setLayer(t, layer);
+    }
+
+    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+
+        // Route through surface animator to accommodate that our surface control might be
+        // attached to the leash, and leash is attached to parent container.
+        mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+    }
+
+    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+        mSurfaceAnimator.reparent(t, newParent);
+    }
+
     void assignChildLayers(Transaction t) {
         int layer = 0;
 
@@ -991,6 +1075,10 @@
         mSurfaceAnimator.startAnimation(t, anim, hidden);
     }
 
+    void transferAnimation(WindowContainer from) {
+        mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
+    }
+
     void cancelAnimation() {
         mSurfaceAnimator.cancelAnimation();
     }
@@ -1000,6 +1088,17 @@
         return makeSurface();
     }
 
+    /**
+     * @return The layer on which all app animations are happening.
+     */
+    SurfaceControl getAppAnimationLayer() {
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            return parent.getAppAnimationLayer();
+        }
+        return null;
+    }
+
     @Override
     public void commitPendingTransaction() {
         scheduleAnimation();
@@ -1059,10 +1158,34 @@
         return mSurfaceControl.getHeight();
     }
 
+    @CallSuper
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         if (mSurfaceAnimator.isAnimating()) {
             pw.print(prefix); pw.println("ContainerAnimator:");
             mSurfaceAnimator.dump(pw, prefix + "  ");
         }
     }
+
+    void updateSurfacePosition(SurfaceControl.Transaction transaction) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        getRelativePosition(mTmpPos);
+        transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            mChildren.get(i).updateSurfacePosition(transaction);
+        }
+    }
+
+    void getRelativePosition(Point outPos) {
+        final Rect bounds = getBounds();
+        outPos.set(bounds.left, bounds.top);
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Rect parentBounds = parent.getBounds();
+            outPos.offset(-parentBounds.left, -parentBounds.top);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62d2e7d..1935a44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,11 @@
          *                of AppTransition.TRANSIT_* values
          * @param openToken the token for the opening app
          * @param closeToken the token for the closing app
-         * @param openAnimation the animation for the opening app
-         * @param closeAnimation the animation for the closing app
+         * @param duration the total duration of the transition
+         * @param statusBarAnimationStartTime the desired start time for all visual animations in
+         *        the status bar caused by this app transition in uptime millis
+         * @param statusBarAnimationDuration the duration for all visual animations in the status
+         *        bar caused by this app transition in millis
          *
          * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
@@ -127,7 +130,7 @@
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
         public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
-                Animation openAnimation, Animation closeAnimation) {
+                long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9fc9f3c..0a2ffbc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -71,13 +71,9 @@
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
@@ -177,6 +173,7 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -566,12 +563,10 @@
     int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
-    private final SparseIntArray mTmpTaskIds = new SparseIntArray();
-
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
 
-    private boolean mDisableTransitionAnimation = false;
+    boolean mDisableTransitionAnimation = false;
 
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -769,6 +764,11 @@
     final WindowAnimator mAnimator;
     final SurfaceAnimationRunner mSurfaceAnimationRunner;
 
+    /**
+     * Keeps track of which animations got transferred to which animators. Entries will get cleaned
+     * up when the animation finishes.
+     */
+    final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>();
     final BoundsAnimationController mBoundsAnimationController;
 
     private final PointerEventDispatcher mPointerEventDispatcher;
@@ -852,35 +852,6 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
-
-    /**
-     * Executes an empty animation transaction without holding the WM lock to simulate
-     * back-pressure. See {@link WindowAnimator#animate} why this is needed.
-     */
-    void executeEmptyAnimationTransaction() {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
-            synchronized (mWindowMap) {
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.openSurfaceTransaction();
-                }
-                SurfaceControl.openTransaction();
-                SurfaceControl.setAnimationTransaction();
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
-                }
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
-            SurfaceControl.closeTransaction();
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
             = new WindowManagerInternal.AppTransitionListener() {
@@ -2279,83 +2250,6 @@
         }
     }
 
-    boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
-            int transit, boolean enter, boolean isVoiceInteraction) {
-        if (mDisableTransitionAnimation) {
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
-                Slog.v(TAG_WM,
-                        "applyAnimation: transition animation is disabled. atoken=" + atoken);
-            }
-            atoken.mAppAnimator.clearAnimation();
-            return false;
-        }
-        // Only apply an animation if the display isn't frozen.  If it is
-        // frozen, there is no reason to animate and it can cause strange
-        // artifacts when we unfreeze the display if some different animation
-        // is running.
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
-        if (atoken.okToAnimate()) {
-            final DisplayContent displayContent = atoken.getTask().getDisplayContent();
-            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            final int width = displayInfo.appWidth;
-            final int height = displayInfo.appHeight;
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
-                    "applyAnimation: atoken=" + atoken);
-
-            // Determine the visible rect to calculate the thumbnail clip
-            final WindowState win = atoken.findMainWindow();
-            final Rect frame = new Rect(0, 0, width, height);
-            final Rect displayFrame = new Rect(0, 0,
-                    displayInfo.logicalWidth, displayInfo.logicalHeight);
-            final Rect insets = new Rect();
-            final Rect stableInsets = new Rect();
-            Rect surfaceInsets = null;
-            final boolean freeform = win != null && win.inFreeformWindowingMode();
-            if (win != null) {
-                // Containing frame will usually cover the whole screen, including dialog windows.
-                // For freeform workspace windows it will not cover the whole screen and it also
-                // won't exactly match the final freeform window frame (e.g. when overlapping with
-                // the status bar). In that case we need to use the final frame.
-                if (freeform) {
-                    frame.set(win.mFrame);
-                } else {
-                    frame.set(win.mContainingFrame);
-                }
-                surfaceInsets = win.getAttrs().surfaceInsets;
-                insets.set(win.mContentInsets);
-                stableInsets.set(win.mStableInsets);
-            }
-
-            if (atoken.mLaunchTaskBehind) {
-                // Differentiate the two animations. This one which is briefly on the screen
-                // gets the !enter animation, and the other activity which remains on the
-                // screen gets the enter animation. Both appear in the mOpeningApps set.
-                enter = false;
-            }
-            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
-                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
-                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
-            final Configuration displayConfig = displayContent.getConfiguration();
-            Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
-                    displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
-                    stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId);
-            if (a != null) {
-                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
-                final int containingWidth = frame.width();
-                final int containingHeight = frame.height();
-                atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width,
-                        height, mAppTransition.canSkipFirstFrame(),
-                        mAppTransition.getAppStackClipMode(),
-                        transit, mAppTransition.getTransitFlags());
-            }
-        } else {
-            atoken.mAppAnimator.clearAnimation();
-        }
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-        return atoken.mAppAnimator.animation != null;
-    }
-
     boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == myPid()) {
@@ -2701,29 +2595,13 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            prolongAnimationsFromSpecs(specs, scaleUp);
 
         }
     }
 
-    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
-        // This is used by freeform <-> recents windows transition. We need to synchronize
-        // the animation with the appearance of the content of recents, so we will make
-        // animation stay on the first or last frame a little longer.
-        mTmpTaskIds.clear();
-        for (int i = specs.length - 1; i >= 0; i--) {
-            mTmpTaskIds.put(specs[i].taskId, 0);
-        }
-        for (final WindowState win : mWindowMap.values()) {
-            final Task task = win.getTask();
-            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
-                    && task.inFreeformWindowingMode()) {
-                final AppWindowToken appToken = win.mAppToken;
-                if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
-                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
-                }
-            }
+    public void overridePendingAppTransitionStartCrossProfileApps() {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionStartCrossProfileApps();
         }
     }
 
@@ -2749,8 +2627,8 @@
         synchronized (mWindowMap) {
             for (final WindowState win : mWindowMap.values()) {
                 final AppWindowToken appToken = win.mAppToken;
-                if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.endProlongedAnimation();
+                if (appToken != null) {
+                    appToken.endDelayingAnimationStart();
                 }
             }
             mAppTransition.notifyProlongedAnimationsEnded();
@@ -2805,15 +2683,6 @@
         }
     }
 
-    void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
-        if (transit != TRANSIT_UNSET) {
-            if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                wtoken.mAppAnimator.setNullAnimation();
-            }
-            applyAnimationLocked(wtoken, null, transit, false, false);
-        }
-    }
-
     public void setDockedStackCreateState(int mode, Rect bounds) {
         synchronized (mWindowMap) {
             setDockedStackCreateStateLocked(mode, bounds);
@@ -5608,7 +5477,9 @@
 
     /** Note that Locked in this case is on mLayoutToAnim */
     void scheduleAnimationLocked() {
-        mAnimator.scheduleAnimation();
+        if (mAnimator != null) {
+            mAnimator.scheduleAnimation();
+        }
     }
 
     // TODO: Move to DisplayContent
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 1b2eb46..dd89b3b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -36,6 +36,7 @@
     private final Object mLock = new Object();
 
     private final int mAnimationThreadId;
+    private final int mSurfaceAnimationThreadId;
 
     @GuardedBy("mLock")
     private boolean mAppTransitionRunning;
@@ -45,14 +46,16 @@
     WindowManagerThreadPriorityBooster() {
         super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
         mAnimationThreadId = AnimationThread.get().getThreadId();
+        mSurfaceAnimationThreadId = SurfaceAnimationThread.get().getThreadId();
     }
 
     @Override
     public void boost() {
 
-        // Do not boost the animation thread. As the animation thread is changing priorities,
+        // Do not boost the animation threads. As the animation threads are changing priorities,
         // boosting it might mess up the priority because we reset it the the previous priority.
-        if (myTid() == mAnimationThreadId) {
+        final int myTid = myTid();
+        if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
             return;
         }
         super.boost();
@@ -62,7 +65,8 @@
     public void reset() {
 
         // See comment in boost().
-        if (myTid() == mAnimationThreadId) {
+        final int myTid = myTid();
+        if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
             return;
         }
         super.reset();
@@ -92,5 +96,6 @@
                 ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
         setBoostToPriority(priority);
         setThreadPriority(mAnimationThreadId, priority);
+        setThreadPriority(mSurfaceAnimationThreadId, priority);
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ddc1eac..a5c46e0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1432,7 +1432,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating())
                 && isVisible();
     }
 
@@ -1441,7 +1441,7 @@
      * not the pending requested hidden state.
      */
     boolean isVisibleNow() {
-        return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+        return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING)
                 && isVisible();
     }
 
@@ -1479,7 +1479,7 @@
         final AppWindowToken atoken = mAppToken;
         if (atoken != null) {
             return ((!isParentWindowHidden() && !atoken.hiddenRequested)
-                    || mWinAnimator.isAnimationSet() || atoken.mAppAnimator.animation != null);
+                    || mWinAnimator.isAnimationSet());
         }
         return !isParentWindowHidden() || mWinAnimator.isAnimationSet();
     }
@@ -1501,7 +1501,7 @@
      */
     boolean isInteresting() {
         return mAppToken != null && !mAppDied
-                && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+                && (!mAppToken.isFreezingScreen() || !mAppFreezing);
     }
 
     /**
@@ -1513,33 +1513,21 @@
             return false;
         }
         return mHasSurface && mPolicyVisibility && !mDestroying
-                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
-                        || mWinAnimator.isAnimationSet()
-                        || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden())
+                        || mWinAnimator.isAnimationSet());
     }
 
     // TODO: Another visibility method that was added late in the release to minimize risk.
     @Override
     public boolean canAffectSystemUiFlags() {
-        final boolean shown = mWinAnimator.getShown();
-
-        // We only consider the app to be exiting when the animation has started. After the app
-        // transition is executed the windows are marked exiting before the new windows have been
-        // shown. Thus, wait considering a window to be exiting after the animation has actually
-        // started.
-        final boolean appAnimationStarting = mAppToken != null
-                && mAppToken.mAppAnimator.isAnimationStarting();
-        final boolean exitingSelf = mAnimatingExit && !appAnimationStarting;
-        final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting;
-
-        final boolean exiting = exitingSelf || mDestroying || appExiting;
         final boolean translucent = mAttrs.alpha == 0.0f;
-
-        // If we are entering with a dummy animation, avoid affecting SystemUI flags until the
-        // transition is starting.
-        final boolean enteringWithDummyAnimation =
-                mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f;
-        return shown && !exiting && !translucent && !enteringWithDummyAnimation;
+        if (mAppToken == null) {
+            final boolean shown = mWinAnimator.getShown();
+            final boolean exiting = mAnimatingExit || mDestroying;
+            return shown && !exiting && !translucent;
+        } else {
+            return !mAppToken.isHidden();
+        }
     }
 
     /**
@@ -1550,10 +1538,8 @@
     public boolean isDisplayedLw() {
         final AppWindowToken atoken = mAppToken;
         return isDrawnLw() && mPolicyVisibility
-            && ((!isParentWindowHidden() &&
-                    (atoken == null || !atoken.hiddenRequested))
-                        || mWinAnimator.isAnimationSet()
-                        || (atoken != null && atoken.mAppAnimator.animation != null));
+                && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested))
+                        || mWinAnimator.isAnimationSet());
     }
 
     /**
@@ -1561,8 +1547,7 @@
      */
     @Override
     public boolean isAnimatingLw() {
-        return mWinAnimator.isAnimationSet()
-                || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+        return isAnimating();
     }
 
     @Override
@@ -1570,7 +1555,7 @@
         final AppWindowToken atoken = mAppToken;
         return mViewVisibility == View.GONE
                 || !mRelayoutCalled
-                || (atoken == null && mToken.hidden)
+                || (atoken == null && mToken.isHidden())
                 || (atoken != null && atoken.hiddenRequested)
                 || isParentWindowHidden()
                 || (mAnimatingExit && !isAnimatingLw())
@@ -1608,8 +1593,7 @@
         // to determine if it's occluding apps.
         return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
                 || (mIsWallpaper && mWallpaperVisible))
-                && isDrawnLw() && !mWinAnimator.isAnimationSet()
-                && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+                && isDrawnLw() && !mWinAnimator.isAnimationSet();
     }
 
     @Override
@@ -1631,7 +1615,7 @@
             // Starting window that's exiting will be removed when the animation finishes.
             // Mark all relevant flags for that onExitAnimationDone will proceed all the way
             // to actually remove it.
-            if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) {
+            if (!visible && isVisibleNow() && mAppToken.isSelfAnimating()) {
                 mAnimatingExit = true;
                 mRemoveOnExit = true;
                 mWindowRemovalAllowed = true;
@@ -1908,7 +1892,7 @@
                     + " surfaceShowing=" + mWinAnimator.getShown()
                     + " isAnimationSet=" + mWinAnimator.isAnimationSet()
                     + " app-animation="
-                    + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                    + (mAppToken != null ? mAppToken.isSelfAnimating() : "false")
                     + " mWillReplaceWindow=" + mWillReplaceWindow
                     + " inPendingTransaction="
                     + (mAppToken != null ? mAppToken.inPendingTransaction : false)
@@ -1968,8 +1952,8 @@
                         mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                     }
                 }
-                final boolean isAnimating =
-                        mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+                final boolean isAnimating = mWinAnimator.isAnimationSet()
+                        && (mAppToken == null || !mAppToken.isWaitingForTransitionStart());
                 final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
                         && mAppToken.isLastWindow(this);
                 // We delay the removal of a window if it has a showing surface that can be used to run
@@ -2019,28 +2003,6 @@
         mHasSurface = hasSurface;
     }
 
-    int getAnimLayerAdjustment() {
-        if (mIsImWindow && mService.mInputMethodTarget != null) {
-            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
-            if (appToken != null) {
-                return appToken.getAnimLayerAdjustment();
-            }
-        }
-
-        return mToken.getAnimLayerAdjustment();
-    }
-
-    int getSpecialWindowAnimLayerAdjustment() {
-        int specialAdjustment = 0;
-        if (mIsImWindow) {
-            specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
-        } else if (mIsWallpaper) {
-            specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
-        }
-
-        return mLayer + specialAdjustment;
-    }
-
     boolean canBeImeTarget() {
         if (mIsImWindow) {
             // IME windows can't be IME targets. IME targets are required to be below the IME
@@ -3170,7 +3132,6 @@
             pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
                     pw.print(" mSubLayer="); pw.print(mSubLayer);
                     pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+");
-                    pw.print(getAnimLayerAdjustment());
                     pw.print("="); pw.print(mWinAnimator.mAnimLayer);
                     pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer);
         }
@@ -3697,10 +3658,10 @@
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.hiddenRequested="
                     + (mAppToken != null && mAppToken.hiddenRequested)
-                    + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+                    + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden())
                     + " animationSet=" + mWinAnimator.isAnimationSet()
                     + " tok animating="
-                    + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+                    + (mAppToken != null && mAppToken.isSelfAnimating())
                     + " Callers=" + Debug.getCallers(4));
         }
     }
@@ -4292,7 +4253,8 @@
         anim.restrictDuration(MAX_ANIMATION_DURATION);
         anim.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());
         final AnimationAdapter adapter = new LocalAnimationAdapter(
-                new WindowAnimationSpec(anim, mSurfacePosition), mService.mSurfaceAnimationRunner);
+                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */),
+                mService.mSurfaceAnimationRunner);
         startAnimation(mPendingTransaction, adapter);
         commitPendingTransaction();
     }
@@ -4415,7 +4377,13 @@
 
     @Override
     boolean needsZBoost() {
-        return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+        if (mIsImWindow && mService.mInputMethodTarget != null) {
+            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+            if (appToken != null) {
+                return appToken.needsZBoost();
+            }
+        }
+        return mWillReplaceWindow;
     }
 
     private void applyDims(Dimmer dimmer) {
@@ -4457,6 +4425,7 @@
         updateSurfacePosition(t);
     }
 
+    @Override
     void updateSurfacePosition(Transaction t) {
         if (mSurfaceControl == null) {
             return;
@@ -4470,11 +4439,15 @@
 
     private void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
         outPoint.set(left, top);
+        final WindowContainer parentWindowContainer = getParent();
         if (isChildWindow()) {
             // TODO: This probably falls apart at some point and we should
             // actually compute relative coordinates.
             final WindowState parent = getParentWindow();
             outPoint.offset(-parent.mFrame.left, -parent.mFrame.top);
+        } else if (parentWindowContainer != null) {
+            final Rect parentBounds = parentWindowContainer.getBounds();
+            outPoint.offset(-parentBounds.left, -parentBounds.top);
         }
 
         // Expand for surface insets. See WindowState.expandForSurfaceInsets.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0eabc89..d2247ac 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -46,7 +45,6 @@
 import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
 
-import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
@@ -65,7 +63,6 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
 
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -103,7 +100,6 @@
     final WindowState mWin;
     private final WindowStateAnimator mParentWinAnimator;
     final WindowAnimator mAnimator;
-    AppWindowAnimator mAppAnimator;
     final Session mSession;
     final WindowManagerPolicy mPolicy;
     final Context mContext;
@@ -228,7 +224,6 @@
 
         mWin = win;
         mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator;
-        mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
         mSession = win.mSession;
         mAttrType = win.mAttrs.type;
         mIsWallpaper = win.mIsWallpaper;
@@ -242,17 +237,12 @@
         return mWin.isAnimating();
     }
 
-    /** Is the window animating the DummyAnimation? */
-    boolean isDummyAnimation() {
-        return mAppAnimator != null
-                && mAppAnimator.animation == sDummyAnimation;
-    }
-
     /**
      * Is this window currently waiting to run an opening animation?
      */
     boolean isWaitingForOpening() {
-        return mService.mAppTransition.isTransitionSet() && isDummyAnimation()
+        return mService.mAppTransition.isTransitionSet()
+                && (mWin.mAppToken != null && mWin.mAppToken.isHidden())
                 && mService.mOpeningApps.contains(mWin.mAppToken);
     }
 
@@ -423,7 +413,7 @@
             return;
         }
 
-        if (mWin.mAppToken.mAppAnimator.animation == null) {
+        if (!mWin.mAppToken.isSelfAnimating()) {
             mWin.mAppToken.clearAllDrawn();
         } else {
             // Currently animating, persist current state of allDrawn until animation
@@ -667,8 +657,6 @@
     }
 
     void computeShownFrameLocked() {
-        Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
-                ? mAppAnimator.transformation : null;
 
         final int displayId = mWin.getDisplayId();
         final ScreenRotationAnimation screenRotationAnimation =
@@ -677,14 +665,14 @@
                 screenRotationAnimation != null && screenRotationAnimation.isAnimating();
 
         mHasClipRect = false;
-        if (appTransformation != null || screenAnimation) {
+        if (screenAnimation) {
             // cache often used attributes locally
             final Rect frame = mWin.mFrame;
             final float tmpFloats[] = mService.mTmpFloats;
             final Matrix tmpMatrix = mWin.mTmpMatrix;
 
             // Compute the desired transformation.
-            if (screenAnimation && screenRotationAnimation.isRotating()) {
+            if (screenRotationAnimation.isRotating()) {
                 // If we are doing a screen animation, the global rotation
                 // applied to windows can result in windows that are carefully
                 // aligned with each other to slightly separate, allowing you
@@ -702,6 +690,7 @@
             } else {
                 tmpMatrix.reset();
             }
+
             tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
 
             // WindowState.prepareSurfaces expands for surface insets (in order they don't get
@@ -709,9 +698,6 @@
             tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
                     mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
 
-            if (appTransformation != null) {
-                tmpMatrix.postConcat(appTransformation.getMatrix());
-            }
 
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
@@ -740,24 +726,6 @@
                     || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
                             && x == frame.left && y == frame.top))) {
                 //Slog.i(TAG_WM, "Applying alpha transform");
-                if (appTransformation != null) {
-                    mShownAlpha *= appTransformation.getAlpha();
-                    if (appTransformation.hasClipRect()) {
-                        mClipRect.set(appTransformation.getClipRect());
-                        mHasClipRect = true;
-                        // The app transformation clip will be in the coordinate space of the main
-                        // activity window, which the animation correctly assumes will be placed at
-                        // (0,0)+(insets) relative to the containing frame. This isn't necessarily
-                        // true for child windows though which can have an arbitrary frame position
-                        // relative to their containing frame. We need to offset the difference
-                        // between the containing frame as used to calculate the crop and our
-                        // bounds to compensate for this.
-                        if (mWin.layoutInParentFrame()) {
-                            mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left),
-                                    mWin.mContainingFrame.top - mWin.mFrame.top );
-                        }
-                    }
-                }
                 if (screenAnimation) {
                     mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
                 }
@@ -768,7 +736,6 @@
             if ((DEBUG_ANIM || WindowManagerService.localLOGV)
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
-                    + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
                     + " screen=" + (screenAnimation ?
                             screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
             return;
@@ -800,47 +767,6 @@
     }
 
     /**
-     * In some scenarios we use a screen space clip rect (so called, final clip rect)
-     * to crop to stack bounds. Generally because it's easier to deal with while
-     * animating.
-     *
-     * @return True in scenarios where we use the final clip rect for stack clipping.
-     */
-    private boolean useFinalClipRect() {
-        return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
-                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
-    }
-
-    /**
-     * Calculate the screen-space crop rect and fill finalClipRect.
-     * @return true if finalClipRect has been filled, otherwise,
-     * no screen space crop should be applied.
-     */
-    private boolean calculateFinalCrop(Rect finalClipRect) {
-        final WindowState w = mWin;
-        final DisplayContent displayContent = w.getDisplayContent();
-        finalClipRect.setEmpty();
-
-        if (displayContent == null) {
-            return false;
-        }
-
-        if (!shouldCropToStackBounds() || !useFinalClipRect()) {
-            return false;
-        }
-
-        // Task is non-null per shouldCropToStackBounds
-        final TaskStack stack = w.getTask().mStack;
-        stack.getDimBounds(finalClipRect);
-
-        if (stack.getWindowConfiguration().tasksAreFloating()) {
-            w.expandForSurfaceInsets(finalClipRect);
-        }
-
-        return true;
-    }
-
-    /**
      * Calculate the window-space crop rect and fill clipRect.
      * @return true if clipRect has been filled otherwise, no window space crop should be applied.
      */
@@ -900,9 +826,6 @@
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top);
 
-        if (!useFinalClipRect()) {
-            adjustCropToStackBounds(clipRect, isFreeformResizing);
-        }
         if (DEBUG_WINDOW_CROP) Slog.d(TAG,
                 "win=" + w + " Clip rect after stack adjustment=" + clipRect);
 
@@ -911,9 +834,9 @@
         return true;
     }
 
-    private void applyCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) {
+    private void applyCrop(Rect clipRect, boolean recoveringMemory) {
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin
-                + " clipRect=" + clipRect + " finalClipRect=" + finalClipRect);
+                + " clipRect=" + clipRect);
         if (clipRect != null) {
             if (!clipRect.equals(mLastClipRect)) {
                 mLastClipRect.set(clipRect);
@@ -922,93 +845,6 @@
         } else {
             mSurfaceController.clearCropInTransaction(recoveringMemory);
         }
-
-        if (finalClipRect == null) {
-            finalClipRect = mService.mTmpRect;
-            finalClipRect.setEmpty();
-        }
-        if (!finalClipRect.equals(mLastFinalClipRect)) {
-            mLastFinalClipRect.set(finalClipRect);
-            mSurfaceController.setFinalCropInTransaction(finalClipRect);
-            if (mDestroyPreservedSurfaceUponRedraw && mPendingDestroySurface != null) {
-                mPendingDestroySurface.setFinalCropInTransaction(finalClipRect);
-            }
-        }
-    }
-
-    private int resolveStackClip() {
-        // App animation overrides window animation stack clip mode.
-        if (mAppAnimator != null && mAppAnimator.animation != null) {
-            return mAppAnimator.getStackClip();
-        } else {
-            return STACK_CLIP_AFTER_ANIM;
-        }
-    }
-
-    private boolean shouldCropToStackBounds() {
-        final WindowState w = mWin;
-        final DisplayContent displayContent = w.getDisplayContent();
-        if (displayContent != null && !displayContent.isDefaultDisplay) {
-            // There are some windows that live on other displays while their app and main window
-            // live on the default display (e.g. casting...). We don't want to crop this windows
-            // to the stack bounds which is only currently supported on the default display.
-            // TODO(multi-display): Need to support cropping to stack bounds on other displays
-            // when we have stacks on other displays.
-            return false;
-        }
-
-        final Task task = w.getTask();
-        if (task == null || !task.cropWindowsToStackBounds()) {
-            return false;
-        }
-
-        final int stackClip = resolveStackClip();
-
-        // It's animating and we don't want to clip it to stack bounds during animation - abort.
-        if (isAnimationSet() && stackClip == STACK_CLIP_NONE) {
-            return false;
-        }
-        return true;
-    }
-
-    private void adjustCropToStackBounds(Rect clipRect,
-            boolean isFreeformResizing) {
-        final WindowState w = mWin;
-
-        if (!shouldCropToStackBounds()) {
-            return;
-        }
-
-        final TaskStack stack = w.getTask().mStack;
-        stack.getDimBounds(mTmpStackBounds);
-        final Rect surfaceInsets = w.getAttrs().surfaceInsets;
-        // When we resize we use the big surface approach, which means we can't trust the
-        // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
-        // hardcoding it, we use surface coordinates.
-        final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
-                w.mFrame.left + mWin.mXOffset - surfaceInsets.left;
-        final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
-                w.mFrame.top + mWin.mYOffset - surfaceInsets.top;
-
-        // We need to do some acrobatics with surface position, because their clip region is
-        // relative to the inside of the surface, but the stack bounds aren't.
-        final WindowConfiguration winConfig = w.getWindowConfiguration();
-        if (winConfig.hasWindowShadow() && !winConfig.canResizeTask()) {
-                // The windows in this stack display drop shadows and the fill the entire stack
-                // area. Adjust the stack bounds we will use to cropping take into account the
-                // offsets we use to display the drop shadow so it doesn't get cropped.
-                mTmpStackBounds.inset(-surfaceInsets.left, -surfaceInsets.top,
-                        -surfaceInsets.right, -surfaceInsets.bottom);
-        }
-
-        clipRect.left = Math.max(0,
-                Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
-        clipRect.top = Math.max(0,
-                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
-        clipRect.right = Math.max(0,
-                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
-        clipRect.bottom = Math.max(0,
-                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
@@ -1053,13 +889,10 @@
         // updates until a resize occurs.
         mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
 
-        Rect clipRect = null, finalClipRect = null;
+        Rect clipRect = null;
         if (calculateCrop(mTmpClipRect)) {
             clipRect = mTmpClipRect;
         }
-        if (calculateFinalCrop(mTmpFinalClipRect)) {
-            finalClipRect = mTmpFinalClipRect;
-        }
 
         float surfaceWidth = mSurfaceController.getWidth();
         float surfaceHeight = mSurfaceController.getHeight();
@@ -1124,7 +957,6 @@
                 // Always clip to the stack bounds since the surface can be larger with the current
                 // scale
                 clipRect = null;
-                finalClipRect = mTmpStackBounds;
             } else {
                 // We want to calculate the scaling based on the content area, not based on
                 // the entire surface, so that we scale in sync with windows that don't have insets.
@@ -1135,7 +967,6 @@
                 // expose the whole window in buffer space, and not risk extending
                 // past where the system would have cropped us
                 clipRect = null;
-                finalClipRect = null;
             }
 
             // In the case of ForceScaleToStack we scale entire tasks together,
@@ -1183,7 +1014,7 @@
         }
 
         if (!w.mSeamlesslyRotated) {
-            applyCrop(clipRect, finalClipRect, recoveringMemory);
+            applyCrop(clipRect, recoveringMemory);
             mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
                     mDtDx * w.mVScale * mExtraVScale,
                     mDtDy * w.mHScale * mExtraHScale,
@@ -1381,7 +1212,7 @@
             mService.openSurfaceTransaction();
             mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
                     mWin.mFrame.top + top, false);
-            applyCrop(null, null, false);
+            applyCrop(null, false);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d8e7457..bdab9c7 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -97,9 +97,6 @@
     static final int SET_TURN_ON_SCREEN                 = 1 << 4;
     static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 5;
 
-    private final Rect mTmpStartRect = new Rect();
-    private final Rect mTmpContentRect = new Rect();
-
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
 
@@ -361,14 +358,9 @@
 
         mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
-        final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
-                topOpeningApp.mAppAnimator;
-        final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
-                topClosingApp.mAppAnimator;
-
         final int flags = mService.mAppTransition.getTransitFlags();
-        int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator,
-                closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
+        int layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp,
+                topClosingApp, mService.mOpeningApps, mService.mClosingApps);
         handleNonAppWindowsInTransition(transit, flags);
         mService.mAppTransition.postAnimationCallback();
         mService.mAppTransition.clear();
@@ -405,14 +397,8 @@
         final int appsCount = mService.mOpeningApps.size();
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
 
-            if (!appAnimator.usingTransferredAnimation) {
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-            }
-
             if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){
                 // This token isn't going to be animating. Add it to the list of tokens to
                 // be notified of app transition complete since the notification will not be
@@ -421,19 +407,17 @@
             }
             wtoken.updateReportedVisibilityLocked();
             wtoken.waitingToShow = false;
-            wtoken.setAllAppWinAnimators();
 
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             mService.openSurfaceTransaction();
             try {
-                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+                wtoken.showAllWindowsLocked();
             } finally {
                 mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
@@ -443,7 +427,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                createThumbnailAppAnimator(transit, wtoken);
+                wtoken.attachThumbnailAnimation();
             }
         }
         return topOpeningApp;
@@ -456,18 +440,11 @@
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
 
-            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
-            appAnimator.clearThumbnail();
-            appAnimator.setNullAnimation();
             // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
             //       animating?
-            wtoken.setAllAppWinAnimators();
             wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
             wtoken.updateReportedVisibilityLocked();
-            // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is
-            // done.
-            wtoken.setAllAppWinAnimators();
             // Force the allDrawn flag, because we want to start
             // this guy's animations regardless of whether it's
             // gotten drawn.
@@ -479,7 +456,6 @@
                     && wtoken.getController() != null) {
                 wtoken.getController().removeStartingWindow();
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 int layer = wtoken.getHighestAnimLayer();
@@ -489,7 +465,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                createThumbnailAppAnimator(transit, wtoken);
+                wtoken.attachThumbnailAnimation();
             }
         }
     }
@@ -666,102 +642,16 @@
             final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
             if (win != null) {
                 final AppWindowToken wtoken = win.mAppToken;
-                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
                 if (DEBUG_APP_TRANSITIONS)
                     Slog.v(TAG, "Now animating app in place " + wtoken);
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-                mService.updateTokenInPlaceLocked(wtoken, transit);
+                wtoken.cancelAnimation();
+                wtoken.applyAnimationLocked(null, transit, false, false);
                 wtoken.updateReportedVisibilityLocked();
-                wtoken.setAllAppWinAnimators();
-                mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+                wtoken.showAllWindowsLocked();
             }
         }
     }
 
-    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
-        AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
-        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
-            return;
-        }
-        final int taskId = appToken.getTask().mTaskId;
-        final GraphicBuffer thumbnailHeader =
-                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
-        if (thumbnailHeader == null) {
-            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
-            return;
-        }
-        // This thumbnail animation is very special, we need to have
-        // an extra surface with the thumbnail included with the animation.
-        Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
-        try {
-            // TODO(multi-display): support other displays
-            final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
-            final Display display = displayContent.getDisplay();
-            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
-            // Create a new surface for the thumbnail
-            WindowState window = appToken.findMainWindow();
-            final SurfaceControl surfaceControl = appToken.makeSurface()
-                    .setName("thumbnail anim")
-                    .setSize(dirty.width(), dirty.height())
-                    .setFormat(PixelFormat.TRANSLUCENT)
-                    .setMetadata(appToken.windowType,
-                            window != null ? window.mOwnerUid : Binder.getCallingUid())
-                    .build();
-
-            if (SHOW_TRANSACTIONS) {
-                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
-            }
-
-            // Transfer the thumbnail to the surface
-            Surface drawSurface = new Surface();
-            drawSurface.copyFrom(surfaceControl);
-            drawSurface.attachAndQueueBuffer(thumbnailHeader);
-            drawSurface.release();
-
-            // Get the thumbnail animation
-            Animation anim;
-            if (mService.mAppTransition.isNextThumbnailTransitionAspectScaled()) {
-                // If this is a multi-window scenario, we use the windows frame as
-                // destination of the thumbnail header animation. If this is a full screen
-                // window scenario, we use the whole display as the target.
-                WindowState win = appToken.findMainWindow();
-                Rect appRect = win != null ? win.getContentFrameLw() :
-                        new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
-                Rect insets = win != null ? win.mContentInsets : null;
-                final Configuration displayConfig = displayContent.getConfiguration();
-                // For the new aspect-scaled transition, we want it to always show
-                // above the animating opening/closing window, and we want to
-                // synchronize its thumbnail surface with the surface for the
-                // open/close animation (only on the way down)
-                anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
-                        insets, thumbnailHeader, taskId, displayConfig.uiMode,
-                        displayConfig.orientation);
-                openingAppAnimator.deferThumbnailDestruction =
-                        !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
-            } else {
-                anim = mService.mAppTransition.createThumbnailScaleAnimationLocked(
-                        displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
-            }
-            anim.restrictDuration(MAX_ANIMATION_DURATION);
-            anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-
-            openingAppAnimator.thumbnail = surfaceControl;
-            openingAppAnimator.thumbnailAnimation = anim;
-            mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
-
-            // We parent the thumbnail to the app token, and just place it
-            // on top of anything else in the app token.
-            surfaceControl.setLayer(Integer.MAX_VALUE);
-        } catch (Surface.OutOfResourcesException e) {
-            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
-                    + dirty.width() + " h=" + dirty.height(), e);
-            openingAppAnimator.clearThumbnail();
-        }
-    }
-
     void requestTraversal() {
         if (!mTraversalScheduled) {
             mTraversalScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bcf59c..bad9bf5 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -63,7 +63,7 @@
     boolean paused = false;
 
     // Should this token's windows be hidden?
-    boolean hidden;
+    private boolean mHidden;
 
     // Temporary for finding which tokens no longer have visible windows.
     boolean hasVisible;
@@ -112,6 +112,16 @@
         onDisplayChanged(dc);
     }
 
+    void setHidden(boolean hidden) {
+        if (hidden != mHidden) {
+            mHidden = hidden;
+        }
+    }
+
+    boolean isHidden() {
+        return mHidden;
+    }
+
     void removeAllWindowsIfPossible() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState win = mChildren.get(i);
@@ -130,7 +140,7 @@
         // This token is exiting, so allow it to be removed when it no longer contains any windows.
         mPersistOnEmpty = false;
 
-        if (hidden) {
+        if (mHidden) {
             return;
         }
 
@@ -146,7 +156,7 @@
             changed |= win.onSetAppExiting();
         }
 
-        hidden = true;
+        setHidden(true);
 
         if (changed) {
             mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -189,11 +199,6 @@
         return mChildren.isEmpty();
     }
 
-    // Used by AppWindowToken.
-    int getAnimLayerAdjustment() {
-        return 0;
-    }
-
     WindowState getReplacingWindow() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowState win = mChildren.get(i);
@@ -274,10 +279,11 @@
         proto.end(token);
     }
 
-    void dump(PrintWriter pw, String prefix) {
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("windows="); pw.println(mChildren);
         pw.print(prefix); pw.print("windowType="); pw.print(windowType);
-                pw.print(" hidden="); pw.print(hidden);
+                pw.print(" hidden="); pw.print(mHidden);
                 pw.print(" hasVisible="); pw.println(hasVisible);
         if (waitingToShow || sendingToBottom) {
             pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e53aa81..b18c1a0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -32,6 +32,7 @@
         "com_android_server_lights_LightsService.cpp",
         "com_android_server_location_GnssLocationProvider.cpp",
         "com_android_server_locksettings_SyntheticPasswordManager.cpp",
+        "com_android_server_net_NetworkStatsService.cpp",
         "com_android_server_power_PowerManagerService.cpp",
         "com_android_server_SerialService.cpp",
         "com_android_server_storage_AppFuseBridge.cpp",
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 35e65bc..79f482c 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -81,16 +81,20 @@
         return NULL;
     }
 
-    char* c_str = usb_device_get_string(device, stringId, 0 /*timeout*/);
+    // Get Raw UCS2 Bytes
+    jbyte* byteBuffer = NULL;
+    size_t numUSC2Bytes = 0;
+    int retVal =
+            usb_device_get_string_ucs2(device, stringId, 0 /*timeout*/,
+                                     (void**)&byteBuffer, &numUSC2Bytes);
 
-    jstring j_str = env->NewStringUTF(c_str);
+    jstring j_str = NULL;
 
-    free(c_str);
-    usb_device_close(device);
-
+    if (retVal == 0) {
+        j_str = env->NewString((jchar*)byteBuffer, numUSC2Bytes/2);
+        free(byteBuffer);
+    }
     return j_str;
 }
 
 } // extern "C"
-
-
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 39cc953..02ad6c7 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -101,7 +101,7 @@
             return -1;
         }
         ALOGV("Registering callback...");
-        set_wakeup_callback(&wakeup_callback);
+        autosuspend_set_wakeup_callback(&wakeup_callback);
     }
 
     // Wait for wakeup.
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 246bd42..67bad0f 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -48,6 +48,7 @@
 static jmethodID method_reportNmea;
 static jmethodID method_setEngineCapabilities;
 static jmethodID method_setGnssYearOfHardware;
+static jmethodID method_setGnssHardwareModelName;
 static jmethodID method_xtraDownloadRequest;
 static jmethodID method_reportNiNotification;
 static jmethodID method_requestRefLocation;
@@ -373,12 +374,11 @@
 Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) {
     ALOGD("%s: name=%s\n", __func__, name.c_str());
 
-    // TODO(b/38003769): build Java code to connect to below code
-    /*
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareName, name);
+    jstring jstringName = env->NewStringUTF(name.c_str());
+    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    */
+
     return Void();
 }
 
@@ -1031,6 +1031,8 @@
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
     method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
+    method_setGnssHardwareModelName = env->GetMethodID(clazz, "setGnssHardwareModelName",
+            "(Ljava/lang/String;)V");
     method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
     method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification",
             "(IIIIILjava/lang/String;Ljava/lang/String;II)V");
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
new file mode 100644
index 0000000..8de24e5
--- /dev/null
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NetworkStatsNative"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "core_jni_helpers.h"
+#include <jni.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+
+namespace android {
+
+static const char* QTAGUID_IFACE_STATS = "/proc/net/xt_qtaguid/iface_stat_fmt";
+static const char* QTAGUID_UID_STATS = "/proc/net/xt_qtaguid/stats";
+
+// NOTE: keep these in sync with TrafficStats.java
+static const uint64_t UNKNOWN = -1;
+
+enum StatsType {
+    RX_BYTES = 0,
+    RX_PACKETS = 1,
+    TX_BYTES = 2,
+    TX_PACKETS = 3,
+    TCP_RX_PACKETS = 4,
+    TCP_TX_PACKETS = 5
+};
+
+struct Stats {
+    uint64_t rxBytes;
+    uint64_t rxPackets;
+    uint64_t txBytes;
+    uint64_t txPackets;
+    uint64_t tcpRxPackets;
+    uint64_t tcpTxPackets;
+};
+
+static uint64_t getStatsType(struct Stats* stats, StatsType type) {
+    switch (type) {
+        case RX_BYTES:
+            return stats->rxBytes;
+        case RX_PACKETS:
+            return stats->rxPackets;
+        case TX_BYTES:
+            return stats->txBytes;
+        case TX_PACKETS:
+            return stats->txPackets;
+        case TCP_RX_PACKETS:
+            return stats->tcpRxPackets;
+        case TCP_TX_PACKETS:
+            return stats->tcpTxPackets;
+        default:
+            return UNKNOWN;
+    }
+}
+
+static int parseIfaceStats(const char* iface, struct Stats* stats) {
+    FILE *fp = fopen(QTAGUID_IFACE_STATS, "r");
+    if (fp == NULL) {
+        return -1;
+    }
+
+    char buffer[384];
+    char cur_iface[32];
+    bool foundTcp = false;
+    uint64_t rxBytes, rxPackets, txBytes, txPackets, tcpRxPackets, tcpTxPackets;
+
+    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+        int matched = sscanf(buffer, "%31s %" SCNu64 " %" SCNu64 " %" SCNu64
+                " %" SCNu64 " " "%*u %" SCNu64 " %*u %*u %*u %*u "
+                "%*u %" SCNu64 " %*u %*u %*u %*u", cur_iface, &rxBytes,
+                &rxPackets, &txBytes, &txPackets, &tcpRxPackets, &tcpTxPackets);
+        if (matched >= 5) {
+            if (matched == 7) {
+                foundTcp = true;
+            }
+            if (!iface || !strcmp(iface, cur_iface)) {
+                stats->rxBytes += rxBytes;
+                stats->rxPackets += rxPackets;
+                stats->txBytes += txBytes;
+                stats->txPackets += txPackets;
+                if (matched == 7) {
+                    stats->tcpRxPackets += tcpRxPackets;
+                    stats->tcpTxPackets += tcpTxPackets;
+                }
+            }
+        }
+    }
+
+    if (!foundTcp) {
+        stats->tcpRxPackets = UNKNOWN;
+        stats->tcpTxPackets = UNKNOWN;
+    }
+
+    if (fclose(fp) != 0) {
+        return -1;
+    }
+    return 0;
+}
+
+static int parseUidStats(const uint32_t uid, struct Stats* stats) {
+    FILE *fp = fopen(QTAGUID_UID_STATS, "r");
+    if (fp == NULL) {
+        return -1;
+    }
+
+    char buffer[384];
+    char iface[32];
+    uint32_t idx, cur_uid, set;
+    uint64_t tag, rxBytes, rxPackets, txBytes, txPackets;
+
+    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+        if (sscanf(buffer,
+                "%" SCNu32 " %31s 0x%" SCNx64 " %u %u %" SCNu64 " %" SCNu64
+                " %" SCNu64 " %" SCNu64 "",
+                &idx, iface, &tag, &cur_uid, &set, &rxBytes, &rxPackets,
+                &txBytes, &txPackets) == 9) {
+            if (uid == cur_uid && tag == 0L) {
+                stats->rxBytes += rxBytes;
+                stats->rxPackets += rxPackets;
+                stats->txBytes += txBytes;
+                stats->txPackets += txPackets;
+            }
+        }
+    }
+
+    if (fclose(fp) != 0) {
+        return -1;
+    }
+    return 0;
+}
+
+static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+    struct Stats stats;
+    memset(&stats, 0, sizeof(Stats));
+    if (parseIfaceStats(NULL, &stats) == 0) {
+        return getStatsType(&stats, (StatsType) type);
+    } else {
+        return UNKNOWN;
+    }
+}
+
+static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+    ScopedUtfChars iface8(env, iface);
+    if (iface8.c_str() == NULL) {
+        return UNKNOWN;
+    }
+
+    struct Stats stats;
+    memset(&stats, 0, sizeof(Stats));
+    if (parseIfaceStats(iface8.c_str(), &stats) == 0) {
+        return getStatsType(&stats, (StatsType) type);
+    } else {
+        return UNKNOWN;
+    }
+}
+
+static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
+    struct Stats stats;
+    memset(&stats, 0, sizeof(Stats));
+    if (parseUidStats(uid, &stats) == 0) {
+        return getStatsType(&stats, (StatsType) type);
+    } else {
+        return UNKNOWN;
+    }
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
+    {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
+    {"nativeGetUidStat", "(II)J", (void*) getUidStat},
+};
+
+int register_android_server_net_NetworkStatsService(JNIEnv* env) {
+    jclass netStatsService = env->FindClass("com/android/server/net/NetworkStatsService");
+    jfieldID rxBytesId = env->GetStaticFieldID(netStatsService, "TYPE_RX_BYTES", "I");
+    jfieldID rxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_RX_PACKETS", "I");
+    jfieldID txBytesId = env->GetStaticFieldID(netStatsService, "TYPE_TX_BYTES", "I");
+    jfieldID txPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TX_PACKETS", "I");
+    jfieldID tcpRxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_RX_PACKETS", "I");
+    jfieldID tcpTxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_TX_PACKETS", "I");
+
+    env->SetStaticIntField(netStatsService, rxBytesId, RX_BYTES);
+    env->SetStaticIntField(netStatsService, rxPacketsId, RX_PACKETS);
+    env->SetStaticIntField(netStatsService, txBytesId, TX_BYTES);
+    env->SetStaticIntField(netStatsService, txPacketsId, TX_PACKETS);
+    env->SetStaticIntField(netStatsService, tcpRxPacketsId, TCP_RX_PACKETS);
+    env->SetStaticIntField(netStatsService, tcpTxPacketsId, TCP_TX_PACKETS);
+
+    return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods,
+                                    NELEM(gMethods));
+}
+
+}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 46d5043..071b6b8 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -51,6 +51,7 @@
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_server_GraphicsStatsService(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_net_NetworkStatsService(JNIEnv* env);
 };
 
 using namespace android;
@@ -95,6 +96,7 @@
     register_android_server_SyntheticPasswordManager(env);
     register_android_server_GraphicsStatsService(env);
     register_android_hardware_display_DisplayViewport(env);
+    register_android_server_net_NetworkStatsService(env);
 
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5b9e3a1..e55d4ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.devicepolicy;
 
+import android.annotation.UserIdInt;
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.os.PersistableBundle;
@@ -24,6 +25,8 @@
 import com.android.internal.R;
 import com.android.server.SystemService;
 
+import java.util.List;
+
 /**
  * Defines the required interface for IDevicePolicyManager implemenation.
  *
@@ -68,7 +71,29 @@
         return false;
     }
 
+    @Override
+    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+            boolean parent) {
+        return false;
+    }
+
+    @Override
+    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+            boolean parent) {
+        return null;
+    }
+
+    @Override
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+        return false;
+    }
+
     public boolean isUsingUnifiedPassword(ComponentName who) {
         return true;
     }
+
+    public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+            byte[] cert, byte[] chain, boolean isUserSelectable) {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bead31f..e5351b4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -734,6 +734,7 @@
         private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
         private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
         private static final String ATTR_VALUE = "value";
+        private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist";
         private static final String TAG_PASSWORD_QUALITY = "password-quality";
         private static final String TAG_POLICIES = "policies";
         private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
@@ -866,6 +867,9 @@
         // Default title of confirm credentials screen
         String organizationName = null;
 
+        // The blacklist data is stored in a file whose name is stored in the XML
+        String passwordBlacklistFile = null;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -947,6 +951,11 @@
                     out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
                 }
             }
+            if (passwordBlacklistFile != null) {
+                out.startTag(null, TAG_PASSWORD_BLACKLIST);
+                out.attribute(null, ATTR_VALUE, passwordBlacklistFile);
+                out.endTag(null, TAG_PASSWORD_BLACKLIST);
+            }
             if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
                 out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
                 out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
@@ -1186,7 +1195,9 @@
                 } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
                     minimumPasswordMetrics.nonLetter = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+                } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) {
+                    passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE);
+                }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
                     maximumTimeToUnlock = Long.parseLong(
                             parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
@@ -1441,6 +1452,8 @@
                     pw.println(minimumPasswordMetrics.symbols);
             pw.print(prefix); pw.print("minimumPasswordNonLetter=");
                     pw.println(minimumPasswordMetrics.nonLetter);
+            pw.print(prefix); pw.print("passwordBlacklist=");
+                    pw.println(passwordBlacklistFile != null);
             pw.print(prefix); pw.print("maximumTimeToUnlock=");
                     pw.println(maximumTimeToUnlock);
             pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -1693,6 +1706,10 @@
             return new LockPatternUtils(mContext);
         }
 
+        PasswordBlacklist newPasswordBlacklist(File file) {
+            return new PasswordBlacklist(file);
+        }
+
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return StorageManager.isFileEncryptedNativeOnly();
         }
@@ -2589,11 +2606,15 @@
         }
     }
 
-    private JournaledFile makeJournaledFile(int userHandle) {
-        final String base = userHandle == UserHandle.USER_SYSTEM
-                ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
-                : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
-                        DEVICE_POLICIES_XML).getAbsolutePath();
+    private File getPolicyFileDirectory(@UserIdInt int userId) {
+        return userId == UserHandle.USER_SYSTEM
+                ? new File(mInjector.getDevicePolicyFilePathForSystemUser())
+                : mInjector.environmentGetUserSystemDirectory(userId);
+    }
+
+    private JournaledFile makeJournaledFile(@UserIdInt int userId) {
+        final String base = new File(getPolicyFileDirectory(userId), DEVICE_POLICIES_XML)
+                .getAbsolutePath();
         if (VERBOSE_LOG) {
             Log.v(LOG_TAG, "Opening " + base);
         }
@@ -4064,6 +4085,136 @@
         }
     }
 
+    /* @return the password blacklist set by the admin or {@code null} if none. */
+    PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) {
+        final int userId = UserHandle.getUserId(admin.getUid());
+        return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist(
+                new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile));
+    }
+
+    private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-";
+    private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = "";
+
+    @Override
+    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+            boolean parent) {
+        if (!mHasFeature) {
+            return false;
+        }
+        Preconditions.checkNotNull(who, "who is null");
+
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+            final int userId = mInjector.userHandleGetCallingUserId();
+            PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin);
+
+            if (blacklist == null || blacklist.isEmpty()) {
+                // Remove the adminBlacklist
+                admin.passwordBlacklistFile = null;
+                saveSettingsLocked(userId);
+                if (adminBlacklist != null) {
+                    adminBlacklist.delete();
+                }
+                return true;
+            }
+
+            // Validate server side
+            Preconditions.checkNotNull(name, "name is null");
+            DevicePolicyManager.enforcePasswordBlacklistSize(blacklist);
+
+            // Blacklist is case insensitive so normalize to lower case
+            final int blacklistSize = blacklist.size();
+            for (int i = 0; i < blacklistSize; ++i) {
+                blacklist.set(i, blacklist.get(i).toLowerCase());
+            }
+
+            final boolean isNewBlacklist = adminBlacklist == null;
+            if (isNewBlacklist) {
+                // Create a new file for the blacklist. There could be multiple admins, each setting
+                // different blacklists, to restrict a user's credential, for example a managed
+                // profile can impose restrictions on its parent while the parent is already
+                // restricted by its own admin. A deterministic naming scheme would be fragile if
+                // new types of admin are introduced so we generate and save the file name instead.
+                // This isn't a temporary file but it reuses the name generation logic
+                final File file;
+                try {
+                    file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX,
+                            PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId));
+                } catch (IOException e) {
+                    Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e);
+                    return false;
+                }
+                adminBlacklist = mInjector.newPasswordBlacklist(file);
+            }
+
+            if (adminBlacklist.savePasswordBlacklist(name, blacklist)) {
+                if (isNewBlacklist) {
+                    // The blacklist was saved so point the admin to the file
+                    admin.passwordBlacklistFile = adminBlacklist.getFile().getName();
+                    saveSettingsLocked(userId);
+                }
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+            boolean parent) {
+        if (!mHasFeature) {
+            return null;
+        }
+        Preconditions.checkNotNull(who, "who is null");
+        enforceFullCrossUsersPermission(userId);
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+            final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin);
+            if (blacklist == null) {
+                return null;
+            }
+            return blacklist.getName();
+        }
+    }
+
+    @Override
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+        if (!mHasFeature) {
+            return false;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null);
+        return isPasswordBlacklistedInternal(userId, password);
+    }
+
+    private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) {
+        Preconditions.checkNotNull(password, "Password is null");
+        enforceFullCrossUsersPermission(userId);
+
+        // Normalize to lower case for case insensitive blacklist match
+        final String lowerCasePassword = password.toLowerCase();
+
+        synchronized (this) {
+            final List<ActiveAdmin> admins =
+                    getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false);
+            final int N = admins.size();
+            for (int i = 0; i < N; i++) {
+                final PasswordBlacklist blacklist
+                        = getAdminPasswordBlacklistLocked(admins.get(i));
+                if (blacklist != null) {
+                    if (blacklist.isPasswordBlacklisted(lowerCasePassword)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
     @Override
     public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
         if (!mHasFeature) {
@@ -4420,6 +4571,11 @@
                     return false;
                 }
             }
+
+            if (isPasswordBlacklistedInternal(userHandle, password)) {
+                Slog.w(LOG_TAG, "resetPassword: the password is blacklisted");
+                return false;
+            }
         }
 
         DevicePolicyData policy = getUserData(userHandle);
@@ -4518,56 +4674,56 @@
         }
     }
 
-    void updateMaximumTimeToLockLocked(int userHandle) {
-        // Calculate the min timeout for all profiles - including the ones with a separate
-        // challenge. Ideally if the timeout only affected the profile challenge we'd lock that
-        // challenge only and keep the screen on. However there is no easy way of doing that at the
-        // moment so we set the screen off timeout regardless of whether it affects the parent user
-        // or the profile challenge only.
-        long timeMs = Long.MAX_VALUE;
-        int[] profileIds = mUserManager.getProfileIdsWithDisabled(userHandle);
-        for (int profileId : profileIds) {
-            DevicePolicyData policy = getUserDataUnchecked(profileId);
-            final int N = policy.mAdminList.size();
-            for (int i = 0; i < N; i++) {
-                ActiveAdmin admin = policy.mAdminList.get(i);
-                if (admin.maximumTimeToUnlock > 0
-                        && timeMs > admin.maximumTimeToUnlock) {
-                    timeMs = admin.maximumTimeToUnlock;
-                }
-                // If userInfo.id is a managed profile, we also need to look at
-                // the policies set on the parent.
-                if (admin.hasParentActiveAdmin()) {
-                    final ActiveAdmin parentAdmin = admin.getParentActiveAdmin();
-                    if (parentAdmin.maximumTimeToUnlock > 0
-                            && timeMs > parentAdmin.maximumTimeToUnlock) {
-                        timeMs = parentAdmin.maximumTimeToUnlock;
-                    }
-                }
-            }
+    private void updateMaximumTimeToLockLocked(@UserIdInt int userId) {
+        // Update the profile's timeout
+        if (isManagedProfile(userId)) {
+            updateProfileLockTimeoutLocked(userId);
         }
 
-        // We only store the last maximum time to lock on the parent profile. So if calling from a
-        // managed profile, retrieve the policy for the parent.
-        DevicePolicyData policy = getUserDataUnchecked(getProfileParentId(userHandle));
-        if (policy.mLastMaximumTimeToLock == timeMs) {
-            return;
-        }
-        policy.mLastMaximumTimeToLock = timeMs;
-
+        final long timeMs;
         final long ident = mInjector.binderClearCallingIdentity();
         try {
+            // Update the device timeout
+            final int parentId = getProfileParentId(userId);
+            timeMs = getMaximumTimeToLockPolicyFromAdmins(
+                    getActiveAdminsForLockscreenPoliciesLocked(parentId, false));
+
+            final DevicePolicyData policy = getUserDataUnchecked(parentId);
+            if (policy.mLastMaximumTimeToLock == timeMs) {
+                return;
+            }
+            policy.mLastMaximumTimeToLock = timeMs;
+
             if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
                 // Make sure KEEP_SCREEN_ON is disabled, since that
                 // would allow bypassing of the maximum time to lock.
                 mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             }
-
-            mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
-                    (int) Math.min(policy.mLastMaximumTimeToLock, Integer.MAX_VALUE));
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
+
+        mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+                UserHandle.USER_SYSTEM, timeMs);
+    }
+
+    private void updateProfileLockTimeoutLocked(@UserIdInt int userId) {
+        final long timeMs;
+        if (isSeparateProfileChallengeEnabled(userId)) {
+            timeMs = getMaximumTimeToLockPolicyFromAdmins(
+                    getActiveAdminsForLockscreenPoliciesLocked(userId, false /* parent */));
+        } else {
+            timeMs = Long.MAX_VALUE;
+        }
+
+        final DevicePolicyData policy = getUserDataUnchecked(userId);
+        if (policy.mLastMaximumTimeToLock == timeMs) {
+            return;
+        }
+        policy.mLastMaximumTimeToLock = timeMs;
+
+        mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+                userId, policy.mLastMaximumTimeToLock);
     }
 
     @Override
@@ -4578,50 +4734,21 @@
         enforceFullCrossUsersPermission(userHandle);
         synchronized (this) {
             if (who != null) {
-                ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
+                final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
                 return admin != null ? admin.maximumTimeToUnlock : 0;
             }
             // Return the strictest policy across all participating admins.
-            List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
+            final List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
                     userHandle, parent);
-            return getMaximumTimeToLockPolicyFromAdmins(admins);
-        }
-    }
-
-    @Override
-    public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
-        if (!mHasFeature) {
-            return 0;
-        }
-        enforceFullCrossUsersPermission(userHandle);
-        synchronized (this) {
-            // All admins for this user.
-            ArrayList<ActiveAdmin> admins = new ArrayList<ActiveAdmin>();
-            for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
-                DevicePolicyData policy = getUserData(userInfo.id);
-                admins.addAll(policy.mAdminList);
-                // If it is a managed profile, it may have parent active admins
-                if (userInfo.isManagedProfile()) {
-                    for (ActiveAdmin admin : policy.mAdminList) {
-                        if (admin.hasParentActiveAdmin()) {
-                            admins.add(admin.getParentActiveAdmin());
-                        }
-                    }
-                }
-            }
-            return getMaximumTimeToLockPolicyFromAdmins(admins);
+            final long timeMs = getMaximumTimeToLockPolicyFromAdmins(admins);
+            return timeMs == Long.MAX_VALUE ? 0 : timeMs;
         }
     }
 
     private long getMaximumTimeToLockPolicyFromAdmins(List<ActiveAdmin> admins) {
-        long time = 0;
-        final int N = admins.size();
-        for (int i = 0; i < N; i++) {
-            ActiveAdmin admin = admins.get(i);
-            if (time == 0) {
-                time = admin.maximumTimeToUnlock;
-            } else if (admin.maximumTimeToUnlock != 0
-                    && time > admin.maximumTimeToUnlock) {
+        long time = Long.MAX_VALUE;
+        for (final ActiveAdmin admin : admins) {
+            if (admin.maximumTimeToUnlock > 0 && admin.maximumTimeToUnlock < time) {
                 time = admin.maximumTimeToUnlock;
             }
         }
@@ -4998,6 +5125,33 @@
     }
 
     @Override
+    public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+            byte[] cert, byte[] chain, boolean isUserSelectable) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_CERT_INSTALL);
+
+        final int callingUid = mInjector.binderGetCallingUid();
+        final long id = mInjector.binderClearCallingIdentity();
+        try (final KeyChainConnection keyChainConnection =
+                KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid))) {
+            IKeyChainService keyChain = keyChainConnection.getService();
+            if (!keyChain.setKeyPairCertificate(alias, cert, chain)) {
+                return false;
+            }
+            keyChain.setUserSelectable(alias, isUserSelectable);
+            return true;
+        } catch (InterruptedException e) {
+            Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
+            Thread.currentThread().interrupt();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Failed setting keypair certificate", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+        return false;
+    }
+
+    @Override
     public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
             final IBinder response) {
         // Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
@@ -9268,7 +9422,7 @@
                 // ignore if it contradicts an existing policy
                 long timeMs = getMaximumTimeToLock(
                         who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
-                if (timeMs > 0 && timeMs < Integer.MAX_VALUE) {
+                if (timeMs > 0 && timeMs < Long.MAX_VALUE) {
                     return;
                 }
             }
@@ -9730,6 +9884,13 @@
         public boolean isUserAffiliatedWithDevice(int userId) {
             return DevicePolicyManagerService.this.isUserAffiliatedWithDeviceLocked(userId);
         }
+
+        @Override
+        public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) {
+            synchronized (DevicePolicyManagerService.this) {
+                updateMaximumTimeToLockLocked(userId);
+            }
+        }
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 4a6bee5..f91f959 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -29,10 +29,10 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 /**
  * A Handler class for managing network logging on a background thread.
@@ -81,6 +81,7 @@
         }
     };
 
+    @VisibleForTesting
     static final int LOG_NETWORK_EVENT_MSG = 1;
 
     /** Network events accumulated so far to be finalized into a batch at some point. */
@@ -106,9 +107,15 @@
     private long mLastRetrievedBatchToken;
 
     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
+        this(looper, dpm, 0 /* event id */);
+    }
+
+    @VisibleForTesting
+    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
         super(looper);
-        mDpm = dpm;
-        mAlarmManager = mDpm.mInjector.getAlarmManager();
+        this.mDpm = dpm;
+        this.mAlarmManager = mDpm.mInjector.getAlarmManager();
+        this.mId = id;
     }
 
     @Override
@@ -189,7 +196,13 @@
         if (mNetworkEvents.size() > 0) {
             // Assign ids to the events.
             for (NetworkEvent event : mNetworkEvents) {
-                event.setId(mId++);
+                event.setId(mId);
+                if (mId == Long.MAX_VALUE) {
+                    Slog.i(TAG, "Reached maximum id value; wrapping around ." + mCurrentBatchToken);
+                    mId = 0;
+                } else {
+                    mId++;
+                }
             }
             // Finalize the batch and start a new one from scratch.
             if (mBatches.size() >= MAX_BATCHES) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
new file mode 100644
index 0000000..6a9b53a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
@@ -0,0 +1,165 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Manages the blacklisted passwords.
+ *
+ * This caller must ensure synchronized access.
+ */
+public class PasswordBlacklist {
+    private static final String TAG = "PasswordBlacklist";
+
+    private final AtomicFile mFile;
+
+    /**
+     * Create an object to manage the password blacklist.
+     *
+     * This is a lightweight operation to prepare variables but not perform any IO.
+     */
+    public PasswordBlacklist(File file) {
+        mFile = new AtomicFile(file);
+    }
+
+    /**
+     * Atomically replace the blacklist.
+     *
+     * Pass {@code null} for an empty list.
+     */
+    public boolean savePasswordBlacklist(@NonNull String name, @NonNull List<String> blacklist) {
+        FileOutputStream fos = null;
+        try {
+            fos = mFile.startWrite();
+            final DataOutputStream out = buildStreamForWriting(fos);
+            final Header header = new Header(Header.VERSION_1, name, blacklist.size());
+            header.write(out);
+            final int blacklistSize = blacklist.size();
+            for (int i = 0; i < blacklistSize; ++i) {
+                out.writeUTF(blacklist.get(i));
+            }
+            out.flush();
+            mFile.finishWrite(fos);
+            return true;
+        } catch (IOException e) {
+            mFile.failWrite(fos);
+            return false;
+        }
+    }
+
+    /** @return the name of the blacklist or {@code null} if none set. */
+    public String getName() {
+        try (DataInputStream in = openForReading()) {
+            return Header.read(in).mName;
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+        }
+        return null;
+    }
+
+    /** @return the number of blacklisted passwords. */
+    public int getSize() {
+        final int blacklistSize;
+        try (DataInputStream in = openForReading()) {
+            return Header.read(in).mSize;
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+        }
+        return 0;
+    }
+
+    /** @return whether the password matches an blacklisted item. */
+    public boolean isPasswordBlacklisted(@NonNull String password) {
+        final int blacklistSize;
+        try (DataInputStream in = openForReading()) {
+            final Header header = Header.read(in);
+            for (int i = 0; i < header.mSize; ++i) {
+                if (in.readUTF().equals(password)) {
+                    return true;
+                }
+            }
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+            // Fail safe and block all passwords. Setting a new blacklist should resolve this
+            // problem which can be identified by examining the log.
+            return true;
+        }
+        return false;
+    }
+
+    /** Delete the blacklist completely from disk. */
+    public void delete() {
+        mFile.delete();
+    }
+
+    /** Get the file the blacklist is stored in. */
+    public File getFile() {
+        return mFile.getBaseFile();
+    }
+
+    private DataOutputStream buildStreamForWriting(FileOutputStream fos) {
+        return new DataOutputStream(new BufferedOutputStream(fos));
+    }
+
+    private DataInputStream openForReading() throws IOException {
+        return new DataInputStream(new BufferedInputStream(mFile.openRead()));
+    }
+
+    /**
+     * Helper to read and write the header of the blacklist file.
+     */
+    private static class Header {
+        static final int VERSION_1 = 1;
+
+        final int mVersion; // File format version
+        final String mName;
+        final int mSize;
+
+        Header(int version, String name, int size) {
+            mVersion = version;
+            mName = name;
+            mSize = size;
+        }
+
+        void write(DataOutputStream out) throws IOException {
+            out.writeInt(mVersion);
+            out.writeUTF(mName);
+            out.writeInt(mSize);
+        }
+
+        static Header read(DataInputStream in) throws IOException {
+            final int version = in.readInt();
+            final String name = in.readUTF();
+            final int size = in.readInt();
+            return new Header(version, name, size);
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 5c3a37a..a9fd8e5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -19,11 +19,13 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.Process;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -32,8 +34,6 @@
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-import android.os.Process;
-
 /**
  * A class managing access to the security logs. It maintains an internal buffer of pending
  * logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via
@@ -48,7 +48,13 @@
     private final Lock mLock = new ReentrantLock();
 
     SecurityLogMonitor(DevicePolicyManagerService service) {
-        mService = service;
+        this(service, 0 /* id */);
+    }
+
+    @VisibleForTesting
+    SecurityLogMonitor(DevicePolicyManagerService service, long id) {
+        this.mService = service;
+        this.mId = id;
     }
 
     private static final boolean DEBUG = false;  // STOPSHIP if true.
@@ -58,7 +64,7 @@
      * it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device
      * Owner.
      */
-    private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
+    @VisibleForTesting static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
     /**
      * The maximum number of entries we should store before dropping earlier logs, to limit the
      * memory usage.
@@ -87,6 +93,8 @@
     @GuardedBy("mLock")
     private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
     @GuardedBy("mLock")
+    private long mId;
+    @GuardedBy("mLock")
     private boolean mAllowedToRetrieve = false;
 
     /**
@@ -112,6 +120,7 @@
         try {
             if (mMonitorThread == null) {
                 mPendingLogs = new ArrayList<>();
+                mId = 0;
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -137,6 +146,7 @@
                 }
                 // Reset state and clear buffer
                 mPendingLogs = new ArrayList<>();
+                mId = 0;
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -305,6 +315,7 @@
             if (lastNanos > currentNanos) {
                 // New event older than the last we've seen so far, must be due to reordering.
                 if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+                assignLogId(curEvent);
                 mPendingLogs.add(curEvent);
                 curPos++;
             } else if (lastNanos < currentNanos) {
@@ -317,6 +328,7 @@
                     if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
                 } else {
                     // Wow, what a coincidence, or probably the clock is too coarse.
+                    assignLogId(curEvent);
                     mPendingLogs.add(curEvent);
                     if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
                 }
@@ -324,8 +336,13 @@
                 curPos++;
             }
         }
+        // Assign an id to the new logs, after the overlap with mLastEvents.
+        List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size());
+        for (SecurityEvent event : idLogs) {
+            assignLogId(event);
+        }
         // Save the rest of the new batch.
-        mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+        mPendingLogs.addAll(idLogs);
 
         if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
             // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
@@ -334,7 +351,20 @@
                     mPendingLogs.size()));
             Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
         }
-        if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+        if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging,"
+                + " with ids " + mPendingLogs.get(0).getId()
+                + " to " + mPendingLogs.get(mPendingLogs.size() - 1).getId());
+    }
+
+    @GuardedBy("mLock")
+    private void assignLogId(SecurityEvent event) {
+        event.setId(mId);
+        if (mId == Long.MAX_VALUE) {
+            Slog.i(TAG, "Reached maximum id value; wrapping around.");
+            mId = 0;
+        } else {
+            mId++;
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7aa628a..4310a98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
+import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
@@ -327,6 +328,8 @@
 
             // The system server should never make non-oneway calls
             Binder.setWarnOnBlocking(true);
+            // Deactivate SQLiteCompatibilityWalFlags until settings provider is initialized
+            SQLiteCompatibilityWalFlags.init(null);
 
             // Here we go!
             Slog.i(TAG, "Entered the Android system server!");
@@ -803,6 +806,8 @@
 
             traceBeginAndSlog("InstallSystemProviders");
             mActivityManagerService.installSystemProviders();
+            // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
+            SQLiteCompatibilityWalFlags.reset();
             traceEnd();
 
             traceBeginAndSlog("StartVibratorService");
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index b5e4af7..1ca6f26 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -44,7 +44,8 @@
     android-support-test \
     mockito-robolectric-prebuilt \
     platform-test-annotations \
-    truth-prebuilt
+    truth-prebuilt \
+    testng
 
 LOCAL_JAVA_LIBRARIES := \
     junit \
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index 13623e5..ced9b1e 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadow.api.Shadow.extract;
+import static org.testng.Assert.expectThrows;
 
 import android.annotation.Nullable;
 import android.app.backup.BackupManager;
@@ -37,7 +38,6 @@
 import android.platform.test.annotations.Presubmit;
 
 import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.server.backup.testing.ShadowBackupTransportStub;
 import com.android.server.backup.testing.ShadowContextImplForBackup;
 import com.android.server.backup.testing.ShadowPackageManagerForBackup;
@@ -58,6 +58,7 @@
 import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
+import org.testng.Assert.ThrowingRunnable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -710,16 +711,6 @@
         return transportManager;
     }
 
-    private static <T extends Throwable> void expectThrows(
-            Class<T> throwableClass, ThrowingRunnable runnable) {
-        try {
-            runnable.runOrThrow();
-            fail("Expected to throw " + throwableClass.getSimpleName());
-        } catch (Throwable t) {
-            assertThat(t).isInstanceOf(throwableClass);
-        }
-    }
-
     private static class TransportInfo {
         public final String packageName;
         public final String name;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c14f74c..0462b14 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -62,7 +62,7 @@
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
     @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
-    @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+    @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock GlobalActionPerformer mMockGlobalActionPerformer;
     @Mock KeyEventDispatcher mMockKeyEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index dbebd01..8853db2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -59,7 +59,7 @@
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
     @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
-    @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+    @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock GlobalActionPerformer mMockGlobalActionPerformer;
     @Mock IBinder mMockOwner;
@@ -153,13 +153,14 @@
     }
 
     @Test
-    public void uiAutomationBinderDiesBeforeConnecting_shouldNotCrash() throws Exception {
+    public void uiAutomationBinderDiesBeforeConnecting_notifiesSystem() throws Exception {
         register(0);
         ArgumentCaptor<IBinder.DeathRecipient> captor = ArgumentCaptor.forClass(
                 IBinder.DeathRecipient.class);
         verify(mMockOwner).linkToDeath(captor.capture(), anyInt());
         captor.getValue().binderDied();
         mMessageCapturingHandler.sendAllMessages();
+        verify(mMockSystemSupport).onClientChange(false);
     }
 
     private void register(int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index f384044..5a21102 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -572,7 +572,6 @@
         });
         assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
         assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
-        assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
         assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
                 null, 0));
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index d168479..0650acb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -102,6 +102,11 @@
         this.context = injector.context;
     }
 
+    @Override
+    public boolean isPasswordBlacklisted(int userId, String password) {
+        return false;
+    }
+
 
     public void notifyChangeToContentObserver(Uri uri, int userHandle) {
         ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
@@ -205,6 +210,11 @@
         }
 
         @Override
+        PasswordBlacklist newPasswordBlacklist(File file) {
+            return services.passwordBlacklist;
+        }
+
+        @Override
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return services.storageManager.isFileBasedEncryptionEnabled();
         }
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 ca918c6..60783db 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -34,7 +34,6 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.nullable;
 import static org.mockito.Mockito.reset;
@@ -2244,27 +2243,32 @@
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 0);
-        verifyScreenTimeoutCall(null, false);
+        verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(false);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 1);
-        verifyScreenTimeoutCall(1, true);
+        verifyScreenTimeoutCall(1L, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 10);
-        verifyScreenTimeoutCall(null, false);
+        verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(false);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 5);
-        verifyScreenTimeoutCall(5, true);
+        verifyScreenTimeoutCall(5L, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 4);
-        verifyScreenTimeoutCall(4, true);
+        verifyScreenTimeoutCall(4L, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
@@ -2272,24 +2276,89 @@
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
-        dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE);
-        verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
-        reset(getServices().powerManagerInternal);
-        reset(getServices().settings);
-
-        dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE + 1);
-        verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
+        dpm.setMaximumTimeToLock(admin2, Long.MAX_VALUE);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 10);
-        verifyScreenTimeoutCall(10, true);
+        verifyScreenTimeoutCall(10L, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
-        // There's no restriction; shold be set to MAX.
+        // There's no restriction; should be set to MAX.
         dpm.setMaximumTimeToLock(admin2, 0);
-        verifyScreenTimeoutCall(Integer.MAX_VALUE, false);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+        verifyStayOnWhilePluggedCleared(false);
+    }
+
+    // Test if lock timeout on managed profile is handled correctly depending on whether profile
+    // uses separate challenge.
+    public void testSetMaximumTimeToLockProfile() throws Exception {
+        final int PROFILE_USER = 15;
+        final int PROFILE_ADMIN = UserHandle.getUid(PROFILE_USER, 19436);
+        addManagedProfile(admin1, PROFILE_ADMIN, admin1);
+        mContext.binder.callingUid = PROFILE_ADMIN;
+        final DevicePolicyManagerInternal dpmi =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+
+        dpm.setMaximumTimeToLock(admin1, 0);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // First add timeout for the profile.
+        dpm.setMaximumTimeToLock(admin1, 10);
+        verifyScreenTimeoutCall(10L, UserHandle.USER_SYSTEM);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // Add separate challenge
+        when(getServices().lockPatternUtils
+                .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(true);
+        dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+
+        verifyScreenTimeoutCall(10L, PROFILE_USER);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // Remove the timeout.
+        dpm.setMaximumTimeToLock(admin1, 0);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
+        verifyScreenTimeoutCall(null , UserHandle.USER_SYSTEM);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // Add it back.
+        dpm.setMaximumTimeToLock(admin1, 10);
+        verifyScreenTimeoutCall(10L, PROFILE_USER);
+        verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // Remove separate challenge.
+        reset(getServices().lockPatternUtils);
+        when(getServices().lockPatternUtils
+                .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
+        dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+
+        verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
+        verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
+
+        reset(getServices().powerManagerInternal);
+        reset(getServices().settings);
+
+        // Remove the timeout.
+        dpm.setMaximumTimeToLock(admin1, 0);
+        verifyScreenTimeoutCall(null, PROFILE_USER);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
     }
 
     public void testSetRequiredStrongAuthTimeout_DeviceOwner() throws Exception {
@@ -2365,15 +2434,17 @@
                 () -> dpm.setRequiredStrongAuthTimeout(admin1, -ONE_MINUTE));
     }
 
-    private void verifyScreenTimeoutCall(Integer expectedTimeout,
-            boolean shouldStayOnWhilePluggedInBeCleared) {
+    private void verifyScreenTimeoutCall(Long expectedTimeout, int userId) {
         if (expectedTimeout == null) {
             verify(getServices().powerManagerInternal, times(0))
-                    .setMaximumScreenOffTimeoutFromDeviceAdmin(anyInt());
+                    .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(userId), anyLong());
         } else {
             verify(getServices().powerManagerInternal, times(1))
-                    .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(expectedTimeout));
+                    .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(userId), eq(expectedTimeout));
         }
+    }
+
+    private void verifyStayOnWhilePluggedCleared(boolean cleared) {
         // TODO Verify calls to settingsGlobalPutInt.  Tried but somehow mockito threw
         // UnfinishedVerificationException.
     }
@@ -3765,6 +3836,36 @@
         assertTrue(dpm.clearResetPasswordToken(admin1));
     }
 
+    public void testSetPasswordBlacklistCannotBeCalledByNonAdmin() throws Exception {
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPasswordBlacklist(admin1, null, null));
+        verifyZeroInteractions(getServices().passwordBlacklist);
+    }
+
+    public void testClearingPasswordBlacklistDoesNotCreateNewBlacklist() throws Exception {
+        setupProfileOwner();
+        dpm.setPasswordBlacklist(admin1, null, null);
+        verifyZeroInteractions(getServices().passwordBlacklist);
+    }
+
+    public void testSetPasswordBlacklistCreatesNewBlacklist() throws Exception {
+        final String name = "myblacklist";
+        final List<String> explicit = Arrays.asList("password", "letmein");
+        setupProfileOwner();
+        dpm.setPasswordBlacklist(admin1, name, explicit);
+        verify(getServices().passwordBlacklist).savePasswordBlacklist(name, explicit);
+    }
+
+    public void testSetPasswordBlacklistOnlyConvertsExplicitToLowerCase() throws Exception {
+        final List<String> mixedCase = Arrays.asList("password", "LETMEIN", "FooTBAll");
+        final List<String> lowerCase = Arrays.asList("password", "letmein", "football");
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        final String name = "Name of the Blacklist";
+        dpm.setPasswordBlacklist(admin1, name, mixedCase);
+        verify(getServices().passwordBlacklist).savePasswordBlacklist(name, lowerCase);
+    }
+
     public void testIsActivePasswordSufficient() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         mContext.packageName = admin1.getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4ee5ba6..4232c44 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -86,6 +86,7 @@
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
+    public final PasswordBlacklist passwordBlacklist;
     public final StorageManagerForMock storageManager;
     public final WifiManager wifiManager;
     public final SettingsForMock settings;
@@ -120,6 +121,7 @@
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
+        passwordBlacklist = mock(PasswordBlacklist.class);
         storageManager = mock(StorageManagerForMock.class);
         wifiManager = mock(WifiManager.class);
         settings = mock(SettingsForMock.class);
@@ -179,6 +181,13 @@
                     return getUserInfo(userId1);
                 }
         );
+        when(userManager.getProfileParent(anyInt())).thenAnswer(
+                invocation -> {
+                    final int userId1 = (int) invocation.getArguments()[0];
+                    final UserInfo ui = getUserInfo(userId1);
+                    return ui == null ? null : getUserInfo(ui.profileGroupId);
+                }
+        );
         when(userManager.getProfiles(anyInt())).thenAnswer(
                 invocation -> {
                     final int userId12 = (int) invocation.getArguments()[0];
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index a92d1db..c698312 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -15,13 +15,131 @@
  */
 package com.android.server.devicepolicy;
 
+import static com.android.server.devicepolicy.NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
 import android.app.admin.ConnectEvent;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
 import android.os.Parcel;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+
 @SmallTest
 public class NetworkEventTest extends DpmTestBase {
+    private static final int MAX_EVENTS_PER_BATCH = 1200;
+
+    private DpmMockContext mSpiedDpmMockContext;
+    private DevicePolicyManagerServiceTestable mDpmTestable;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSpiedDpmMockContext = spy(mMockContext);
+        mSpiedDpmMockContext.callerPermissions.add(
+                android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+        doNothing().when(mSpiedDpmMockContext).sendBroadcastAsUser(any(Intent.class),
+                any(UserHandle.class));
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext);
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+        mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE);
+    }
+
+    public void testNetworkEventId_monotonicallyIncreasing() throws Exception {
+        // GIVEN the handler has not processed any events.
+        long startingId = 0;
+
+        // WHEN the handler has processed the events.
+        List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId);
+
+        // THEN the events are in a batch.
+        assertTrue("Batch not at the returned token.",
+                events != null && events.size() == MAX_EVENTS_PER_BATCH);
+        // THEN event ids are monotonically increasing.
+        long expectedId = startingId;
+        for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
+            assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+                    events.get(i).getId());
+            expectedId++;
+        }
+    }
+
+    public void testNetworkEventId_wrapsAround() throws Exception {
+        // GIVEN the handler has almost processed Long.MAX_VALUE events.
+        int gap = 5;
+        long startingId = Long.MAX_VALUE - gap;
+
+        // WHEN the handler has processed the events.
+        List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId);
+
+        // THEN the events are in a batch.
+        assertTrue("Batch not at the returned token.",
+                events != null && events.size() == MAX_EVENTS_PER_BATCH);
+        // THEN event ids are monotonically increasing.
+        long expectedId = startingId;
+        for (int i = 0; i < gap; i++) {
+            assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+                    events.get(i).getId());
+            expectedId++;
+        }
+        // THEN event ids are reset when the id reaches the maximum possible value.
+        assertEquals("Event was not assigned the maximum id value.", Long.MAX_VALUE,
+                events.get(gap).getId());
+        assertEquals("Event id was not reset.", 0, events.get(gap + 1).getId());
+        // THEN event ids are monotonically increasing.
+        expectedId = 0;
+        for (int i = gap + 1; i < MAX_EVENTS_PER_BATCH; i++) {
+            assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+                    events.get(i).getId());
+            expectedId++;
+        }
+    }
+
+    private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
+        // GIVEN a handler with events
+        NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
+                mDpmTestable, startingId);
+        // GIVEN network events are sent to the handler.
+        for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
+            ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
+                    SystemClock.currentThreadTimeMillis());
+            Message msg = new Message();
+            msg.what = LOG_NETWORK_EVENT_MSG;
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event);
+            msg.setData(bundle);
+            handler.handleMessage(msg);
+        }
+
+        // WHEN the handler processes the events.
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mSpiedDpmMockContext).sendBroadcastAsUser(intentCaptor.capture(),
+                any(UserHandle.class));
+        assertEquals(intentCaptor.getValue().getAction(),
+                DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE);
+        long token = intentCaptor.getValue().getExtras().getLong(
+                DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, 0);
+        return handler.retrieveFullLogBatch(token);
+    }
 
     /**
      * Test parceling and unparceling of a ConnectEvent.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
new file mode 100644
index 0000000..1b3fc2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link PasswordBlacklist}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
+ */
+@RunWith(AndroidJUnit4.class)
+public final class PasswordBlacklistTest {
+    private File mBlacklistFile;
+    private PasswordBlacklist mBlacklist;
+
+    @Before
+    public void setUp() throws IOException {
+        mBlacklistFile = File.createTempFile("pwdbl", null);
+        mBlacklist = new PasswordBlacklist(mBlacklistFile);
+    }
+
+    @After
+    public void tearDown() {
+        mBlacklist.delete();
+    }
+
+    @Test
+    public void matchIsExact() {
+        // Note: Case sensitivity is handled by the user of PasswordBlacklist by normalizing the
+        // values stored in and tested against it.
+        mBlacklist.savePasswordBlacklist("matchIsExact", Arrays.asList("password", "qWERty"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("password"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("qWERty"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("Password"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("qwert"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("letmein"));
+    }
+
+    @Test
+    public void matchIsNotRegex() {
+        mBlacklist.savePasswordBlacklist("matchIsNotRegex", Arrays.asList("a+b*"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("a+b*"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("abbbb"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+    }
+
+    @Test
+    public void matchFailsSafe() throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(mBlacklistFile)) {
+            // Write a malformed blacklist file
+            fos.write(17);
+        }
+        assertTrue(mBlacklist.isPasswordBlacklisted("anything"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("at"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("ALL"));
+    }
+
+    @Test
+    public void blacklistCanBeNamed() {
+        final String name = "identifier";
+        mBlacklist.savePasswordBlacklist(name, Arrays.asList("one", "two", "three"));
+        assertEquals(mBlacklist.getName(), name);
+    }
+
+    @Test
+    public void reportsTheCorrectNumberOfEntries() {
+        mBlacklist.savePasswordBlacklist("Count Entries", Arrays.asList("1", "2", "3", "4"));
+        assertEquals(mBlacklist.getSize(), 4);
+    }
+
+    @Test
+    public void reportsBlacklistFile() {
+        assertEquals(mBlacklistFile, mBlacklist.getFile());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
new file mode 100644
index 0000000..0f05212
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
@@ -0,0 +1,61 @@
+package com.android.server.devicepolicy;
+
+import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.EventLog;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+public class SecurityEventTest extends DpmTestBase {
+    private static long ID = 549;
+    private static String DATA = "adb shell some_command";
+
+    public void testSecurityEventId() {
+        SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+        assertEquals(ID, event.getId());
+        event.setId(20);
+        assertEquals(20, event.getId());
+    }
+
+    public void testSecurityEventParceling() {
+        // GIVEN an event.
+        SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+        // WHEN parceling the event.
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(event, 0);
+        p.setDataPosition(0);
+        SecurityEvent unparceledEvent = p.readParcelable(SecurityEventTest.class.getClassLoader());
+        p.recycle();
+        // THEN the event state is preserved.
+        assertEquals(event.getTag(), unparceledEvent.getTag());
+        assertEquals(event.getData(), unparceledEvent.getData());
+        assertEquals(event.getTimeNanos(), unparceledEvent.getTimeNanos());
+        assertEquals(event.getId(), unparceledEvent.getId());
+    }
+
+    private List<SecurityEvent> buildSecurityEvents(int numEvents, long id) {
+        // Write an event to the EventLog.
+        for (int i = 0; i < numEvents; i++) {
+            EventLog.writeEvent(TAG_ADB_SHELL_CMD, DATA + "_" + i);
+        }
+        List<EventLog.Event> events = new ArrayList<>();
+        try {
+            EventLog.readEvents(new int[]{TAG_ADB_SHELL_CMD}, events);
+        } catch (IOException e) {
+            fail("Reading a test event from storage failed: " + e);
+        }
+        assertTrue("Unexpected number of events read from the log.", events.size() >= numEvents);
+        // Read events generated by test, from the end of the log.
+        List<SecurityEvent> securityEvents = new ArrayList<>();
+        for (int i = events.size() - numEvents; i < events.size(); i++) {
+          securityEvents.add(new SecurityEvent(id++, events.get(i).getBytes()));
+        }
+        return securityEvents;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
new file mode 100644
index 0000000..b8000c4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeySyncTaskTest {
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+    private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+    private static final int TEST_USER_ID = 1000;
+    private static final int TEST_APP_UID = 10009;
+    private static final String TEST_APP_KEY_ALIAS = "rcleaver";
+    private static final int TEST_GENERATION_ID = 2;
+    private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
+    private static final String TEST_CREDENTIAL = "password1234";
+    private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
+            "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+
+    @Mock private KeySyncTask.RecoverableSnapshotConsumer mRecoverableSnapshotConsumer;
+    @Mock private PlatformKeyManager mPlatformKeyManager;
+
+    @Captor private ArgumentCaptor<byte[]> mSaltCaptor;
+    @Captor private ArgumentCaptor<byte[]> mEncryptedRecoveryKeyCaptor;
+    @Captor private ArgumentCaptor<Map<String, byte[]>> mEncryptedApplicationKeysCaptor;
+
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+    private KeyPair mKeyPair;
+    private AndroidKeyStoreSecretKey mWrappingKey;
+    private PlatformEncryptionKey mEncryptKey;
+
+    private KeySyncTask mKeySyncTask;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+        mKeyPair = SecureBox.genKeyPair();
+
+        mKeySyncTask = new KeySyncTask(
+                mRecoverableKeyStoreDb,
+                TEST_USER_ID,
+                TEST_CREDENTIAL_TYPE,
+                TEST_CREDENTIAL,
+                () -> mPlatformKeyManager,
+                mRecoverableSnapshotConsumer,
+                () -> mKeyPair.getPublic());
+
+        mWrappingKey = generateAndroidKeyStoreKey();
+        mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
+        when(mPlatformKeyManager.getDecryptKey()).thenReturn(
+                new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void isPin_isTrueForNumericString() {
+        assertTrue(KeySyncTask.isPin("3298432574398654376547"));
+    }
+
+    @Test
+    public void isPin_isFalseForStringContainingLetters() {
+        assertFalse(KeySyncTask.isPin("398i54369548654"));
+    }
+
+    @Test
+    public void isPin_isFalseForStringContainingSymbols() {
+        assertFalse(KeySyncTask.isPin("-3987543643"));
+    }
+
+    @Test
+    public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
+        String credentials = "password1234";
+        byte[] salt = randomBytes(16);
+
+        assertArrayEquals(
+                KeySyncTask.hashCredentials(salt, credentials),
+                KeySyncTask.hashCredentials(salt, credentials));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
+        byte[] salt = randomBytes(16);
+
+        assertFalse(
+                Arrays.equals(
+                    KeySyncTask.hashCredentials(salt, "password1234"),
+                    KeySyncTask.hashCredentials(salt, "password12345")));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashForDifferentSalt() {
+        String credentials = "wowmuch";
+
+        assertFalse(
+                Arrays.equals(
+                        KeySyncTask.hashCredentials(randomBytes(64), credentials),
+                        KeySyncTask.hashCredentials(randomBytes(64), credentials)));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
+        assertFalse(
+                Arrays.equals(
+                        KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
+                        KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
+    }
+
+    @Test
+    public void getUiFormat_returnsPinIfPin() {
+        assertEquals(TYPE_PIN,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
+    }
+
+    @Test
+    public void getUiFormat_returnsPasswordIfPassword() {
+        assertEquals(TYPE_PASSWORD,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
+    }
+
+    @Test
+    public void getUiFormat_returnsPatternIfPattern() {
+        assertEquals(TYPE_PATTERN,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
+
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
+        // TODO: proper test here, once we have proper implementation for checking that keys need
+        // to be synced.
+        mKeySyncTask.run();
+
+        verifyZeroInteractions(mRecoverableSnapshotConsumer);
+    }
+
+    @Test
+    public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
+        SecretKey applicationKey = generateKey();
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.insertKey(
+                TEST_USER_ID,
+                TEST_APP_UID,
+                TEST_APP_KEY_ALIAS,
+                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+
+        mKeySyncTask.run();
+
+        verify(mRecoverableSnapshotConsumer).accept(
+                mSaltCaptor.capture(),
+                mEncryptedRecoveryKeyCaptor.capture(),
+                mEncryptedApplicationKeysCaptor.capture());
+        byte[] lockScreenHash = KeySyncTask.hashCredentials(
+                mSaltCaptor.getValue(), TEST_CREDENTIAL);
+        // TODO: what should vault params be here?
+        byte[] recoveryKey = decryptThmEncryptedKey(
+                lockScreenHash,
+                mEncryptedRecoveryKeyCaptor.getValue(),
+                /*vaultParams=*/ new byte[0]);
+        Map<String, byte[]> applicationKeys = mEncryptedApplicationKeysCaptor.getValue();
+        assertEquals(1, applicationKeys.size());
+        byte[] appKey = KeySyncUtils.decryptApplicationKey(
+                recoveryKey, applicationKeys.get(TEST_APP_KEY_ALIAS));
+        assertArrayEquals(applicationKey.getEncoded(), appKey);
+    }
+
+    private byte[] decryptThmEncryptedKey(
+            byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
+        byte[] locallyEncryptedKey = SecureBox.decrypt(
+                mKeyPair.getPrivate(),
+                /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
+                /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+                encryptedKey
+        );
+        return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
+    }
+
+    private SecretKey generateKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(/*keySize=*/ 256);
+        return keyGenerator.generateKey();
+    }
+
+    private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                KEY_ALGORITHM,
+                ANDROID_KEY_STORE_PROVIDER);
+        keyGenerator.init(new KeyGenParameterSpec.Builder(
+                WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                .build());
+        return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+    }
+
+    private static byte[] utf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static byte[] randomBytes(int n) {
+        byte[] bytes = new byte[n];
+        new Random().nextBytes(bytes);
+        return bytes;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index c918e8c..6254d52 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import static junit.framework.Assert.fail;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -23,13 +25,20 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.google.common.collect.ImmutableMap;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
 import java.security.MessageDigest;
 import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
 
+import javax.crypto.AEADBadTagException;
+import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
 @SmallTest
@@ -37,7 +46,17 @@
 public class KeySyncUtilsTest {
     private static final int RECOVERY_KEY_LENGTH_BITS = 256;
     private static final int THM_KF_HASH_SIZE = 256;
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
     private static final String SHA_256_ALGORITHM = "SHA-256";
+    private static final String APPLICATION_KEY_ALGORITHM = "AES";
+    private static final byte[] LOCK_SCREEN_HASH_1 =
+            utf8Bytes("g09TEvo6XqVdNaYdRggzn5w2C5rCeE1F");
+    private static final byte[] LOCK_SCREEN_HASH_2 =
+            utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe");
+    private static final byte[] RECOVERY_CLAIM_HEADER =
+            "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
 
     @Test
     public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
@@ -70,6 +89,259 @@
         assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded()));
     }
 
+    @Test
+    public void generateKeyClaimant_returns16Bytes() throws Exception {
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+
+        assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length);
+    }
+
+    @Test
+    public void generateKeyClaimant_generatesANewClaimantEachTime() {
+        byte[] a = KeySyncUtils.generateKeyClaimant();
+        byte[] b = KeySyncUtils.generateKeyClaimant();
+
+        assertFalse(Arrays.equals(a, b));
+    }
+
+    @Test
+    public void concat_concatenatesArrays() {
+        assertArrayEquals(
+                utf8Bytes("hello, world!"),
+                KeySyncUtils.concat(
+                        utf8Bytes("hello"),
+                        utf8Bytes(", "),
+                        utf8Bytes("world"),
+                        utf8Bytes("!")));
+    }
+
+    @Test
+    public void decryptApplicationKey_decryptsAnApplicationKeyEncryptedWithSecureBox()
+            throws Exception {
+        String alias = "phoebe";
+        SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+        SecretKey applicationKey = generateApplicationKey();
+        Map<String, byte[]> encryptedKeys =
+                KeySyncUtils.encryptKeysWithRecoveryKey(
+                        recoveryKey, ImmutableMap.of(alias, applicationKey));
+        byte[] encryptedKey = encryptedKeys.get(alias);
+
+        byte[] keyMaterial =
+                KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), encryptedKey);
+
+        assertArrayEquals(applicationKey.getEncoded(), keyMaterial);
+    }
+
+    @Test
+    public void decryptApplicationKey_throwsIfUnableToDecrypt() throws Exception {
+        String alias = "casper";
+        Map<String, byte[]> encryptedKeys =
+                KeySyncUtils.encryptKeysWithRecoveryKey(
+                        KeySyncUtils.generateRecoveryKey(),
+                        ImmutableMap.of("casper", generateApplicationKey()));
+        byte[] encryptedKey = encryptedKeys.get(alias);
+
+        try {
+            KeySyncUtils.decryptApplicationKey(
+                    KeySyncUtils.generateRecoveryKey().getEncoded(), encryptedKey);
+            fail("Did not throw decrypting with bad key.");
+        } catch (AEADBadTagException error) {
+            // expected
+        }
+    }
+
+    @Test
+    public void decryptRecoveryKey_decryptsALocallyEncryptedKey() throws Exception {
+        SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+        byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(
+                LOCK_SCREEN_HASH_1, recoveryKey);
+
+        byte[] keyMaterial = KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_1, encrypted);
+
+        assertArrayEquals(recoveryKey.getEncoded(), keyMaterial);
+    }
+
+    @Test
+    public void decryptRecoveryKey_throwsIfCannotDecrypt() throws Exception {
+        SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+        byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(LOCK_SCREEN_HASH_1, recoveryKey);
+
+        try {
+            KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_2, encrypted);
+            fail("Did not throw decrypting with bad key.");
+        } catch (AEADBadTagException error) {
+            // expected
+        }
+    }
+
+    @Test
+    public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception {
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] vaultParams = randomBytes(100);
+        byte[] recoveryKey = randomBytes(32);
+        byte[] encryptedPayload = SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ recoveryKey);
+
+        byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
+                keyClaimant, vaultParams, encryptedPayload);
+
+        assertArrayEquals(recoveryKey, decrypted);
+    }
+
+    @Test
+    public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception {
+        byte[] vaultParams = randomBytes(100);
+        byte[] recoveryKey = randomBytes(32);
+        byte[] encryptedPayload = SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ recoveryKey);
+
+        try {
+            KeySyncUtils.decryptRecoveryClaimResponse(
+                    KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload);
+            fail("Did not throw decrypting with bad keyClaimant");
+        } catch (AEADBadTagException error) {
+            // expected
+        }
+    }
+
+    @Test
+    public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception {
+        KeyPair keyPair = SecureBox.genKeyPair();
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] challenge = randomBytes(32);
+        byte[] vaultParams = randomBytes(100);
+
+        byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+                keyPair.getPublic(),
+                vaultParams,
+                challenge,
+                LOCK_SCREEN_HASH_1,
+                keyClaimant);
+
+        byte[] decrypted = SecureBox.decrypt(
+                keyPair.getPrivate(),
+                /*sharedSecret=*/ null,
+                /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                encryptedRecoveryClaim);
+        assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
+    }
+
+    @Test
+    public void encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge() throws Exception {
+        KeyPair keyPair = SecureBox.genKeyPair();
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] vaultParams = randomBytes(100);
+
+        byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+                keyPair.getPublic(),
+                vaultParams,
+                /*challenge=*/ randomBytes(32),
+                LOCK_SCREEN_HASH_1,
+                keyClaimant);
+
+        try {
+            SecureBox.decrypt(
+                    keyPair.getPrivate(),
+                    /*sharedSecret=*/ null,
+                    /*header=*/ KeySyncUtils.concat(
+                            RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
+                    encryptedRecoveryClaim);
+            fail("Should throw if challenge is incorrect.");
+        } catch (AEADBadTagException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey() throws Exception {
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] challenge = randomBytes(32);
+        byte[] vaultParams = randomBytes(100);
+
+        byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+                SecureBox.genKeyPair().getPublic(),
+                vaultParams,
+                challenge,
+                LOCK_SCREEN_HASH_1,
+                keyClaimant);
+
+        try {
+            SecureBox.decrypt(
+                    SecureBox.genKeyPair().getPrivate(),
+                    /*sharedSecret=*/ null,
+                    /*header=*/ KeySyncUtils.concat(
+                            RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                    encryptedRecoveryClaim);
+            fail("Should throw if secret key is incorrect.");
+        } catch (AEADBadTagException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams() throws Exception {
+        KeyPair keyPair = SecureBox.genKeyPair();
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] challenge = randomBytes(32);
+
+        byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+                keyPair.getPublic(),
+                /*vaultParams=*/ randomBytes(100),
+                challenge,
+                LOCK_SCREEN_HASH_1,
+                keyClaimant);
+
+        try {
+            SecureBox.decrypt(
+                    keyPair.getPrivate(),
+                    /*sharedSecret=*/ null,
+                    /*header=*/ KeySyncUtils.concat(
+                            RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
+                    encryptedRecoveryClaim);
+            fail("Should throw if vault params is incorrect.");
+        } catch (AEADBadTagException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader() throws Exception {
+        KeyPair keyPair = SecureBox.genKeyPair();
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] challenge = randomBytes(32);
+        byte[] vaultParams = randomBytes(100);
+
+        byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+                keyPair.getPublic(),
+                vaultParams,
+                challenge,
+                LOCK_SCREEN_HASH_1,
+                keyClaimant);
+
+        try {
+            SecureBox.decrypt(
+                    keyPair.getPrivate(),
+                    /*sharedSecret=*/ null,
+                    /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
+                    encryptedRecoveryClaim);
+            fail("Should throw if header is incorrect.");
+        } catch (AEADBadTagException e) {
+            // expected
+        }
+    }
+
+    private static byte[] randomBytes(int n) {
+        byte[] bytes = new byte[n];
+        new Random().nextBytes(bytes);
+        return bytes;
+    }
+
     private static byte[] utf8Bytes(String s) {
         return s.getBytes(StandardCharsets.UTF_8);
     }
@@ -79,4 +351,10 @@
         messageDigest.update(bytes);
         return messageDigest.digest();
     }
+
+    private static SecretKey generateApplicationKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(APPLICATION_KEY_ALGORITHM);
+        keyGenerator.init(/*keySize=*/ 256);
+        return keyGenerator.generateKey();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
new file mode 100644
index 0000000..6f13a98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PlatformKeyManagerTest {
+
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+    private static final int USER_ID_FIXTURE = 42;
+
+    @Mock private Context mContext;
+    @Mock private KeyStoreProxy mKeyStoreProxy;
+    @Mock private KeyguardManager mKeyguardManager;
+
+    @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
+    @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
+
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+
+    private PlatformKeyManager mPlatformKeyManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+        mPlatformKeyManager = new PlatformKeyManager(
+                USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
+
+        when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
+        when(mContext.getSystemServiceName(any())).thenReturn("test");
+        when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void init_createsEncryptKeyWithCorrectAlias() throws Exception {
+        mPlatformKeyManager.init();
+
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                any(),
+                any());
+    }
+
+    @Test
+    public void init_createsEncryptKeyWithCorrectPurposes() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes());
+    }
+
+    @Test
+    public void init_createsEncryptKeyWithCorrectPaddings() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertArrayEquals(
+                new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
+                getEncryptKeyProtection().getEncryptionPaddings());
+    }
+
+    @Test
+    public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertArrayEquals(
+                new String[] { KeyProperties.BLOCK_MODE_GCM },
+                getEncryptKeyProtection().getBlockModes());
+    }
+
+    @Test
+    public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithCorrectAlias() throws Exception {
+        mPlatformKeyManager.init();
+
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                any(),
+                any());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithCorrectPurposes() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithCorrectPaddings() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertArrayEquals(
+                new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
+                getDecryptKeyProtection().getEncryptionPaddings());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertArrayEquals(
+                new String[] { KeyProperties.BLOCK_MODE_GCM },
+                getDecryptKeyProtection().getBlockModes());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+    }
+
+    @Test
+    public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertEquals(
+                USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
+                getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+    }
+
+    @Test
+    public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertEquals(
+                USER_ID_FIXTURE,
+                getDecryptKeyProtection().getBoundToSpecificSecureUserId());
+    }
+
+    @Test
+    public void init_createsBothKeysWithSameMaterial() throws Exception {
+        mPlatformKeyManager.init();
+
+        verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any());
+        List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues();
+        assertArrayEquals(
+                ((KeyStore.SecretKeyEntry) entries.get(0)).getSecretKey().getEncoded(),
+                ((KeyStore.SecretKeyEntry) entries.get(1)).getSecretKey().getEncoded());
+    }
+
+    @Test
+    public void init_setsGenerationIdTo1() throws Exception {
+        mPlatformKeyManager.init();
+
+        assertEquals(1, mPlatformKeyManager.getGenerationId());
+    }
+
+    @Test
+    public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+        mPlatformKeyManager.getDecryptKey();
+
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                any());
+    }
+
+    @Test
+    public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+        mPlatformKeyManager.getEncryptKey();
+
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                any());
+    }
+
+    @Test
+    public void regenerate_incrementsTheGenerationId() throws Exception {
+        mPlatformKeyManager.init();
+
+        mPlatformKeyManager.regenerate();
+
+        assertEquals(2, mPlatformKeyManager.getGenerationId());
+    }
+
+    @Test
+    public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception {
+        mPlatformKeyManager.init();
+
+        mPlatformKeyManager.regenerate();
+
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                any(),
+                any());
+    }
+
+    @Test
+    public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception {
+        mPlatformKeyManager.init();
+
+        mPlatformKeyManager.regenerate();
+
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                any(),
+                any());
+    }
+
+    private KeyProtection getEncryptKeyProtection() throws Exception {
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                any(),
+                mProtectionParameterCaptor.capture());
+        return (KeyProtection) mProtectionParameterCaptor.getValue();
+    }
+
+    private KeyProtection getDecryptKeyProtection() throws Exception {
+        verify(mKeyStoreProxy).setEntry(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                any(),
+                mProtectionParameterCaptor.capture());
+        return (KeyProtection) mProtectionParameterCaptor.getValue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 298a988..8a461ac 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,61 +16,65 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import static junit.framework.Assert.assertNotNull;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.nio.charset.StandardCharsets;
 import java.security.KeyStore;
 
+import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RecoverableKeyGeneratorTest {
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+    private static final int TEST_GENERATION_ID = 3;
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String KEY_ALGORITHM = "AES";
+    private static final int KEY_SIZE_BYTES = 32;
+    private static final String KEY_WRAP_ALGORITHM = "AES/GCM/NoPadding";
     private static final String TEST_ALIAS = "karlin";
     private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
+    private static final int TEST_USER_ID = 1000;
+    private static final int KEYSTORE_UID_SELF = -1;
+    private static final int GCM_TAG_LENGTH_BITS = 128;
 
-    @Mock
-    RecoverableKeyStorage mRecoverableKeyStorage;
-
-    @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
-
-    private AndroidKeyStoreSecretKey mPlatformKey;
-    private SecretKey mKeyHandle;
+    private PlatformEncryptionKey mPlatformKey;
+    private PlatformDecryptionKey mDecryptKey;
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
     private RecoverableKeyGenerator mRecoverableKeyGenerator;
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mPlatformKey = generateAndroidKeyStoreKey();
-        mKeyHandle = generateKey();
-        mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(
-                mPlatformKey, mRecoverableKeyStorage);
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
 
-        when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle);
+        AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+        mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
+        mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
+        mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
     }
 
     @After
@@ -78,67 +82,39 @@
         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
         keyStore.load(/*param=*/ null);
         keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
-    }
 
-    @Test
-    public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        verify(mRecoverableKeyStorage, times(1))
-                .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any());
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertEquals(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
-                keyProtection.getPurposes());
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM },
-                keyProtection.getBlockModes());
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForNoPadding() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
-                keyProtection.getEncryptionPaddings());
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
     }
 
     @Test
     public void generateAndStoreKey_storesWrappedKey() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+        mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any());
+        WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+        assertNotNull(wrappedKey);
     }
 
     @Test
-    public void generateAndStoreKey_returnsKeyHandle() throws Exception {
-        SecretKey secretKey = mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+    public void generateAndStoreKey_returnsRawMaterialOfCorrectLength() throws Exception {
+        byte[] rawKey = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        assertEquals(mKeyHandle, secretKey);
+        assertEquals(KEY_SIZE_BYTES, rawKey.length);
     }
 
-    private KeyProtection getKeyProtectionUsed() throws Exception {
-        verify(mRecoverableKeyStorage, times(1)).importIntoAndroidKeyStore(
-                any(), any(), mKeyProtectionArgumentCaptor.capture());
-        return mKeyProtectionArgumentCaptor.getValue();
-    }
+    @Test
+    public void generateAndStoreKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception {
+        byte[] rawMaterial = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-    private SecretKey generateKey() throws Exception {
-        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
-        keyGenerator.init(/*keySize=*/ 256);
-        return keyGenerator.generateKey();
+        WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+        Cipher cipher = Cipher.getInstance(KEY_WRAP_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, mDecryptKey.getKey(),
+                new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
+        byte[] unwrappedMaterial = cipher.doFinal(wrappedKey.getKeyMaterial());
+        assertArrayEquals(rawMaterial, unwrappedMaterial);
     }
 
     private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
@@ -152,4 +128,8 @@
                     .build());
         return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
     }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
deleted file mode 100644
index fb4e75e..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
+++ /dev/null
@@ -1,155 +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.server.locksettings.recoverablekeystore;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RecoverableKeyStorageImplTest {
-    private static final String KEY_ALGORITHM = "AES";
-    private static final int GCM_TAG_LENGTH_BYTES = 16;
-    private static final int BITS_PER_BYTE = 8;
-    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
-    private static final String TEST_KEY_ALIAS = "RecoverableKeyStorageImplTestKey";
-    private static final int KEYSTORE_UID_SELF = -1;
-
-    private RecoverableKeyStorageImpl mRecoverableKeyStorage;
-
-    @Before
-    public void setUp() throws Exception {
-        mRecoverableKeyStorage = RecoverableKeyStorageImpl.newInstance(
-                /*userId=*/ KEYSTORE_UID_SELF);
-    }
-
-    @After
-    public void tearDown() {
-        try {
-            mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-        } catch (KeyStoreException e) {
-            // Do nothing.
-        }
-    }
-
-    @Test
-    public void loadFromAndroidKeyStore_loadsAKeyThatWasImported() throws Exception {
-        SecretKey key = generateKey();
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                key,
-                getKeyProperties());
-
-        assertKeysAreEquivalent(
-                key, mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
-    }
-
-    @Test
-    public void importIntoAndroidKeyStore_importsWithKeyProperties() throws Exception {
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                generateKey(),
-                getKeyProperties());
-
-        SecretKey key = mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS);
-
-        Mac mac = Mac.getInstance("HmacSHA256");
-        try {
-            // Fails because missing PURPOSE_SIGN or PURPOSE_VERIFY
-            mac.init(key);
-            fail("Was able to initialize Mac with an ENCRYPT/DECRYPT-only key.");
-        } catch (InvalidKeyException e) {
-            // expect exception
-        }
-    }
-
-    @Test
-    public void removeFromAndroidKeyStore_removesAnEntry() throws Exception {
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                generateKey(),
-                getKeyProperties());
-
-        mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-
-        assertNull(mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
-    }
-
-    private static KeyProtection getKeyProperties() {
-        return new KeyProtection.Builder(
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                .build();
-    }
-
-    /**
-     * Asserts that {@code b} key can decrypt data encrypted with {@code a} key. Otherwise throws.
-     */
-    private static void assertKeysAreEquivalent(SecretKey a, SecretKey b) throws Exception {
-        byte[] plaintext = "doge".getBytes(StandardCharsets.UTF_8);
-        byte[] nonce = generateGcmNonce();
-
-        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-        cipher.init(Cipher.ENCRYPT_MODE, a, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-        byte[] encrypted = cipher.doFinal(plaintext);
-
-        cipher.init(Cipher.DECRYPT_MODE, b, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-        byte[] decrypted = cipher.doFinal(encrypted);
-
-        assertArrayEquals(decrypted, plaintext);
-    }
-
-    /**
-     * Returns a new random GCM nonce.
-     */
-    private static byte[] generateGcmNonce() {
-        Random random = new Random();
-        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
-        random.nextBytes(nonce);
-        return nonce;
-    }
-
-    private static SecretKey generateKey() throws Exception {
-        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
-        keyGenerator.init(/*keySize=*/ 256);
-        return keyGenerator.generateKey();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
new file mode 100644
index 0000000..c2166e5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -0,0 +1,458 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.Executors;
+import java.util.Map;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreManagerTest {
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+    private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final String TEST_SESSION_ID = "karlin";
+    private static final byte[] TEST_PUBLIC_KEY = new byte[] {
+        (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
+        (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+        (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
+        (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
+        (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
+        (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
+        (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+        (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
+        (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
+        (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
+        (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
+        (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
+    private static final byte[] TEST_SALT = getUtf8Bytes("salt");
+    private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
+    private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
+    private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+    private static final int TEST_USER_ID = 10009;
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+    private static final String TEST_ALIAS = "nick";
+    private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
+    private static final int GENERATION_ID = 1;
+    private static final byte[] NONCE = getUtf8Bytes("nonce");
+    private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
+    private static final int GCM_TAG_SIZE_BITS = 128;
+
+    @Mock private Context mMockContext;
+    @Mock private ListenersStorage mMockListenersStorage;
+    @Mock private KeyguardManager mKeyguardManager;
+
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+    private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+    private RecoverySessionStorage mRecoverySessionStorage;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+
+        mRecoverySessionStorage = new RecoverySessionStorage();
+
+        when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
+        when(mMockContext.getSystemServiceName(any())).thenReturn("test");
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+        mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
+                mMockContext,
+                mRecoverableKeyStoreDb,
+                mRecoverySessionStorage,
+                Executors.newSingleThreadExecutor(),
+                mMockListenersStorage);
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void generateAndStoreKey_storesTheKey() throws Exception {
+        int uid = Binder.getCallingUid();
+
+        mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+
+        assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
+    }
+
+    @Test
+    public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
+        assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
+                .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
+    }
+
+    @Test
+    public void startRecoverySession_checksPermissionFirst() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(
+                        new KeyStoreRecoveryMetadata(
+                                TYPE_LOCKSCREEN,
+                                TYPE_PASSWORD,
+                                KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                                TEST_SECRET)),
+                TEST_USER_ID);
+
+        verify(mMockContext, times(1))
+                .enforceCallingOrSelfPermission(
+                        eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
+    }
+
+    @Test
+    public void startRecoverySession_storesTheSessionInfo() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(
+                        new KeyStoreRecoveryMetadata(
+                                TYPE_LOCKSCREEN,
+                                TYPE_PASSWORD,
+                                KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                                TEST_SECRET)),
+                TEST_USER_ID);
+
+        assertEquals(1, mRecoverySessionStorage.size());
+        RecoverySessionStorage.Entry entry =
+                mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID);
+        assertArrayEquals(TEST_SECRET, entry.getLskfHash());
+        assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
+    }
+
+    @Test
+    public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.startRecoverySession(
+                    TEST_SESSION_ID,
+                    TEST_PUBLIC_KEY,
+                    TEST_VAULT_PARAMS,
+                    TEST_VAULT_CHALLENGE,
+                    ImmutableList.of(),
+                    TEST_USER_ID);
+            fail("should have thrown");
+        } catch (RemoteException e) {
+            assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
+        }
+    }
+
+    @Test
+    public void startRecoverySession_throwsIfBadKey() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.startRecoverySession(
+                    TEST_SESSION_ID,
+                    getUtf8Bytes("0"),
+                    TEST_VAULT_PARAMS,
+                    TEST_VAULT_CHALLENGE,
+                    ImmutableList.of(
+                            new KeyStoreRecoveryMetadata(
+                                    TYPE_LOCKSCREEN,
+                                    TYPE_PASSWORD,
+                                    KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                                    TEST_SECRET)),
+                    TEST_USER_ID);
+            fail("should have thrown");
+        } catch (RemoteException e) {
+            assertEquals("Not a valid X509 key", e.getMessage());
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*recoveryKeyBlob=*/ randomBytes(32),
+                    /*applicationKeys=*/ ImmutableList.of(
+                            new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32))
+                    ),
+                    TEST_USER_ID);
+            fail("should have thrown");
+        } catch (RemoteException e) {
+            assertEquals("User 10009 does not have pending session 'karlin'",
+                    e.getMessage());
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                        TEST_SECRET)),
+                TEST_USER_ID);
+
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*encryptedRecoveryKey=*/ randomBytes(60),
+                    /*applicationKeys=*/ ImmutableList.of(),
+                    /*uid=*/ TEST_USER_ID);
+            fail("should have thrown");
+        } catch (RemoteException e) {
+            assertEquals("Failed to decrypt recovery key", e.getMessage());
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                        TEST_SECRET)),
+                TEST_USER_ID);
+        byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
+                .getKeyClaimant();
+        SecretKey recoveryKey = randomRecoveryKey();
+        byte[] encryptedClaimResponse = encryptClaimResponse(
+                keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+        KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
+                TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
+                randomBytes(32));
+
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*encryptedRecoveryKey=*/ encryptedClaimResponse,
+                    /*applicationKeys=*/ ImmutableList.of(badApplicationKey),
+                    /*uid=*/ TEST_USER_ID);
+            fail("should have thrown");
+        } catch (RemoteException e) {
+            assertEquals("Failed to recover key with alias 'nick'", e.getMessage());
+        }
+    }
+
+    @Test
+    public void recoverKeys_returnsDecryptedKeys() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                        TEST_SECRET)),
+                TEST_USER_ID);
+        byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
+                .getKeyClaimant();
+        SecretKey recoveryKey = randomRecoveryKey();
+        byte[] encryptedClaimResponse = encryptClaimResponse(
+                keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+        byte[] applicationKeyBytes = randomBytes(32);
+        KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
+                TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
+                encryptedApplicationKey(recoveryKey, applicationKeyBytes));
+
+        Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+                TEST_SESSION_ID,
+                encryptedClaimResponse,
+                ImmutableList.of(applicationKey),
+                TEST_USER_ID);
+
+        assertThat(recoveredKeys).hasSize(1);
+        assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes);
+    }
+
+    @Test
+    public void setSnapshotCreatedPendingIntent() throws Exception {
+        int uid = Binder.getCallingUid();
+        PendingIntent intent = PendingIntent.getBroadcast(
+                InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
+                new Intent(), /*flags=*/ 0);
+        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, /*userId=*/ 0);
+        verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
+    }
+
+    @Test
+    public void setRecoveryStatus_forOneAlias() throws Exception {
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        int status = 100;
+        int status2 = 200;
+        String alias = "key1";
+        WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+        Map<String, Integer> statuses =
+                mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(1);
+        assertThat(statuses).containsEntry(alias, status);
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias}, status2, userId);
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(1);
+        assertThat(statuses).containsEntry(alias, status2); // updated
+    }
+
+    @Test
+    public void setRecoveryStatus_for2Aliases() throws Exception {
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        int status = 100;
+        int status2 = 200;
+        int status3 = 300;
+        String alias = "key1";
+        String alias2 = "key2";
+        WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
+        Map<String, Integer> statuses =
+                mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status);
+        assertThat(statuses).containsEntry(alias2, status);
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, /*aliases=*/ null, status2, userId);
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status2); // updated
+        assertThat(statuses).containsEntry(alias2, status2); // updated
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias2}, status3, userId);
+
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status2);
+        assertThat(statuses).containsEntry(alias2, status3); // updated
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias, alias2}, status, userId);
+
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status); // updated
+        assertThat(statuses).containsEntry(alias2, status); // updated
+    }
+
+    private static byte[] encryptedApplicationKey(
+            SecretKey recoveryKey, byte[] applicationKey) throws Exception {
+        return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
+                "alias", new SecretKeySpec(applicationKey, "AES")
+        )).get("alias");
+    }
+
+    private static byte[] encryptClaimResponse(
+            byte[] keyClaimant,
+            byte[] lskfHash,
+            byte[] vaultParams,
+            SecretKey recoveryKey) throws Exception {
+        byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
+                lskfHash, recoveryKey);
+        return SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ locallyEncryptedRecoveryKey);
+    }
+
+    private static SecretKey randomRecoveryKey() {
+        return new SecretKeySpec(randomBytes(32), "AES");
+    }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static byte[] randomBytes(int n) {
+        byte[] bytes = new byte[n];
+        new Random().nextBytes(bytes);
+        return bytes;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
new file mode 100644
index 0000000..35ec23b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecureBoxTest {
+
+    private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
+    private static final int NUM_TEST_ITERATIONS = 100;
+    private static final int VERSION_LEN_BYTES = 2;
+
+    // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
+    // cross-verify the two implementations.
+    private static final byte[] VAULT_PARAMS =
+            new byte[] {
+                (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98,
+                (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe,
+                (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01,
+                (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61,
+                (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+                (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f,
+                (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
+                (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a,
+                (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2,
+                (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+                (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, (byte) 0x31,
+                (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00
+            };
+    private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
+    private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
+    private static final byte[] ENCRYPTED_RECOVERY_KEY =
+            new byte[] {
+                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
+                (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
+                (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
+                (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
+                (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
+                (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
+                (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
+                (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
+                (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
+                (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
+                (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
+                (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
+                (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
+                (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
+                (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
+                (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
+                (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
+                (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
+                (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
+                (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
+            };
+    private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
+    private static final byte[] RECOVERY_CLAIM =
+            new byte[] {
+                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
+                (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
+                (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
+                (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
+                (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
+                (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
+                (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
+                (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
+                (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
+                (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
+                (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
+                (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
+                (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
+                (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
+                (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
+                (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
+                (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
+                (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
+                (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
+                (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
+                (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
+                (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
+                (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
+                (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
+            };
+
+    private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
+    private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
+    private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
+
+    private static final PublicKey THM_PUBLIC_KEY;
+    private static final PrivateKey THM_PRIVATE_KEY;
+
+    static {
+        try {
+            THM_PUBLIC_KEY =
+                    SecureBox.decodePublicKey(
+                            new byte[] {
+                                (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18,
+                                (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4,
+                                (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c,
+                                (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a,
+                                (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0,
+                                (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+                                (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
+                                (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79,
+                                (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0,
+                                (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32,
+                                (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc,
+                                (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+                                (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa
+                            });
+            THM_PRIVATE_KEY =
+                    decodePrivateKey(
+                            new byte[] {
+                                (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
+                                (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
+                                (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
+                                (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
+                                (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
+                                (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
+                                (byte) 0x77, (byte) 0x01
+                            });
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Test
+    public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
+        KeyPair keyPair1 = SecureBox.genKeyPair();
+        KeyPair keyPair2 = SecureBox.genKeyPair();
+        assertThat(keyPair1).isNotEqualTo(keyPair2);
+    }
+
+    @Test
+    public void decryptRecoveryClaim() throws Exception {
+        byte[] claimContent =
+                SecureBox.decrypt(
+                        THM_PRIVATE_KEY,
+                        /*sharedSecret=*/ null,
+                        SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+                        RECOVERY_CLAIM);
+        assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+    }
+
+    @Test
+    public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
+        SecureBox.decrypt(
+                THM_PRIVATE_KEY,
+                THM_KF_HASH,
+                SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+                ENCRYPTED_RECOVERY_KEY);
+    }
+
+    @Test
+    public void encryptThenDecrypt() throws Exception {
+        byte[] state = TEST_PAYLOAD;
+        // Iterate multiple times to amplify any errors
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+        }
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+        }
+        assertThat(state).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(
+                        /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullSharedSecret() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullHeader() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullPayload() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
+        byte[] decrypted =
+                SecureBox.decrypt(
+                        THM_PRIVATE_KEY,
+                        TEST_SHARED_SECRET,
+                        TEST_HEADER,
+                        /*encryptedPayload=*/ encrypted);
+        assertThat(decrypted.length).isEqualTo(0);
+    }
+
+    @Test
+    public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
+        IllegalArgumentException expected =
+                expectThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                SecureBox.encrypt(
+                                        /*theirPublicKey=*/ null,
+                                        /*sharedSecret=*/ null,
+                                        TEST_HEADER,
+                                        TEST_PAYLOAD));
+        assertThat(expected.getMessage()).contains("public key and shared secret");
+    }
+
+    @Test
+    public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
+        IllegalArgumentException expected =
+                expectThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        /*ourPrivateKey=*/ null,
+                                        /*sharedSecret=*/ null,
+                                        TEST_HEADER,
+                                        TEST_PAYLOAD));
+        assertThat(expected.getMessage()).contains("private key and shared secret");
+    }
+
+    @Test
+    public void decrypt_nullEncryptedPayload() throws Exception {
+        NullPointerException expected =
+                expectThrows(
+                        NullPointerException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        THM_PRIVATE_KEY,
+                                        TEST_SHARED_SECRET,
+                                        TEST_HEADER,
+                                        /*encryptedPayload=*/ null));
+        assertThat(expected.getMessage()).contains("payload");
+    }
+
+    @Test
+    public void decrypt_badAuthenticationTag() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        encrypted[encrypted.length - 1] ^= (byte) 1;
+
+        assertThrows(
+                AEADBadTagException.class,
+                () ->
+                        SecureBox.decrypt(
+                                THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+    }
+
+    @Test
+    public void encrypt_invalidPublicKey() throws Exception {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        PublicKey publicKey = keyGen.genKeyPair().getPublic();
+
+        assertThrows(
+                InvalidKeyException.class,
+                () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
+    }
+
+    @Test
+    public void decrypt_invalidPrivateKey() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
+
+        assertThrows(
+                InvalidKeyException.class,
+                () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+    }
+
+    @Test
+    public void decrypt_publicKeyOutsideCurve() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        // Flip the least significant bit of the encoded public key
+        encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
+
+        InvalidKeyException expected =
+                expectThrows(
+                        InvalidKeyException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        THM_PRIVATE_KEY,
+                                        TEST_SHARED_SECRET,
+                                        TEST_HEADER,
+                                        encrypted));
+        assertThat(expected.getMessage()).contains("expected curve");
+    }
+
+    @Test
+    public void encodeThenDecodePublicKey() throws Exception {
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            PublicKey originalKey = SecureBox.genKeyPair().getPublic();
+            byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
+            PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
+            assertThat(originalKey).isEqualTo(decodedKey);
+        }
+    }
+
+    private static byte[] getBytes(String str) {
+        return str.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
+        assertThat(keyBytes.length).isEqualTo(32);
+        BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("EC");
+        return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
index fa73722..56122a7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
@@ -46,6 +47,7 @@
     private static final String KEY_ALGORITHM = "AES";
     private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
     private static final String WRAPPING_KEY_ALIAS = "WrappedKeyTestWrappingKeyAlias";
+    private static final int GENERATION_ID = 1;
     private static final int GCM_TAG_LENGTH_BYTES = 16;
     private static final int BITS_PER_BYTE = 8;
     private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
@@ -59,7 +61,8 @@
 
     @Test
     public void fromSecretKey_createsWrappedKeyThatCanBeUnwrapped() throws Exception {
-        SecretKey wrappingKey = generateAndroidKeyStoreKey();
+        PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
+                GENERATION_ID, generateAndroidKeyStoreKey());
         SecretKey rawKey = generateKey();
 
         WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
@@ -67,7 +70,7 @@
         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
         cipher.init(
                 Cipher.UNWRAP_MODE,
-                wrappingKey,
+                wrappingKey.getKey(),
                 new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
         SecretKey unwrappedKey = (SecretKey) cipher.unwrap(
                 wrappedKey.getKeyMaterial(), KEY_ALGORITHM, Cipher.SECRET_KEY);
@@ -75,15 +78,28 @@
     }
 
     @Test
+    public void fromSecretKey_returnsAKeyWithTheGenerationIdOfTheWrappingKey() throws Exception {
+        PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
+                GENERATION_ID, generateAndroidKeyStoreKey());
+        SecretKey rawKey = generateKey();
+
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
+
+        assertEquals(GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
+    }
+
+    @Test
     public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
         String alias = "karlin";
-        SecretKey platformKey = generateAndroidKeyStoreKey();
+        AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
         SecretKey appKey = generateKey();
-        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, appKey);
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+                new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
         HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
         keysByAlias.put(alias, wrappedKey);
 
-        Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias);
+        Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
+                new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias);
 
         assertEquals(1, unwrappedKeys.size());
         assertTrue(unwrappedKeys.containsKey(alias));
@@ -93,17 +109,41 @@
     @Test
     public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception {
         String alias = "karlin";
+        AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
         SecretKey appKey = generateKey();
-        WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), appKey);
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+                new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
         HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
         keysByAlias.put(alias, wrappedKey);
 
         Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
-                generateAndroidKeyStoreKey(), keysByAlias);
+                new PlatformDecryptionKey(GENERATION_ID, generateAndroidKeyStoreKey()),
+                keysByAlias);
 
         assertEquals(0, unwrappedKeys.size());
     }
 
+    @Test
+    public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
+        AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+                new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey());
+        HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
+        keysByAlias.put("benji", wrappedKey);
+
+        try {
+            WrappedKey.unwrapKeys(
+                    new PlatformDecryptionKey(/*generationId=*/ 2, platformKey),
+                    keysByAlias);
+            fail("Should have thrown.");
+        } catch (BadPlatformKeyException e) {
+            assertEquals(
+                    "WrappedKey with alias 'benji' was wrapped with platform key 1,"
+                            + " not platform key 2",
+                    e.getMessage());
+        }
+    }
+
     private SecretKey generateKey() throws Exception {
         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
         keyGenerator.init(/*keySize=*/ 256);
@@ -122,4 +162,12 @@
                 .build());
         return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
     }
+
+    private PlatformDecryptionKey generatePlatformDecryptionKey() throws Exception {
+        return generatePlatformDecryptionKey(GENERATION_ID);
+    }
+
+    private PlatformDecryptionKey generatePlatformDecryptionKey(int generationId) throws Exception {
+        return new PlatformDecryptionKey(generationId, generateAndroidKeyStoreKey());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
new file mode 100644
index 0000000..9cde074
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -0,0 +1,423 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.WrappedKey;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreDbTest {
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void insertKey_replacesOldKey() {
+        int userId = 12;
+        int uid = 10009;
+        String alias = "test";
+        WrappedKey oldWrappedKey = new WrappedKey(
+                getUtf8Bytes("nonce1"),
+                getUtf8Bytes("keymaterial1"),
+                /*platformKeyGenerationId=*/ 1);
+        mRecoverableKeyStoreDb.insertKey(
+                userId, uid, alias, oldWrappedKey);
+        byte[] nonce = getUtf8Bytes("nonce2");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial2");
+        WrappedKey newWrappedKey = new WrappedKey(
+                nonce, keyMaterial, /*platformKeyGenerationId=*/2);
+
+        mRecoverableKeyStoreDb.insertKey(
+                userId, uid, alias, newWrappedKey);
+
+        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+        assertArrayEquals(nonce, retrievedKey.getNonce());
+        assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+        assertEquals(2, retrievedKey.getPlatformKeyGenerationId());
+    }
+
+    @Test
+    public void insertKey_allowsTwoUidsToHaveSameAlias() {
+        int userId = 6;
+        String alias = "pcoulton";
+        WrappedKey key1 = new WrappedKey(
+                getUtf8Bytes("nonce1"),
+                getUtf8Bytes("key1"),
+                /*platformKeyGenerationId=*/ 1);
+        WrappedKey key2 = new WrappedKey(
+                getUtf8Bytes("nonce2"),
+                getUtf8Bytes("key2"),
+                /*platformKeyGenerationId=*/ 1);
+
+        mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 1, alias, key1);
+        mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 2, alias, key2);
+
+        assertArrayEquals(
+                getUtf8Bytes("nonce1"),
+                mRecoverableKeyStoreDb.getKey(1, alias).getNonce());
+        assertArrayEquals(
+                getUtf8Bytes("nonce2"),
+                mRecoverableKeyStoreDb.getKey(2, alias).getNonce());
+    }
+
+    @Test
+    public void getKey_returnsNullIfNoKey() {
+        WrappedKey key = mRecoverableKeyStoreDb.getKey(
+                /*userId=*/ 1, /*alias=*/ "hello");
+
+        assertNull(key);
+    }
+
+    @Test
+    public void getKey_returnsInsertedKey() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        int status = 120;
+        String alias = "test";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, 120);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+
+        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+
+        assertArrayEquals(nonce, retrievedKey.getNonce());
+        assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+        assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId());
+        assertEquals(status,retrievedKey.getRecoveryStatus());
+    }
+
+    @Test
+    public void getAllKeys_getsKeysWithUserIdAndGenerationId() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        String alias = "test";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+
+        Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
+
+        assertEquals(1, keys.size());
+        assertTrue(keys.containsKey(alias));
+        WrappedKey retrievedKey = keys.get(alias);
+        assertArrayEquals(nonce, retrievedKey.getNonce());
+        assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+        assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId());
+    }
+
+    @Test
+    public void getAllKeys_doesNotReturnKeysWithBadGenerationId() {
+        int userId = 12;
+        int uid = 6000;
+        WrappedKey wrappedKey = new WrappedKey(
+                getUtf8Bytes("nonce"),
+                getUtf8Bytes("keymaterial"),
+                /*platformKeyGenerationId=*/ 5);
+        mRecoverableKeyStoreDb.insertKey(
+                userId, uid, /*alias=*/ "test", wrappedKey);
+
+        Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
+                userId, /*generationId=*/ 7);
+
+        assertTrue(keys.isEmpty());
+    }
+
+    @Test
+    public void getAllKeys_doesNotReturnKeysWithBadUserId() {
+        int generationId = 12;
+        int uid = 10009;
+        WrappedKey wrappedKey = new WrappedKey(
+                getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId);
+        mRecoverableKeyStoreDb.insertKey(
+                /*userId=*/ 1, uid, /*alias=*/ "test", wrappedKey);
+
+        Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
+                /*userId=*/ 2, generationId);
+
+        assertTrue(keys.isEmpty());
+    }
+
+    @Test
+    public void getPlatformKeyGenerationId_returnsGenerationId() {
+        int userId = 42;
+        int generationId = 24;
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+
+        assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+    }
+
+    @Test
+    public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() {
+        assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42));
+    }
+
+    @Test
+    public void setPlatformKeyGenerationId_replacesOldEntry() {
+        int userId = 42;
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2);
+
+        assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+    }
+
+    @Test
+    public void setRecoveryStatus_withSingleKey() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        int status = 120;
+        int status2 = 121;
+        String alias = "test";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+
+        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+        assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status);
+
+        mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
+
+        retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+        assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status2);
+    }
+
+    @Test
+    public void getStatusForAllKeys_with3Keys() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        int status = 120;
+        int status2 = 121;
+        String alias = "test";
+        String alias2 = "test2";
+        String alias3 = "test3";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
+        WrappedKey wrappedKey2 = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias3, wrappedKey);
+        WrappedKey wrappedKeyWithDefaultStatus = new WrappedKey(nonce, keyMaterial, generationId);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKeyWithDefaultStatus);
+
+        Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+        assertThat(statuses).hasSize(3);
+        assertThat(statuses).containsEntry(alias,
+                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        assertThat(statuses).containsEntry(alias2, status);
+        assertThat(statuses).containsEntry(alias3, status);
+
+        int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
+        assertThat(updates).isEqualTo(1);
+        updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias3, status2);
+        assertThat(updates).isEqualTo(1);
+        statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+
+        assertThat(statuses).hasSize(3);
+        assertThat(statuses).containsEntry(alias, status2); // updated from default
+        assertThat(statuses).containsEntry(alias2, status);
+        assertThat(statuses).containsEntry(alias3, status2); // updated
+    }
+
+    @Test
+    public void setRecoveryStatus_withEmptyDatabase() throws Exception{
+        int uid = 1009;
+        String alias = "test";
+        int status = 120;
+        int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status);
+        assertThat(updates).isEqualTo(0); // database was empty
+    }
+
+
+    @Test
+    public void getStatusForAllKeys_withEmptyDatabase() {
+        int uid = 1009;
+        Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+        assertThat(statuses).hasSize(0);
+    }
+
+    @Test
+    public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey1 = genRandomPublicKey();
+        PublicKey pubkey2 = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey2);
+    }
+
+    @Test
+    public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+
+        long serverParams = 123456L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+    }
+
+    @Test
+    public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void setServerParameters_replaceOldValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long serverParams1 = 111L;
+        long serverParams2 = 222L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+                serverParams2);
+    }
+
+    @Test
+    public void getServerParameters_returnsNullIfNoValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+    }
+
+    @Test
+    public void getServerParameters_returnsInsertedValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long serverParams = 123456L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_allowsAUserToHaveTwoUids() throws Exception {
+        int userId = 12;
+        int uid1 = 10009;
+        int uid2 = 20009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, pubkey);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid1)).isEqualTo(
+                pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid2)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_allowsTwoUsersToHaveTheSameUid() throws Exception {
+        int userId1 = 12;
+        int userId2 = 23;
+        int uid = 10009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
+                pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_updatesColumnsSeparately() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey1 = genRandomPublicKey();
+        PublicKey pubkey2 = genRandomPublicKey();
+        long serverParams = 123456L;
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+    }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static PublicKey genRandomPublicKey() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
+        keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
+        return keyPairGenerator.generateKeyPair().getPublic();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
new file mode 100644
index 0000000..6f93fe4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySessionStorageTest {
+
+    private static final String TEST_SESSION_ID = "peter";
+    private static final int TEST_USER_ID = 696;
+    private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf");
+    private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333");
+    private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault params vault params");
+
+    @Test
+    public void size_isZeroForEmpty() {
+        assertEquals(0, new RecoverySessionStorage().size());
+    }
+
+    @Test
+    public void size_incrementsAfterAdd() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
+
+        assertEquals(1, storage.size());
+    }
+
+    @Test
+    public void size_decrementsAfterRemove() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
+        storage.remove(TEST_USER_ID);
+
+        assertEquals(0, storage.size());
+    }
+
+    @Test
+    public void remove_overwritesLskfHashMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.remove(TEST_USER_ID);
+
+        assertZeroedOut(entry.getLskfHash());
+    }
+
+    @Test
+    public void remove_overwritesKeyClaimantMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.remove(TEST_USER_ID);
+
+        assertZeroedOut(entry.getKeyClaimant());
+    }
+
+    @Test
+    public void destroy_overwritesLskfHashMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.destroy();
+
+        assertZeroedOut(entry.getLskfHash());
+    }
+
+    @Test
+    public void destroy_overwritesKeyClaimantMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.destroy();
+
+        assertZeroedOut(entry.getKeyClaimant());
+    }
+
+    private static void assertZeroedOut(byte[] bytes) {
+        for (byte b : bytes) {
+            if (b != (byte) 0) {
+                fail("Bytes were not all zeroed out.");
+            }
+        }
+    }
+
+    private static byte[] lskfHashFixture() {
+        return Arrays.copyOf(TEST_LSKF_HASH, TEST_LSKF_HASH.length);
+    }
+
+    private static byte[] keyClaimantFixture() {
+        return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length);
+    }
+
+    private static byte[] vaultParamsFixture() {
+        return Arrays.copyOf(TEST_VAULT_PARAMS, TEST_VAULT_PARAMS.length);
+    }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index 7f48b8e..8581079 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.frameworks.servicestests.R;
 import com.android.servicestests.aidl.ICmdReceiverService;
@@ -42,6 +41,7 @@
 import android.util.Log;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,7 +81,7 @@
 
     private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec
 
-    private static final long SCREEN_ON_DELAY_MS = 500; // 0.5 sec
+    private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec
 
     private static final long BIND_SERVICE_TIMEOUT_SEC = 4;
 
@@ -239,7 +239,6 @@
             try {
                 Log.d(TAG, testName + " Start #" + i);
                 turnScreenOn();
-                SystemClock.sleep(SCREEN_ON_DELAY_MS);
                 startActivityAndCheckNetworkAccess();
             } finally {
                 finishActivity();
@@ -345,16 +344,22 @@
     private void turnScreenOn() throws Exception {
         executeCommand("input keyevent KEYCODE_WAKEUP");
         executeCommand("wm dismiss-keyguard");
+        // Wait for screen-on state to propagate through the system.
+        SystemClock.sleep(SCREEN_ON_DELAY_MS);
     }
 
     private String executeCommand(String cmd) throws IOException {
-        final String result = mUiDevice.executeShellCommand(cmd).trim();
+        final String result = executeSilentCommand(cmd);
         Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
         return result;
     }
 
+    private static String executeSilentCommand(String cmd) throws IOException {
+        return mUiDevice.executeShellCommand(cmd).trim();
+    }
+
     private void assertDelayedCommandResult(String cmd, String expectedResult,
-            int maxTries, int napTimeMs) throws IOException {
+            int maxTries, int napTimeMs) throws Exception {
         String result = "";
         for (int i = 1; i <= maxTries; ++i) {
             result = executeCommand(cmd);
@@ -394,6 +399,24 @@
         }
     }
 
+    private static void fail(String msg) throws Exception {
+        dumpOnFailure();
+        Assert.fail(msg);
+    }
+
+    private static void dumpOnFailure() throws Exception {
+        dump("network_management");
+        dump("netpolicy");
+        dump("usagestats");
+    }
+
+    private static void dump(String service) throws Exception {
+        Log.d(TAG, ">>> Begin dump " + service);
+        Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
+                TAG, executeSilentCommand("dumpsys " + service), null);
+        Log.d(TAG, "<<< End dump " + service);
+    }
+
     private void finishActivity() throws Exception {
         mCmdReceiverService.finishActivity();
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 2257960f..56d4b7e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -120,9 +120,6 @@
  */
 @SmallTest
 public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
-
-    private static final boolean SKIP_FOR_BUG_67325252 = true;
-
     /**
      * Test for the first launch path, no settings file available.
      */
@@ -724,10 +721,6 @@
     }
 
     public void testCleanupDanglingBitmaps() throws Exception {
-        if (SKIP_FOR_BUG_67325252) {
-            return;
-        }
-
         assertBitmapDirectories(USER_0, EMPTY_STRINGS);
         assertBitmapDirectories(USER_10, EMPTY_STRINGS);
 
@@ -786,6 +779,7 @@
 
         dumpsysOnLogcat();
 
+        mService.waitForBitmapSavesForTest();
         // Check files and directories.
         // Package 3 has no bitmaps, so we don't create a directory.
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -841,6 +835,7 @@
         makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
         makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
 
+        mService.waitForBitmapSavesForTest();
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
                 "a.b.c", "d.e.f");
 
@@ -855,6 +850,7 @@
         // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
         // directory.
 
+        mService.waitForBitmapSavesForTest();
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
         assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
 
@@ -1133,13 +1129,13 @@
                             .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
                             .build()
             )));
-
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconResource());
                         assertEquals(R.drawable.black_32x32, si.getIconResourceId());
                     });
-
+            mService.waitForBitmapSavesForTest();
             // Set bitmap icon
             assertTrue(mManager.updateShortcuts(list(
                     new ShortcutInfo.Builder(mClientContext, "s1")
@@ -1147,7 +1143,7 @@
                                     getTestContext().getResources(), R.drawable.black_64x64)))
                             .build()
             )));
-
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconFile());
@@ -1167,7 +1163,7 @@
                                     getTestContext().getResources(), R.drawable.black_64x64)))
                             .build()
             )));
-
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconFile());
@@ -1179,7 +1175,7 @@
                             .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
                             .build()
             )));
-
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconResource());
diff --git a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
index 880b77e..ff55a2b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
@@ -205,8 +205,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PRIMARY_USER)));
 
         verify(mContext, never())
@@ -217,33 +215,6 @@
     }
 
     @Test
-    public void startActivityAsUser_profile_successWithOption() throws Exception {
-        Bundle options = Bundle.forPair("test_key", "test_value");
-
-        mCrossProfileAppsServiceImpl.startActivityAsUser(
-                PACKAGE_ONE,
-                ACTIVITY_COMPONENT,
-                null,
-                options,
-                UserHandle.of(PROFILE_OF_PRIMARY_USER));
-
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-
-        verify(mContext)
-                .startActivityAsUser(
-                        intentCaptor.capture(),
-                        bundleCaptor.capture(),
-                        eq(UserHandle.of(PROFILE_OF_PRIMARY_USER)));
-
-        Intent intent = intentCaptor.getValue();
-        assertEquals(ACTIVITY_COMPONENT, intent.getComponent());
-
-        Bundle bundle = bundleCaptor.getValue();
-        assertEquals("test_value", bundle.getString("test_key"));
-    }
-
-    @Test
     public void startActivityAsUser_profile_notInstalled() throws Exception {
         mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
 
@@ -253,8 +224,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -272,8 +241,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_TWO,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -293,8 +260,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -312,8 +277,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 new ComponentName(PACKAGE_TWO, "test"),
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -331,8 +294,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(SECONDARY_USER)));
 
         verify(mContext, never())
@@ -349,8 +310,6 @@
         mCrossProfileAppsServiceImpl.startActivityAsUser(
                 PACKAGE_ONE,
                 ACTIVITY_COMPONENT,
-                null,
-                null,
                 UserHandle.of(PRIMARY_USER));
 
         verify(mContext)
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index d9ab5c8..759894b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
 
         mToken.setFillsParent(true);
-        mToken.hidden = true;
+        mToken.setHidden(true);
         mToken.sendingToBottom = true;
         // Can not specify orientation if app isn't visible even though it fills parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 53a5899..2bfe274 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -110,7 +110,7 @@
         }
 
         public void notifyTransitionStarting(int transit) {
-            mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
+            mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
         }
 
         public void notifyTransitionFinished() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index c309611..17fe642 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -181,7 +181,7 @@
         final Animation a = new TranslateAnimation(-10, 10, 0, 0);
         a.initialize(0, 0, 0, 0);
         a.setDuration(50);
-        return new WindowAnimationSpec(a, new Point(0, 0));
+        return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6f739ca..96ff461 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -61,12 +62,14 @@
 
     private SurfaceSession mSession = new SurfaceSession();
     private MyAnimatable mAnimatable;
+    private MyAnimatable mAnimatable2;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
         mAnimatable = new MyAnimatable();
+        mAnimatable2 = new MyAnimatable();
     }
 
     @Test
@@ -74,15 +77,12 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         callbackCaptor.getValue().onAnimationFinished(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
@@ -99,8 +99,7 @@
 
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         // First animation was finished, but this shouldn't cancel the second animation
@@ -110,16 +109,16 @@
         // Second animation was finished
         verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
         callbackCaptor.getValue().onAnimationFinished(mSpec2);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
     }
 
     @Test
     public void testCancelAnimation() throws Exception {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         verify(mSpec).onAnimationCancelled(any());
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
@@ -130,7 +129,7 @@
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         verifyZeroInteractions(mSpec);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
         verify(mSpec).startAnimation(any(), any(), any());
     }
@@ -141,11 +140,41 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
         verifyZeroInteractions(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
     }
 
+    @Test
+    public void testTransferAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        final SurfaceControl leash = mAnimatable.mLeash;
+
+        mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
+        assertNotAnimating(mAnimatable);
+        assertAnimating(mAnimatable2);
+        assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
+        assertFalse(mAnimatable.mPendingDestroySurfaces.contains(leash));
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertNotAnimating(mAnimatable2);
+        assertTrue(mAnimatable2.mFinishedCallbackCalled);
+        assertTrue(mAnimatable2.mPendingDestroySurfaces.contains(leash));
+    }
+
+    private void assertAnimating(MyAnimatable animatable) {
+        assertTrue(animatable.mSurfaceAnimator.isAnimating());
+        assertNotNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
+    private void assertNotAnimating(MyAnimatable animatable) {
+        assertFalse(animatable.mSurfaceAnimator.isAnimating());
+        assertNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
     private class MyAnimatable implements Animatable {
 
         final SurfaceControl mParent;
@@ -223,8 +252,6 @@
             return 1;
         }
 
-        private final Runnable mFinishedCallback = () -> {
-            mFinishedCallbackCalled = true;
-        };
+        private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 307deb4..196b4a9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -564,6 +564,86 @@
         assertEquals(1, child2223.compareTo(child21));
     }
 
+    @Test
+    public void testPrefixOrderIndex() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+
+        final TestWindowContainer child221 = child22.addChildWindow();
+        final TestWindowContainer child222 = child22.addChildWindow();
+        final TestWindowContainer child223 = child22.addChildWindow();
+
+        final TestWindowContainer child23 = child2.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+        assertEquals(5, child21.getPrefixOrderIndex());
+        assertEquals(6, child22.getPrefixOrderIndex());
+        assertEquals(7, child221.getPrefixOrderIndex());
+        assertEquals(8, child222.getPrefixOrderIndex());
+        assertEquals(9, child223.getPrefixOrderIndex());
+        assertEquals(10, child23.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_addEntireSubtree() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+        final TestWindowContainer subtree = builder.build();
+        final TestWindowContainer subtree2 = builder.build();
+
+        final TestWindowContainer child1 = subtree.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child2 = subtree2.addChildWindow();
+        final TestWindowContainer child3 = subtree2.addChildWindow();
+        subtree.addChild(subtree2, 1);
+        root.addChild(subtree, 0);
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, subtree.getPrefixOrderIndex());
+        assertEquals(2, child1.getPrefixOrderIndex());
+        assertEquals(3, child11.getPrefixOrderIndex());
+        assertEquals(4, subtree2.getPrefixOrderIndex());
+        assertEquals(5, child2.getPrefixOrderIndex());
+        assertEquals(6, child3.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_remove() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+
+        root.removeChild(child1);
+
+        assertEquals(1, child2.getPrefixOrderIndex());
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 337fd50..0959df2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -451,9 +451,9 @@
     private DisplayCutout createDisplayCutoutFromRect(int left, int top, int right, int bottom) {
         return DisplayCutout.fromBoundingPolygon(Arrays.asList(
                 new Point(left, top),
-                new Point (left, bottom),
-                new Point (right, bottom),
-                new Point (left, bottom)
+                new Point(left, bottom),
+                new Point(right, bottom),
+                new Point(right, top)
         ));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index 5f58744..012fc23 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -160,7 +160,6 @@
 
     /* Used so we can gain access to some protected members of the {@link WindowToken} class */
     public static class TestWindowToken extends WindowToken {
-        int adj = 0;
 
         TestWindowToken(int type, DisplayContent dc) {
             this(type, dc, false /* persistOnEmpty */);
@@ -178,11 +177,6 @@
         boolean hasWindow(WindowState w) {
             return mChildren.contains(w);
         }
-
-        @Override
-        int getAnimLayerAdjustment() {
-            return adj;
-        }
     }
 
     /* Used so we can gain access to some protected members of the {@link Task} class */
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
index b26cc7fd..f8d1d03 100644
--- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
+++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
@@ -77,6 +77,7 @@
         Log.i(TAG, "onStop called");
         if (finishCommandReceiver != null) {
             unregisterReceiver(finishCommandReceiver);
+            finishCommandReceiver = null;
         }
         super.onStop();
     }
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index 620922ac..a1f1810 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,15 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.UsageStatsManager.REASON_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
+import static android.app.usage.UsageStatsManager.REASON_USAGE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+
 import android.app.usage.UsageStatsManager;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -197,14 +206,14 @@
                 + (elapsedRealtime - mElapsedSnapshot);
         appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
         appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
-        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             if (DEBUG) {
                 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                         + ", reason=" + appUsageHistory.bucketingReason);
             }
         }
-        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+        appUsageHistory.bucketingReason = REASON_USAGE;
 
         return appUsageHistory.currentBucket;
     }
@@ -213,15 +222,15 @@
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
-        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
             if (DEBUG) {
                 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                         + ", reason=" + appUsageHistory.bucketingReason);
             }
         }
         // TODO: Should this be a different reason for partial usage?
-        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+        appUsageHistory.bucketingReason = REASON_USAGE;
 
         return appUsageHistory.currentBucket;
     }
@@ -279,8 +288,8 @@
             appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
             appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
             appUsageHistory.lastPredictedTime = getElapsedTime(0);
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
+            appUsageHistory.bucketingReason = REASON_DEFAULT;
             appUsageHistory.lastInformedBucket = -1;
             userHistory.put(packageName, appUsageHistory);
         }
@@ -298,7 +307,7 @@
         if (appUsageHistory == null) {
             return false; // Default to not idle
         } else {
-            return appUsageHistory.currentBucket >= UsageStatsManager.STANDBY_BUCKET_RARE;
+            return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE;
             // Whether or not it's passed will now be externally calculated and the
             // bucket will be pushed to the history using setAppStandbyBucket()
             //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
@@ -320,7 +329,7 @@
                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
         appUsageHistory.currentBucket = bucket;
         appUsageHistory.bucketingReason = reason;
-        if (reason.startsWith(UsageStatsManager.REASON_PREDICTED)) {
+        if (reason.startsWith(REASON_PREDICTED)) {
             appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime);
         }
         if (DEBUG) {
@@ -336,12 +345,14 @@
         return appUsageHistory.currentBucket;
     }
 
-    public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) {
+    public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime,
+            boolean appIdleEnabled) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         int size = userHistory.size();
         HashMap<String, Integer> buckets = new HashMap<>(size);
         for (int i = 0; i < size; i++) {
-            buckets.put(userHistory.keyAt(i), userHistory.valueAt(i).currentBucket);
+            buckets.put(userHistory.keyAt(i),
+                    appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE);
         }
         return buckets;
     }
@@ -363,12 +374,12 @@
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
         if (idle) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_RARE;
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_FORCED;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+            appUsageHistory.bucketingReason = REASON_FORCED;
         } else {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             // This is to pretend that the app was just used, don't freeze the state anymore.
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+            appUsageHistory.bucketingReason = REASON_USAGE;
         }
         return appUsageHistory.currentBucket;
     }
@@ -471,12 +482,12 @@
                         String currentBucketString = parser.getAttributeValue(null,
                                 ATTR_CURRENT_BUCKET);
                         appUsageHistory.currentBucket = currentBucketString == null
-                                ? UsageStatsManager.STANDBY_BUCKET_ACTIVE
+                                ? STANDBY_BUCKET_ACTIVE
                                 : Integer.parseInt(currentBucketString);
                         appUsageHistory.bucketingReason =
                                 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
                         if (appUsageHistory.bucketingReason == null) {
-                            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
+                            appUsageHistory.bucketingReason = REASON_DEFAULT;
                         }
                         appUsageHistory.lastInformedBucket = -1;
                         userHistory.put(packageName, appUsageHistory);
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index cc0259d..9b588fa 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -162,12 +162,14 @@
     long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
     long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
 
-    boolean mAppIdleEnabled;
+    volatile boolean mAppIdleEnabled;
     boolean mAppIdleTempParoled;
     boolean mCharging;
     private long mLastAppIdleParoledTime;
     private boolean mSystemServicesReady = false;
 
+    private final DeviceStateReceiver mDeviceStateReceiver;
+
     private volatile boolean mPendingOneTimeCheckIdleStates;
 
     private final AppStandbyHandler mHandler;
@@ -178,7 +180,7 @@
     private AppWidgetManager mAppWidgetManager;
     private PowerManager mPowerManager;
     private PackageManager mPackageManager;
-    private Injector mInjector;
+    Injector mInjector;
 
 
     AppStandbyController(Context context, Looper looper) {
@@ -190,14 +192,13 @@
         mContext = mInjector.getContext();
         mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
-        mAppIdleEnabled = mInjector.isAppIdleEnabled();
+        mDeviceStateReceiver = new DeviceStateReceiver();
 
-        if (mAppIdleEnabled) {
-            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
-            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
-        }
+        IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+        deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(mDeviceStateReceiver, deviceStates);
+
         synchronized (mAppIdleLock) {
             mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
                     mInjector.elapsedRealtime());
@@ -213,9 +214,14 @@
                 null, mHandler);
     }
 
+    void setAppIdleEnabled(boolean enabled) {
+        mAppIdleEnabled = enabled;
+    }
+
     public void onBootPhase(int phase) {
         mInjector.onBootPhase(phase);
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            setAppIdleEnabled(mInjector.isAppIdleEnabled());
             // Observe changes to the threshold
             SettingsObserver settingsObserver = new SettingsObserver(mHandler);
             settingsObserver.registerObserver();
@@ -240,6 +246,8 @@
     }
 
     void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+        if (!mAppIdleEnabled) return;
+
         // Get sync adapters for the authority
         String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
                 authority, userId);
@@ -295,6 +303,7 @@
     }
 
     boolean isParoledOrCharging() {
+        if (!mAppIdleEnabled) return true;
         synchronized (mAppIdleLock) {
             return mAppIdleTempParoled || mCharging;
         }
@@ -520,6 +529,7 @@
     }
 
     void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+        if (!mAppIdleEnabled) return;
         synchronized (mAppIdleLock) {
             // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
             // about apps that are on some kind of whitelist anyway.
@@ -559,6 +569,8 @@
      * required.
      */
     void forceIdleState(String packageName, int userId, boolean idle) {
+        if (!mAppIdleEnabled) return;
+
         final int appId = getAppId(packageName);
         if (appId < 0) return;
         final long elapsedRealtime = mInjector.elapsedRealtime();
@@ -763,7 +775,7 @@
     }
 
     void setAppIdleAsync(String packageName, boolean idle, int userId) {
-        if (packageName == null) return;
+        if (packageName == null || !mAppIdleEnabled) return;
 
         mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
                 .sendToTarget();
@@ -771,8 +783,8 @@
 
     @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
             long elapsedRealtime, boolean shouldObfuscateInstantApps) {
-        if (shouldObfuscateInstantApps &&
-                mInjector.isPackageEphemeral(userId, packageName)) {
+        if (!mAppIdleEnabled || (shouldObfuscateInstantApps
+                && mInjector.isPackageEphemeral(userId, packageName))) {
             return STANDBY_BUCKET_ACTIVE;
         }
 
@@ -783,7 +795,7 @@
 
     public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) {
         synchronized (mAppIdleLock) {
-            return mAppIdleHistory.getAppStandbyBuckets(userId, elapsedRealtime);
+            return mAppIdleHistory.getAppStandbyBuckets(userId, elapsedRealtime, mAppIdleEnabled);
         }
     }
 
@@ -1058,8 +1070,11 @@
         }
 
         boolean isAppIdleEnabled() {
-            return mContext.getResources().getBoolean(
+            final boolean buildFlag = mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_enableAutoPowerModes);
+            final boolean runtimeFlag = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.APP_STANDBY_ENABLED, 1) == 1;
+            return buildFlag && runtimeFlag;
         }
 
         boolean isCharging() {
@@ -1130,7 +1145,7 @@
                     break;
 
                 case MSG_CHECK_IDLE_STATES:
-                    if (checkIdleStates(msg.arg1)) {
+                    if (checkIdleStates(msg.arg1) && mAppIdleEnabled) {
                         mHandler.sendMessageDelayed(mHandler.obtainMessage(
                                 MSG_CHECK_IDLE_STATES, msg.arg1, 0),
                                 mCheckIdleIntervalMillis);
@@ -1229,6 +1244,8 @@
         void registerObserver() {
             mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.APP_IDLE_CONSTANTS), false, this);
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_STANDBY_ENABLED), false, this);
         }
 
         @Override
@@ -1238,15 +1255,27 @@
         }
 
         void updateSettings() {
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "appidle=" + Settings.Global.getString(mContext.getContentResolver(),
+                                Settings.Global.APP_STANDBY_ENABLED));
+                Slog.d(TAG, "appidleconstants=" + Settings.Global.getString(
+                        mContext.getContentResolver(),
+                        Settings.Global.APP_IDLE_CONSTANTS));
+            }
+            // Check if app_idle_enabled has changed
+            setAppIdleEnabled(mInjector.isAppIdleEnabled());
+
+            // Look at global settings for this.
+            // TODO: Maybe apply different thresholds for different users.
+            try {
+                mParser.setString(mInjector.getAppIdleSettings());
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                // fallthrough, mParser is empty and all defaults will be returned.
+            }
+
             synchronized (mAppIdleLock) {
-                // Look at global settings for this.
-                // TODO: Maybe apply different thresholds for different users.
-                try {
-                    mParser.setString(mInjector.getAppIdleSettings());
-                } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
-                    // fallthrough, mParser is empty and all defaults will be returned.
-                }
 
                 // Default: 24 hours between paroles
                 mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 07c860b..cdce448 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -495,8 +495,8 @@
                         flushToDiskLocked();
                         pw.println("Flushed stats to disk");
                         return;
-                    } else {
-                        // Anything else is a pkg to filter
+                    } else if (arg != null && !arg.startsWith("-")) {
+                        // Anything else that doesn't start with '-' is a pkg to filter
                         pkg = arg;
                         break;
                     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c0685f9..44f5551 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -67,6 +67,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -389,11 +390,13 @@
         }
 
         public void switchUser(int userHandle) {
-            synchronized (this) {
-                mCurUser = userHandle;
-                mCurUserUnlocked = false;
-                switchImplementationIfNeededLocked(false);
-            }
+            FgThread.getHandler().post(() -> {
+                synchronized (this) {
+                    mCurUser = userHandle;
+                    mCurUserUnlocked = false;
+                    switchImplementationIfNeededLocked(false);
+                }
+            });
         }
 
         void switchImplementationIfNeeded(boolean force) {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index dec7b76..6af01ae 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1466,8 +1466,9 @@
         }
         Log.d(this, "createConnection, connection: %s", connection);
         if (connection == null) {
+            Log.i(this, "createConnection, implementation returned null connection.");
             connection = Connection.createFailedConnection(
-                    new DisconnectCause(DisconnectCause.ERROR));
+                    new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
         }
 
         connection.setTelecomCallId(callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dedce17..6a47d05 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1717,6 +1717,13 @@
     public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
             "spn_display_rule_use_roaming_from_service_state_bool";
 
+    /**
+     * Determines whether any carrier has been identified and its specific config has been applied,
+     * default to false.
+     * @hide
+     */
+    public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2002,6 +2009,7 @@
         sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
         sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
     }
 
     /**
@@ -2047,6 +2055,33 @@
     }
 
     /**
+     * Determines whether a configuration {@link PersistableBundle} obtained from
+     * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier.
+     * <p>
+     * When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED}
+     * broadcast which informs it that the carrier configuration has changed, it is possible
+     * that another reload of the carrier configuration has begun since the intent was sent.
+     * In this case, the carrier configuration the app fetches (e.g. via {@link #getConfig()})
+     * may not represent the configuration for the current carrier. It should be noted that it
+     * does not necessarily mean the configuration belongs to current carrier when this function
+     * return true because it may belong to another previous identified carrier. Users should
+     * always call {@link #getConfig()} or {@link #getConfigForSubId(int)} after receiving the
+     * broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED}.
+     * </p>
+     * <p>
+     * After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always
+     * use this method to confirm whether any carrier specific configuration has been applied.
+     * </p>
+     *
+     * @param bundle the configuration bundle to be checked.
+     * @return boolean true if any carrier specific configuration bundle has been applied, false
+     * otherwise or the bundle is null.
+     */
+    public static boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
+        return bundle != null && bundle.getBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL);
+    }
+
+    /**
      * Calling this method triggers telephony services to fetch the current carrier configuration.
      * <p>
      * Normally this does not need to be called because the platform reloads config on its own.
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index a554c69..059a2d0 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -502,8 +502,10 @@
      * Asynchronous errors through the callback may include any error not specific to the
      * streaming use-case.
      * @param request The request that specifies what should be downloaded.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void download(@NonNull DownloadRequest request) {
+    public int download(@NonNull DownloadRequest request) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -519,13 +521,16 @@
             setTempFileRootDirectory(tempRootDirectory);
         }
 
-        writeDownloadRequestToken(request);
         try {
-            downloadService.download(request);
+            int result = downloadService.download(request);
+            if (result == MbmsErrors.SUCCESS) {
+                writeDownloadRequestToken(request);
+            }
+            return result;
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
     }
 
@@ -565,8 +570,10 @@
      * @param callback The callback that should be called when the middleware has information to
      *                 share on the download.
      * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void registerStateCallback(@NonNull DownloadRequest request,
+    public int registerStateCallback(@NonNull DownloadRequest request,
             @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
@@ -583,16 +590,15 @@
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
                 }
-                sendErrorToApp(result, null);
-                return;
+                return result;
             }
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
-            return;
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
         mInternalDownloadCallbacks.put(callback, internalCallback);
+        return MbmsErrors.SUCCESS;
     }
 
     /**
@@ -606,8 +612,10 @@
      *
      * @param request The {@link DownloadRequest} provided during registration
      * @param callback The callback provided during registration.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void unregisterStateCallback(@NonNull DownloadRequest request,
+    public int unregisterStateCallback(@NonNull DownloadRequest request,
             @NonNull DownloadStateCallback callback) {
         try {
             IMbmsDownloadService downloadService = mService.get();
@@ -617,6 +625,9 @@
 
             InternalDownloadStateCallback internalCallback =
                     mInternalDownloadCallbacks.get(callback);
+            if (internalCallback == null) {
+                throw new IllegalArgumentException("Provided callback was never registered");
+            }
 
             try {
                 int result = downloadService.unregisterStateCallback(request, internalCallback);
@@ -624,12 +635,12 @@
                     if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                         throw new IllegalArgumentException("Unknown download request.");
                     }
-                    sendErrorToApp(result, null);
+                    return result;
                 }
             } catch (RemoteException e) {
                 mService.set(null);
                 sIsInitialized.set(false);
-                sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+                return MbmsErrors.ERROR_MIDDLEWARE_LOST;
             }
         } finally {
             InternalDownloadStateCallback internalCallback =
@@ -638,6 +649,7 @@
                 internalCallback.stop();
             }
         }
+        return MbmsErrors.SUCCESS;
     }
 
     /**
@@ -647,8 +659,10 @@
      * this method will throw an {@link IllegalArgumentException}.
      *
      * @param downloadRequest The download request that you wish to cancel.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+    public int cancelDownload(@NonNull DownloadRequest downloadRequest) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -660,16 +674,15 @@
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
                 }
-                sendErrorToApp(result, null);
-                return;
+            } else {
+                deleteDownloadRequestToken(downloadRequest);
             }
+            return result;
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
-            return;
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
-        deleteDownloadRequestToken(downloadRequest);
     }
 
     /**
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index f15fde8..a277212 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -19,50 +19,92 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.annotation.IntDef;
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * Allows applications to request the system to perform a network scan.
- *
- * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will
- * receive a NetworkScan which contains the callback method to stop the scan requested.
- * @hide
+ * The caller of
+ * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)}
+ * will receive an instance of {@link NetworkScan}, which contains a callback method
+ * {@link #stop()} for stopping the in-progress scan.
  */
 public class NetworkScan {
 
-    public static final String TAG = "NetworkScan";
+    private static final String TAG = "NetworkScan";
 
     // Below errors are mapped from RadioError which is returned from RIL. We will consolidate
     // RadioErrors during the mapping if those RadioErrors mean no difference to the users.
+
+    /**
+     * Defines acceptable values of scan error code.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_MODEM_ERROR, ERROR_INVALID_SCAN, ERROR_MODEM_UNAVAILABLE, ERROR_UNSUPPORTED,
+            ERROR_RADIO_INTERFACE_ERROR, ERROR_INVALID_SCANID, ERROR_INTERRUPTED})
+    public @interface ScanErrorCode {}
+
+    /**
+     * The RIL has successfully performed the network scan.
+     */
     public static final int SUCCESS = 0;                    // RadioError:NONE
+
+    /**
+     * The scan has failed due to some modem errors.
+     */
     public static final int ERROR_MODEM_ERROR = 1;          // RadioError:RADIO_NOT_AVAILABLE
                                                             // RadioError:NO_MEMORY
                                                             // RadioError:INTERNAL_ERR
                                                             // RadioError:MODEM_ERR
                                                             // RadioError:OPERATION_NOT_ALLOWED
+
+    /**
+     * The parameters of the scan is invalid.
+     */
     public static final int ERROR_INVALID_SCAN = 2;         // RadioError:INVALID_ARGUMENTS
-    public static final int ERROR_MODEM_BUSY = 3;           // RadioError:DEVICE_IN_USE
+
+    /**
+     * The modem can not perform the scan because it is doing something else.
+     */
+    public static final int ERROR_MODEM_UNAVAILABLE = 3;    // RadioError:DEVICE_IN_USE
+
+    /**
+     * The modem does not support the request scan.
+     */
     public static final int ERROR_UNSUPPORTED = 4;          // RadioError:REQUEST_NOT_SUPPORTED
 
+
     // Below errors are generated at the Telephony.
-    public static final int ERROR_RIL_ERROR = 10000;        // Nothing or only exception is
-                                                            // returned from RIL.
-    public static final int ERROR_INVALID_SCANID = 10001;   // The scanId is invalid. The user is
-                                                            // either trying to stop a scan which
-                                                            // does not exist or started by others.
-    public static final int ERROR_INTERRUPTED = 10002;      // Scan was interrupted by another scan
-                                                            // with higher priority.
+
+    /**
+     * The RIL returns nothing or exceptions.
+     */
+    public static final int ERROR_RADIO_INTERFACE_ERROR = 10000;
+
+    /**
+     * The scan ID is invalid. The user is either trying to stop a scan which does not exist
+     * or started by others.
+     */
+    public static final int ERROR_INVALID_SCANID = 10001;
+
+    /**
+     * The scan has been interrupted by another scan with higher priority.
+     */
+    public static final int ERROR_INTERRUPTED = 10002;
+
     private final int mScanId;
     private final int mSubId;
 
     /**
      * Stops the network scan
      *
-     * This is the callback method to stop an ongoing scan. When user requests a new scan,
-     * a NetworkScan object will be returned, and the user can stop the scan by calling this
-     * method.
+     * Use this method to stop an ongoing scan. When user requests a new scan, a {@link NetworkScan}
+     * object will be returned, and the user can stop the scan by calling this method.
      */
     public void stop() throws RemoteException {
         try {
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 9674c93..ea503c3 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -16,11 +16,14 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Defines a request to peform a network scan.
@@ -28,7 +31,6 @@
  * This class defines whether the network scan will be performed only once or periodically until
  * cancelled, when the scan is performed periodically, the time interval is not controlled by the
  * user but defined by the modem vendor.
- * @hide
  */
 public final class NetworkScanRequest implements Parcelable {
 
@@ -54,6 +56,14 @@
     /** @hide */
     public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        SCAN_TYPE_ONE_SHOT,
+        SCAN_TYPE_PERIODIC,
+    })
+    public @interface ScanType {}
+
     /** Performs the scan only once */
     public static final int SCAN_TYPE_ONE_SHOT = 0;
     /**
@@ -65,21 +75,21 @@
     public static final int SCAN_TYPE_PERIODIC = 1;
 
     /** Defines the type of the scan. */
-    public int scanType;
+    private int mScanType;
 
     /**
      * Search periodicity (in seconds).
      * Expected range for the input is [5s - 300s]
-     * This value must be less than or equal to maxSearchTime
+     * This value must be less than or equal to mMaxSearchTime
      */
-    public int searchPeriodicity;
+    private int mSearchPeriodicity;
 
     /**
      * Maximum duration of the periodic search (in seconds).
      * Expected range for the input is [60s - 3600s]
      * If the search lasts this long, it will be terminated.
      */
-    public int maxSearchTime;
+    private int mMaxSearchTime;
 
     /**
      * Indicates whether the modem should report incremental
@@ -87,18 +97,18 @@
      * FALSE – Incremental results are not reported.
      * TRUE (default) – Incremental results are reported
      */
-    public boolean incrementalResults;
+    private boolean mIncrementalResults;
 
     /**
      * Indicates the periodicity with which the modem should
      * report incremental results to the client (in seconds).
      * Expected range for the input is [1s - 10s]
-     * This value must be less than or equal to maxSearchTime
+     * This value must be less than or equal to mMaxSearchTime
      */
-    public int incrementalResultsPeriodicity;
+    private int mIncrementalResultsPeriodicity;
 
     /** Describes the radio access technologies with bands or channels that need to be scanned. */
-    public RadioAccessSpecifier[] specifiers;
+    private RadioAccessSpecifier[] mSpecifiers;
 
     /**
      * Describes the List of PLMN ids (MCC-MNC)
@@ -107,20 +117,24 @@
      * If list not sent, search to be completed till end and all PLMNs found to be reported.
      * Max size of array is MAX_MCC_MNC_LIST_SIZE
      */
-    public ArrayList<String> mccMncs;
+    private ArrayList<String> mMccMncs;
 
     /**
-     * Creates a new NetworkScanRequest with scanType and network specifiers
+     * Creates a new NetworkScanRequest with mScanType and network mSpecifiers
      *
-     * @param scanType The type of the scan
+     * @param scanType The type of the scan, can be either one shot or periodic
      * @param specifiers the radio network with bands / channels to be scanned
-     * @param searchPeriodicity Search periodicity (in seconds)
-     * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+     * @param searchPeriodicity The modem will restart the scan every searchPeriodicity seconds if
+     *                          no network has been found, until it reaches the maxSearchTime. Only
+     *                          valid when scan type is periodic scan.
+     * @param maxSearchTime Maximum duration of the search (in seconds)
      * @param incrementalResults Indicates whether the modem should report incremental
      *                           results of the network scan to the client
      * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
-     *                                      report incremental results to the client (in seconds)
-     * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
+     *                                      report incremental results to the client (in seconds),
+     *                                      only valid when incrementalResults is true
+     * @param mccMncs Describes the list of PLMN ids (MCC-MNC), once any network in the list has
+     *                been found, the scan will be terminated by the modem.
      */
     public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
                     int searchPeriodicity,
@@ -128,19 +142,63 @@
                     boolean incrementalResults,
                     int incrementalResultsPeriodicity,
                     ArrayList<String> mccMncs) {
-        this.scanType = scanType;
-        this.specifiers = specifiers;
-        this.searchPeriodicity = searchPeriodicity;
-        this.maxSearchTime = maxSearchTime;
-        this.incrementalResults = incrementalResults;
-        this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
-        if (mccMncs != null) {
-            this.mccMncs = mccMncs;
+        this.mScanType = scanType;
+        this.mSpecifiers = specifiers.clone();
+        this.mSearchPeriodicity = searchPeriodicity;
+        this.mMaxSearchTime = maxSearchTime;
+        this.mIncrementalResults = incrementalResults;
+        this.mIncrementalResultsPeriodicity = incrementalResultsPeriodicity;
+        if (mMccMncs != null) {
+            this.mMccMncs = (ArrayList<String>) mccMncs.clone();
         } else {
-            this.mccMncs = new ArrayList<>();
+            this.mMccMncs = new ArrayList<>();
         }
     }
 
+    /** Returns the type of the scan. */
+    @ScanType
+    public int getScanType() {
+        return mScanType;
+    }
+
+    /** Returns the search periodicity in seconds. */
+    public int getSearchPeriodicity() {
+        return mSearchPeriodicity;
+    }
+
+    /** Returns maximum duration of the periodic search in seconds. */
+    public int getMaxSearchTime() {
+        return mMaxSearchTime;
+    }
+
+    /**
+     * Returns whether incremental result is enabled.
+     * FALSE – Incremental results is not enabled.
+     * TRUE – Incremental results is reported.
+     */
+    public boolean getIncrementalResults() {
+        return mIncrementalResults;
+    }
+
+    /** Returns the periodicity in seconds of incremental results. */
+    public int getIncrementalResultsPeriodicity() {
+        return mIncrementalResultsPeriodicity;
+    }
+
+    /** Returns the radio access technologies with bands or channels that need to be scanned. */
+    public RadioAccessSpecifier[] getSpecifiers() {
+        return mSpecifiers.clone();
+    }
+
+    /**
+     * Returns the List of PLMN ids (MCC-MNC) for early termination of scan.
+     * If any PLMN of this list is found, search should end at that point and
+     * results with all PLMN found till that point should be sent as response.
+     */
+    public ArrayList<String> getPlmns() {
+        return (ArrayList<String>) mMccMncs.clone();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -148,26 +206,26 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(scanType);
-        dest.writeParcelableArray(specifiers, flags);
-        dest.writeInt(searchPeriodicity);
-        dest.writeInt(maxSearchTime);
-        dest.writeBoolean(incrementalResults);
-        dest.writeInt(incrementalResultsPeriodicity);
-        dest.writeStringList(mccMncs);
+        dest.writeInt(mScanType);
+        dest.writeParcelableArray(mSpecifiers, flags);
+        dest.writeInt(mSearchPeriodicity);
+        dest.writeInt(mMaxSearchTime);
+        dest.writeBoolean(mIncrementalResults);
+        dest.writeInt(mIncrementalResultsPeriodicity);
+        dest.writeStringList(mMccMncs);
     }
 
     private NetworkScanRequest(Parcel in) {
-        scanType = in.readInt();
-        specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
+        mScanType = in.readInt();
+        mSpecifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
                 Object.class.getClassLoader(),
                 RadioAccessSpecifier.class);
-        searchPeriodicity = in.readInt();
-        maxSearchTime = in.readInt();
-        incrementalResults = in.readBoolean();
-        incrementalResultsPeriodicity = in.readInt();
-        mccMncs = new ArrayList<>();
-        in.readStringList(mccMncs);
+        mSearchPeriodicity = in.readInt();
+        mMaxSearchTime = in.readInt();
+        mIncrementalResults = in.readBoolean();
+        mIncrementalResultsPeriodicity = in.readInt();
+        mMccMncs = new ArrayList<>();
+        in.readStringList(mMccMncs);
     }
 
     @Override
@@ -184,25 +242,25 @@
             return false;
         }
 
-        return (scanType == nsr.scanType
-                && Arrays.equals(specifiers, nsr.specifiers)
-                && searchPeriodicity == nsr.searchPeriodicity
-                && maxSearchTime == nsr.maxSearchTime
-                && incrementalResults == nsr.incrementalResults
-                && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
-                && (((mccMncs != null)
-                && mccMncs.equals(nsr.mccMncs))));
+        return (mScanType == nsr.mScanType
+                && Arrays.equals(mSpecifiers, nsr.mSpecifiers)
+                && mSearchPeriodicity == nsr.mSearchPeriodicity
+                && mMaxSearchTime == nsr.mMaxSearchTime
+                && mIncrementalResults == nsr.mIncrementalResults
+                && mIncrementalResultsPeriodicity == nsr.mIncrementalResultsPeriodicity
+                && (((mMccMncs != null)
+                && mMccMncs.equals(nsr.mMccMncs))));
     }
 
     @Override
     public int hashCode () {
-        return ((scanType * 31)
-                + (Arrays.hashCode(specifiers)) * 37
-                + (searchPeriodicity * 41)
-                + (maxSearchTime * 43)
-                + ((incrementalResults == true? 1 : 0) * 47)
-                + (incrementalResultsPeriodicity * 53)
-                + (mccMncs.hashCode() * 59));
+        return ((mScanType * 31)
+                + (Arrays.hashCode(mSpecifiers)) * 37
+                + (mSearchPeriodicity * 41)
+                + (mMaxSearchTime * 43)
+                + ((mIncrementalResults == true? 1 : 0) * 47)
+                + (mIncrementalResultsPeriodicity * 53)
+                + (mMccMncs.hashCode() * 59));
     }
 
     public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index 33ce8b4..5412c61 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -25,34 +25,40 @@
  * Describes a particular radio access network to be scanned.
  *
  * The scan can be performed on either bands or channels for a specific radio access network type.
- * @hide
  */
 public final class RadioAccessSpecifier implements Parcelable {
 
     /**
      * The radio access network that needs to be scanned
      *
+     * This parameter must be provided or else the scan will be rejected.
+     *
      * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
      */
-    public int radioAccessNetwork;
+    private int mRadioAccessNetwork;
 
     /**
      * The frequency bands that need to be scanned
      *
-     * bands must be used together with radioAccessNetwork
+     * When no specific bands are specified (empty array or null), all the frequency bands
+     * supported by the modem will be scanned.
      *
      * See {@link RadioNetworkConstants} for details.
      */
-    public int[] bands;
+    private int[] mBands;
 
     /**
      * The frequency channels that need to be scanned
      *
-     * channels must be used together with radioAccessNetwork
+     * When any specific channels are provided for scan, the corresponding frequency bands that
+     * contains those channels must also be provided, or else the channels will be ignored.
      *
-     * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+     * When no specific channels are specified (empty array or null), all the frequency channels
+     * supported by the modem will be scanned.
+     *
+     * See {@link RadioNetworkConstants} for details.
      */
-    public int[] channels;
+    private int[] mChannels;
 
     /**
     * Creates a new RadioAccessSpecifier with radio network, bands and channels
@@ -65,9 +71,34 @@
     * @param channels the frequency bands to be scanned
     */
     public RadioAccessSpecifier(int ran, int[] bands, int[] channels) {
-        this.radioAccessNetwork = ran;
-        this.bands = bands;
-        this.channels = channels;
+        this.mRadioAccessNetwork = ran;
+        this.mBands = bands.clone();
+        this.mChannels = channels.clone();
+    }
+
+    /**
+     * Returns the radio access network that needs to be scanned.
+     *
+     * The returned value is define in {@link RadioNetworkConstants.RadioAccessNetworks};
+     */
+    public int getRadioAccessNetwork() {
+        return mRadioAccessNetwork;
+    }
+
+    /**
+     * Returns the frequency bands that need to be scanned.
+     *
+     * The returned value is defined in either of {@link RadioNetworkConstants.GeranBands},
+     * {@link RadioNetworkConstants.UtranBands} and {@link RadioNetworkConstants.EutranBands}, and
+     * it depends on the returned value of {@link #getRadioAccessNetwork()}.
+     */
+    public int[] getBands() {
+        return mBands.clone();
+    }
+
+    /** Returns the frequency channels that need to be scanned. */
+    public int[] getChannels() {
+        return mChannels.clone();
     }
 
     public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR =
@@ -90,15 +121,15 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(radioAccessNetwork);
-        dest.writeIntArray(bands);
-        dest.writeIntArray(channels);
+        dest.writeInt(mRadioAccessNetwork);
+        dest.writeIntArray(mBands);
+        dest.writeIntArray(mChannels);
     }
 
     private RadioAccessSpecifier(Parcel in) {
-        radioAccessNetwork = in.readInt();
-        bands = in.createIntArray();
-        channels = in.createIntArray();
+        mRadioAccessNetwork = in.readInt();
+        mBands = in.createIntArray();
+        mChannels = in.createIntArray();
     }
 
     @Override
@@ -115,15 +146,15 @@
             return false;
         }
 
-        return (radioAccessNetwork == ras.radioAccessNetwork
-                && Arrays.equals(bands, ras.bands)
-                && Arrays.equals(channels, ras.channels));
+        return (mRadioAccessNetwork == ras.mRadioAccessNetwork
+                && Arrays.equals(mBands, ras.mBands)
+                && Arrays.equals(mChannels, ras.mChannels));
     }
 
     @Override
     public int hashCode () {
-        return ((radioAccessNetwork * 31)
-                + (Arrays.hashCode(bands) * 37)
-                + (Arrays.hashCode(channels)) * 39);
+        return ((mRadioAccessNetwork * 31)
+                + (Arrays.hashCode(mBands) * 37)
+                + (Arrays.hashCode(mChannels)) * 39);
     }
 }
diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/RadioNetworkConstants.java
index 1a9072d..5f5dd82 100644
--- a/telephony/java/android/telephony/RadioNetworkConstants.java
+++ b/telephony/java/android/telephony/RadioNetworkConstants.java
@@ -18,7 +18,6 @@
 
 /**
  * Contains radio access network related constants.
- * @hide
  */
 public final class RadioNetworkConstants {
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 7ab75f5..5d88cf0 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -344,6 +344,7 @@
      * </p>
      *
      * <p>Requires Permission:
+     * {@link android.Manifest.permission#SEND_SMS} and
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
      * privileges.
      * </p>
@@ -351,6 +352,10 @@
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
      */
     @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+            android.Manifest.permission.SEND_SMS
+    })
     public void sendTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -1129,6 +1134,8 @@
 
     // SMS send failure result codes
 
+    /** No error. {@hide}*/
+    static public final int RESULT_ERROR_NONE    = 0;
     /** Generic failure cause */
     static public final int RESULT_ERROR_GENERIC_FAILURE    = 1;
     /** Failed because radio was explicitly turned off */
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 710eff0..a6dbc06 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -19,6 +19,7 @@
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
 import android.annotation.StringDef;
+import android.app.PendingIntent;
 import android.content.res.Resources;
 import android.os.Binder;
 import android.text.TextUtils;
@@ -92,11 +93,13 @@
 
     /**
      * Indicates a 3GPP format SMS message.
+     * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent)
      */
     public static final String FORMAT_3GPP = "3gpp";
 
     /**
      * Indicates a 3GPP2 format SMS message.
+     * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent)
      */
     public static final String FORMAT_3GPP2 = "3gpp2";
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 28ae10b..af5b190 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1176,12 +1176,14 @@
     }
 
     /**
-     * Returns the NAI. Return null if NAI is not available.
-     *
+     * Returns the Network Access Identifier (NAI). Return null if NAI is not available.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
-    /** {@hide}*/
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public String getNai() {
-        return getNai(getSlotIndex());
+        return getNaiBySubscriberId(getSubId());
     }
 
     /**
@@ -1192,11 +1194,18 @@
     /** {@hide}*/
     public String getNai(int slotIndex) {
         int[] subId = SubscriptionManager.getSubId(slotIndex);
+        if (subId == null) {
+            return null;
+        }
+        return getNaiBySubscriberId(subId[0]);
+    }
+
+    private String getNaiBySubscriberId(int subId) {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
             if (info == null)
                 return null;
-            String nai = info.getNaiForSubscriber(subId[0], mContext.getOpPackageName());
+            String nai = info.getNaiForSubscriber(subId, mContext.getOpPackageName());
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Rlog.v(TAG, "Nai = " + nai);
             }
@@ -3146,6 +3155,7 @@
      * Initial SIM activation state, unknown. Not set by any carrier apps.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0;
 
     /**
@@ -3156,12 +3166,14 @@
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1;
 
     /**
      * Indicate SIM has been successfully activated with full service
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2;
 
     /**
@@ -3171,6 +3183,7 @@
      * deactivated sim state and set it back to activated after successfully run activation service.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3;
 
     /**
@@ -3178,14 +3191,47 @@
      * note this is currently available for data activation state. For example out of byte sim.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4;
 
+    /** @hide */
+    @IntDef({
+            SIM_ACTIVATION_STATE_UNKNOWN,
+            SIM_ACTIVATION_STATE_ACTIVATING,
+            SIM_ACTIVATION_STATE_ACTIVATED,
+            SIM_ACTIVATION_STATE_DEACTIVATED,
+            SIM_ACTIVATION_STATE_RESTRICTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SimActivationState{}
+
+     /**
+      * Sets the voice activation state
+      *
+      * <p>Requires Permission:
+      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+      * Or the calling app has carrier privileges.
+      *
+      * @param activationState The voice activation state
+      * @see #SIM_ACTIVATION_STATE_UNKNOWN
+      * @see #SIM_ACTIVATION_STATE_ACTIVATING
+      * @see #SIM_ACTIVATION_STATE_ACTIVATED
+      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+      * @see #hasCarrierPrivileges
+      * @hide
+      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setVoiceActivationState(@SimActivationState int activationState) {
+        setVoiceActivationState(getSubId(), activationState);
+    }
+
     /**
      * Sets the voice activation state for the given subscriber.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
-     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     * Or the calling app has carrier privileges.
      *
      * @param subId The subscription id.
      * @param activationState The voice activation state of the given subscriber.
@@ -3193,24 +3239,48 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATING
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    public void setVoiceActivationState(int subId, int activationState) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setVoiceActivationState(int subId, @SimActivationState int activationState) {
         try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                telephony.setVoiceActivationState(subId, activationState);
-        } catch (RemoteException ex) {
-        } catch (NullPointerException ex) {
-        }
+           ITelephony telephony = getITelephony();
+           if (telephony != null)
+               telephony.setVoiceActivationState(subId, activationState);
+       } catch (RemoteException ex) {
+       } catch (NullPointerException ex) {
+       }
+    }
+
+    /**
+     * Sets the data activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @param activationState The data activation state
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDataActivationState(@SimActivationState int activationState) {
+        setDataActivationState(getSubId(), activationState);
     }
 
     /**
      * Sets the data activation state for the given subscriber.
      *
      * <p>Requires Permission:
-     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
      *
      * @param subId The subscription id.
      * @param activationState The data activation state of the given subscriber.
@@ -3219,9 +3289,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    public void setDataActivationState(int subId, int activationState) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDataActivationState(int subId, @SimActivationState int activationState) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -3232,8 +3304,33 @@
     }
 
     /**
+     * Returns the voice activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @return voiceActivationState
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getVoiceActivationState() {
+        return getVoiceActivationState(getSubId());
+    }
+
+    /**
      * Returns the voice activation state for the given subscriber.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
      * @param subId The subscription id.
      *
      * @return voiceActivationState for the given subscriber
@@ -3241,10 +3338,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATING
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getVoiceActivationState(int subId) {
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getVoiceActivationState(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -3256,8 +3354,34 @@
     }
 
     /**
+     * Returns the data activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @return dataActivationState for the given subscriber
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getDataActivationState() {
+        return getDataActivationState(getSubId());
+    }
+
+    /**
      * Returns the data activation state for the given subscriber.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
      * @param subId The subscription id.
      *
      * @return dataActivationState for the given subscriber
@@ -3266,10 +3390,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getDataActivationState(int subId) {
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getDataActivationState(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -4840,15 +4965,14 @@
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
-     *
-     * @hide
-     * TODO: Add an overload that takes no args.
      */
-    public void setNetworkSelectionModeAutomatic(int subId) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setNetworkSelectionModeAutomatic() {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                telephony.setNetworkSelectionModeAutomatic(subId);
+            if (telephony != null) {
+                telephony.setNetworkSelectionModeAutomatic(getSubId());
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -4896,9 +5020,9 @@
      *
      * @param request Contains all the RAT with bands/channels that need to be scanned.
      * @param callback Returns network scan results or errors.
-     * @return A NetworkScan obj which contains a callback which can stop the scan.
-     * @hide
+     * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
      */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public NetworkScan requestNetworkScan(
             NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
         synchronized (this) {
@@ -4917,15 +5041,20 @@
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
-     * @hide
-     * TODO: Add an overload that takes no args.
+     * @param operatorNumeric the PLMN ID of the network to select.
+     * @param persistSelection whether the selection will persist until reboot. If true, only allows
+     * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+     * normal network selection next time.
+     * @return true on success; false on any failure.
      */
-    public boolean setNetworkSelectionModeManual(int subId, OperatorInfo operator,
-            boolean persistSelection) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean setNetworkSelectionModeManual(String operatorNumeric, boolean persistSelection) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setNetworkSelectionModeManual(subId, operator, persistSelection);
+            if (telephony != null) {
+                return telephony.setNetworkSelectionModeManual(
+                        getSubId(), operatorNumeric, persistSelection);
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setNetworkSelectionModeManual RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -4950,8 +5079,9 @@
     public boolean setPreferredNetworkType(int subId, int networkType) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
+            if (telephony != null) {
                 return telephony.setPreferredNetworkType(subId, networkType);
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -5747,39 +5877,38 @@
      * @param enable Whether to enable mobile data.
      *
      * @see #hasCarrierPrivileges
+     * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setDataEnabled(boolean enable) {
-        setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
+        setUserMobileDataEnabled(enable);
     }
 
-    /** @hide */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void setDataEnabled(int subId, boolean enable) {
-        try {
-            Log.d(TAG, "setDataEnabled: enabled=" + enable);
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                telephony.setDataEnabled(subId, enable);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
-        }
-    }
-
-
     /**
-     * @deprecated use {@link #isDataEnabled()} instead.
+     * @hide
+     * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead.
+    */
+    @SystemApi
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDataEnabled(int subId, boolean enable) {
+        setUserMobileDataEnabled(subId, enable);
+    }
+
+    /**
+     * @deprecated use {@link #isUserMobileDataEnabled()} instead.
      * @hide
      */
     @SystemApi
     @Deprecated
     public boolean getDataEnabled() {
-        return isDataEnabled();
+        return isUserMobileDataEnabled();
     }
 
     /**
-     * Returns whether mobile data is enabled or not.
+     * Returns whether mobile data is enabled or not per user setting. There are other factors
+     * that could disable mobile data, but they are not considered here.
      *
      * If this object has been created with {@link #createForSubscriptionId}, applies to the given
      * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
@@ -5796,28 +5925,21 @@
      * @return true if mobile data is enabled.
      *
      * @see #hasCarrierPrivileges
+     * @deprecated use {@link #isUserMobileDataEnabled()} instead.
      */
-    @SuppressWarnings("deprecation")
+    @Deprecated
     public boolean isDataEnabled() {
-        return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+        return isUserMobileDataEnabled();
     }
 
     /**
-     * @deprecated use {@link #isDataEnabled(int)} instead.
+     * @deprecated use {@link #isUserMobileDataEnabled()} instead.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public boolean getDataEnabled(int subId) {
-        boolean retVal = false;
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                retVal = telephony.getDataEnabled(subId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#getDataEnabled", e);
-        } catch (NullPointerException e) {
-        }
-        return retVal;
+        return isUserMobileDataEnabled(subId);
     }
 
     /** @hide */
@@ -7021,4 +7143,101 @@
         }
         return null;
     }
+
+    /**
+     * Turns mobile data on or off.
+     * If the {@link TelephonyManager} object has been created with
+     * {@link #createForSubscriptionId}, this API applies to the given subId.
+     * Otherwise, it applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
+     *     calling app has carrier privileges.
+     *
+     * @param enable Whether to enable mobile data.
+     *
+     * @see #hasCarrierPrivileges
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setUserMobileDataEnabled(boolean enable) {
+        setUserMobileDataEnabled(
+                getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
+    }
+
+    /**
+     * Returns whether mobile data is enabled or not per user setting. There are other factors
+     * that could disable mobile data, but they are not considered here.
+     *
+     * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+     * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires one of the following permissions:
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+     * calling app has carrier privileges.
+     *
+     * <p>Note that this does not take into account any data restrictions that may be present on the
+     * calling app. Such restrictions may be inspected with
+     * {@link ConnectivityManager#getRestrictBackgroundStatus}.
+     *
+     * @return true if mobile data is enabled.
+     *
+     * @see #hasCarrierPrivileges
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.ACCESS_NETWORK_STATE,
+            android.Manifest.permission.MODIFY_PHONE_STATE
+    })
+    public boolean isUserMobileDataEnabled() {
+        return isUserMobileDataEnabled(
+                getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+    }
+
+    /**
+     * @hide
+     * Unlike isUserMobileDataEnabled, this API also evaluates carrierDataEnabled,
+     * policyDataEnabled etc to give a final decision.
+     */
+    public boolean isMobileDataEnabled() {
+        boolean retVal = false;
+        try {
+            int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                retVal = telephony.isDataEnabled(subId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+        } catch (NullPointerException e) {
+        }
+        return retVal;
+    }
+
+    /**
+     * Utility class of {@link #isUserMobileDataEnabled()};
+     */
+    private boolean isUserMobileDataEnabled(int subId) {
+        boolean retVal = false;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                retVal = telephony.isUserDataEnabled(subId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isUserDataEnabled", e);
+        } catch (NullPointerException e) {
+        }
+        return retVal;
+    }
+
+    /** Utility method of {@link #setUserMobileDataEnabled(boolean)} */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    private void setUserMobileDataEnabled(int subId, boolean enable) {
+        try {
+            Log.d(TAG, "setUserMobileDataEnabled: enabled=" + enable);
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                telephony.setUserDataEnabled(subId, enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setUserDataEnabled", e);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 7bcdcdc..c182e34 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -38,7 +38,6 @@
 
 /**
  * Manages the radio access network scan requests and callbacks.
- * @hide
  */
 public final class TelephonyScanManager {
 
@@ -55,7 +54,8 @@
     public static final int CALLBACK_SCAN_COMPLETE = 3;
 
     /**
-     * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
+     * The caller of
+     * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
      * implement and provide this callback so that the scan results or errors can be returned.
      */
     public static abstract class NetworkScanCallback {
@@ -75,8 +75,10 @@
          *
          * This callback will be called whenever there is any error about the scan, and the scan
          * will be terminated. onComplete() will NOT be called.
+         *
+         * @param error Error code when the scan is failed, as defined in {@link NetworkScan}.
          */
-        public void onError(int error) {}
+        public void onError(@NetworkScan.ScanErrorCode int error) {}
     }
 
     private static class NetworkScanInfo {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 2507cfee..973df31 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -168,12 +168,10 @@
     public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
 
     private final Context mContext;
-    private final IEuiccController mController;
 
     /** @hide */
     public EuiccManager(Context context) {
         mContext = context;
-        mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
     }
 
     /**
@@ -189,7 +187,7 @@
     public boolean isEnabled() {
         // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
         // restrictions.
-        return mController != null;
+        return getIEuiccController() != null;
     }
 
     /**
@@ -206,7 +204,7 @@
             return null;
         }
         try {
-            return mController.getEid();
+            return getIEuiccController().getEid();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -232,7 +230,7 @@
             return;
         }
         try {
-            mController.downloadSubscription(subscription, switchAfterDownload,
+            getIEuiccController().downloadSubscription(subscription, switchAfterDownload,
                     mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -296,7 +294,7 @@
             return;
         }
         try {
-            mController.continueOperation(resolutionIntent, resolutionExtras);
+            getIEuiccController().continueOperation(resolutionIntent, resolutionExtras);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -328,7 +326,7 @@
             return;
         }
         try {
-            mController.getDownloadableSubscriptionMetadata(
+            getIEuiccController().getDownloadableSubscriptionMetadata(
                     subscription, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -358,7 +356,7 @@
             return;
         }
         try {
-            mController.getDefaultDownloadableSubscriptionList(
+            getIEuiccController().getDefaultDownloadableSubscriptionList(
                     mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -377,7 +375,7 @@
             return null;
         }
         try {
-            return mController.getEuiccInfo();
+            return getIEuiccController().getEuiccInfo();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -402,7 +400,7 @@
             return;
         }
         try {
-            mController.deleteSubscription(
+            getIEuiccController().deleteSubscription(
                     subscriptionId, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -429,7 +427,7 @@
             return;
         }
         try {
-            mController.switchToSubscription(
+            getIEuiccController().switchToSubscription(
                     subscriptionId, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -455,7 +453,8 @@
             return;
         }
         try {
-            mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent);
+            getIEuiccController().updateSubscriptionNickname(
+                    subscriptionId, nickname, callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -477,7 +476,7 @@
             return;
         }
         try {
-            mController.eraseSubscriptions(callbackIntent);
+            getIEuiccController().eraseSubscriptions(callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -507,7 +506,7 @@
             return;
         }
         try {
-            mController.retainSubscriptionsForFactoryReset(callbackIntent);
+            getIEuiccController().retainSubscriptionsForFactoryReset(callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -520,4 +519,8 @@
             // Caller canceled the callback; do nothing.
         }
     }
+
+    private static IEuiccController getIEuiccController() {
+        return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+    }
 }
diff --git a/telephony/java/android/telephony/ims/feature/SmsFeature.java b/telephony/java/android/telephony/ims/feature/SmsFeature.java
deleted file mode 100644
index c1366db..0000000
--- a/telephony/java/android/telephony/ims/feature/SmsFeature.java
+++ /dev/null
@@ -1,237 +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 android.telephony.ims.feature;
-
-import android.annotation.SystemApi;
-import android.os.RemoteException;
-import com.android.ims.internal.IImsSmsFeature;
-import com.android.ims.internal.ISmsListener;
-
-/**
- * Base implementation of SMS over IMS functionality.
- *
- * @hide
- */
-public class SmsFeature extends ImsFeature {
-  /**
-   * SMS over IMS format is 3gpp.
-   */
-  public static final int IMS_SMS_FORMAT_3GPP = 1;
-
-  /**
-   * SMS over IMS format is 3gpp2.
-   */
-  public static final int IMS_SMS_FORMAT_3GPP2 = 2;
-
-  /**
-   * Message was sent successfully.
-   */
-  public static final int SEND_STATUS_OK = 1;
-
-  /**
-   * IMS provider failed to send the message and platform should not retry falling back to sending
-   * the message using the radio.
-   */
-  public static final int SEND_STATUS_ERROR = 2;
-
-  /**
-   * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
-   * to high.
-   */
-  public static final int SEND_STATUS_ERROR_RETRY = 3;
-
-  /**
-   * IMS provider failed to send the message and platform should retry falling back to sending
-   * the message using the radio.
-   */
-  public static final int SEND_STATUS_ERROR_FALLBACK = 4;
-
-  /**
-   * Message was delivered successfully.
-   */
-  public static final int DELIVER_STATUS_OK = 1;
-
-  /**
-   * Message was not delivered.
-   */
-  public static final int DELIVER_STATUS_ERROR = 2;
-
-  // Lock for feature synchronization
-  private final Object mLock = new Object();
-  private ISmsListener mSmsListener;
-
-  private final IImsSmsFeature mIImsSmsBinder = new IImsSmsFeature.Stub() {
-    @Override
-    public void registerSmsListener(ISmsListener listener) {
-      synchronized (mLock) {
-        SmsFeature.this.registerSmsListener(listener);
-      }
-    }
-
-    @Override
-    public void sendSms(int format, int messageRef, boolean retry, byte[] pdu) {
-      synchronized (mLock) {
-        SmsFeature.this.sendSms(format, messageRef, retry, pdu);
-      }
-    }
-
-    @Override
-    public void acknowledgeSms(int messageRef, int result) {
-      synchronized (mLock) {
-        SmsFeature.this.acknowledgeSms(messageRef, result);
-      }
-    }
-
-    @Override
-    public int getSmsFormat() {
-      synchronized (mLock) {
-        return SmsFeature.this.getSmsFormat();
-      }
-    }
-  };
-
-  /**
-   * Registers a listener responsible for handling tasks like delivering messages.
-
-   * @param listener listener to register.
-   *
-   * @hide
-   */
-  @SystemApi
-  public final void registerSmsListener(ISmsListener listener) {
-    synchronized (mLock) {
-      mSmsListener = listener;
-    }
-  }
-
-  /**
-   * This method will be triggered by the platform when the user attempts to send an SMS. This
-   * method should be implemented by the IMS providers to provide implementation of sending an SMS
-   * over IMS.
-   *
-   * @param format the format of the message. One of {@link #IMS_SMS_FORMAT_3GPP} or
-   *                {@link #IMS_SMS_FORMAT_3GPP2}
-   * @param messageRef the message reference.
-   * @param retry whether it is a retry of an already attempted message or not.
-   * @param pdu PDUs representing the contents of the message.
-   */
-  public void sendSms(int format, int messageRef, boolean isRetry, byte[] pdu) {
-  }
-
-  /**
-   * This method will be triggered by the platform after {@link #deliverSms(int, byte[])} has been
-   * called to deliver the result to the IMS provider. It will also be triggered after
-   * {@link #setSentSmsResult(int, int)} has been called to provide the result of the operation.
-   *
-   * @param result Should be {@link #DELIVER_STATUS_OK} if the message was delivered successfully,
-   * {@link #DELIVER_STATUS_ERROR} otherwise.
-   * @param messageRef the message reference.
-   */
-  public void acknowledgeSms(int messageRef, int result) {
-
-  }
-
-  /**
-   * This method should be triggered by the IMS providers when there is an incoming message. The
-   * platform will deliver the message to the messages database and notify the IMS provider of the
-   * result by calling {@link #acknowledgeSms(int)}.
-   *
-   * This method must not be called before {@link #onFeatureReady()} is called.
-   *
-   * @param format the format of the message.One of {@link #IMS_SMS_FORMAT_3GPP} or
-   *                {@link #IMS_SMS_FORMAT_3GPP2}
-   * @param pdu PDUs representing the contents of the message.
-   * @throws IllegalStateException if called before {@link #onFeatureReady()}
-   */
-  public final void deliverSms(int format, byte[] pdu) throws IllegalStateException {
-    // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
-    // otherwise.
-    try {
-      mSmsListener.deliverSms(format, pdu);
-    } catch (RemoteException e) {
-    }
-  }
-
-  /**
-   * This method should be triggered by the IMS providers to pass the result of the sent message
-   * to the platform.
-   *
-   * This method must not be called before {@link #onFeatureReady()} is called.
-   *
-   * @param messageRef the message reference.
-   * @param result One of {@link #SEND_STATUS_OK}, {@link #SEND_STATUS_ERROR},
-   *                {@link #SEND_STATUS_ERROR_RETRY}, {@link #SEND_STATUS_ERROR_FALLBACK}
-   * @throws IllegalStateException if called before {@link #onFeatureReady()}
-   */
-  public final void setSentSmsResult(int messageRef, int result) throws IllegalStateException {
-    // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
-    // otherwise.
-    try {
-      mSmsListener.setSentSmsResult(messageRef, result);
-    } catch (RemoteException e) {
-    }
-  }
-
-  /**
-   * Sets the status report of the sent message.
-   *
-   * @param format Should be {@link #IMS_SMS_FORMAT_3GPP} or {@link #IMS_SMS_FORMAT_3GPP2}
-   * @param pdu PDUs representing the content of the status report.
-   * @throws IllegalStateException if called before {@link #onFeatureReady()}
-   */
-  public final void setSentSmsStatusReport(int format, byte[] pdu) {
-    // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
-    // otherwise.
-    try {
-      mSmsListener.setSentSmsStatusReport(format, pdu);
-    } catch (RemoteException e) {
-    }
-  }
-
-  /**
-   * Returns the SMS format. Default is {@link #IMS_SMS_FORMAT_3GPP} unless overridden by IMS
-   * Provider.
-   *
-   * @return sms format.
-   */
-  public int getSmsFormat() {
-    return IMS_SMS_FORMAT_3GPP;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public void onFeatureReady() {
-
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void onFeatureRemoved() {
-
-  }
-
-  /**
-   * @hide
-   */
-  @Override
-  public final IImsSmsFeature getBinder() {
-    return mIImsSmsBinder;
-  }
-}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
new file mode 100644
index 0000000..47414cf
--- /dev/null
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.ims.internal;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for SMS over IMS.
+ *
+ * Any service wishing to provide SMS over IMS should extend this class and implement all methods
+ * that the service supports.
+ * @hide
+ */
+public class SmsImplBase {
+  private static final String LOG_TAG = "SmsImplBase";
+
+  @IntDef({
+          SEND_STATUS_OK,
+          SEND_STATUS_ERROR,
+          SEND_STATUS_ERROR_RETRY,
+          SEND_STATUS_ERROR_FALLBACK
+      })
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface SendStatusResult {}
+  /**
+   * Message was sent successfully.
+   */
+  public static final int SEND_STATUS_OK = 1;
+
+  /**
+   * IMS provider failed to send the message and platform should not retry falling back to sending
+   * the message using the radio.
+   */
+  public static final int SEND_STATUS_ERROR = 2;
+
+  /**
+   * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
+   * to high.
+   */
+  public static final int SEND_STATUS_ERROR_RETRY = 3;
+
+  /**
+   * IMS provider failed to send the message and platform should retry falling back to sending
+   * the message using the radio.
+   */
+  public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+
+  @IntDef({
+          DELIVER_STATUS_OK,
+          DELIVER_STATUS_ERROR
+      })
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface DeliverStatusResult {}
+  /**
+   * Message was delivered successfully.
+   */
+  public static final int DELIVER_STATUS_OK = 1;
+
+  /**
+   * Message was not delivered.
+   */
+  public static final int DELIVER_STATUS_ERROR = 2;
+
+  @IntDef({
+          STATUS_REPORT_STATUS_OK,
+          STATUS_REPORT_STATUS_ERROR
+      })
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface StatusReportResult {}
+
+  /**
+   * Status Report was set successfully.
+   */
+  public static final int STATUS_REPORT_STATUS_OK = 1;
+
+  /**
+   * Error while setting status report.
+   */
+  public static final int STATUS_REPORT_STATUS_ERROR = 2;
+
+
+  // Lock for feature synchronization
+  private final Object mLock = new Object();
+  private IImsSmsListener mListener;
+
+  /**
+   * Registers a listener responsible for handling tasks like delivering messages.
+   *
+   * @param listener listener to register.
+   *
+   * @hide
+   */
+  public final void registerSmsListener(IImsSmsListener listener) {
+    synchronized (mLock) {
+      mListener = listener;
+    }
+  }
+
+  /**
+   * This method will be triggered by the platform when the user attempts to send an SMS. This
+   * method should be implemented by the IMS providers to provide implementation of sending an SMS
+   * over IMS.
+   *
+   * @param smsc the Short Message Service Center address.
+   * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+   * {@link SmsMessage#FORMAT_3GPP2}.
+   * @param messageRef the message reference.
+   * @param isRetry whether it is a retry of an already attempted message or not.
+   * @param pdu PDUs representing the contents of the message.
+   */
+  public void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+    // Base implementation returns error. Should be overridden.
+    try {
+      onSendSmsResult(messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+    } catch (RemoteException e) {
+      Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
+    }
+  }
+
+  /**
+   * This method will be triggered by the platform after {@link #onSmsReceived(String, byte[])} has
+   * been called to deliver the result to the IMS provider.
+   *
+   * @param result result of delivering the message. Valid values are defined in
+   * {@link DeliverStatusResult}
+   * @param messageRef the message reference or -1 of unavailable.
+   */
+  public void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+
+  }
+
+  /**
+   * This method will be triggered by the platform after
+   * {@link #onSmsStatusReportReceived(int, int, byte[])} has been called to provide the result to
+   * the IMS provider.
+   *
+   * @param result result of delivering the message. Valid values are defined in
+   * {@link StatusReportResult}
+   * @param messageRef the message reference or -1 of unavailable.
+   */
+  public void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+
+  }
+
+  /**
+   * This method should be triggered by the IMS providers when there is an incoming message. The
+   * platform will deliver the message to the messages database and notify the IMS provider of the
+   * result by calling {@link #acknowledgeSms(int, int)}.
+   *
+   * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+   *
+   * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+   * {@link SmsMessage#FORMAT_3GPP2}.
+   * @param pdu PDUs representing the contents of the message.
+   * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+   */
+  public final void onSmsReceived(String format, byte[] pdu) throws IllegalStateException {
+    synchronized (mLock) {
+      if (mListener == null) {
+        throw new IllegalStateException("Feature not ready.");
+      }
+      try {
+        mListener.onSmsReceived(format, pdu);
+        acknowledgeSms(-1, DELIVER_STATUS_OK);
+      } catch (RemoteException e) {
+        Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
+        acknowledgeSms(-1, DELIVER_STATUS_ERROR);
+      }
+    }
+  }
+
+  /**
+   * This method should be triggered by the IMS providers to pass the result of the sent message
+   * to the platform.
+   *
+   * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+   *
+   * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+   * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+   * @param reason reason in case status is failure. Valid values are:
+   *  {@link SmsManager#RESULT_ERROR_NONE},
+   *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
+   *  {@link SmsManager#RESULT_ERROR_RADIO_OFF},
+   *  {@link SmsManager#RESULT_ERROR_NULL_PDU},
+   *  {@link SmsManager#RESULT_ERROR_NO_SERVICE},
+   *  {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+   *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
+   *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
+   * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+   * @throws RemoteException if the connection to the framework is not available. If this happens
+   *  attempting to send the SMS should be aborted.
+   */
+  public final void onSendSmsResult(int messageRef, @SendStatusResult int status, int reason)
+      throws IllegalStateException, RemoteException {
+    synchronized (mLock) {
+      if (mListener == null) {
+        throw new IllegalStateException("Feature not ready.");
+      }
+      mListener.onSendSmsResult(messageRef, status, reason);
+    }
+  }
+
+  /**
+   * Sets the status report of the sent message.
+   *
+   * @param messageRef the message reference.
+   * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+   * {@link SmsMessage#FORMAT_3GPP2}.
+   * @param pdu PDUs representing the content of the status report.
+   * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+   */
+  public final void onSmsStatusReportReceived(int messageRef, String format, byte[] pdu) {
+    synchronized (mLock) {
+      if (mListener == null) {
+        throw new IllegalStateException("Feature not ready.");
+      }
+      try {
+        mListener.onSmsStatusReportReceived(messageRef, format, pdu);
+      } catch (RemoteException e) {
+        Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+        acknowledgeSmsReport(messageRef, STATUS_REPORT_STATUS_ERROR);
+      }
+    }
+  }
+
+  /**
+   * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
+   * Provider.
+   *
+   * @return  the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+   * {@link SmsMessage#FORMAT_3GPP2}.
+   */
+  public String getSmsFormat() {
+    return SmsMessage.FORMAT_3GPP;
+  }
+
+}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
index 7125781..d976686 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
@@ -18,6 +18,7 @@
 
 import android.os.Message;
 import android.telephony.ims.internal.aidl.IImsMmTelListener;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
 import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
 import android.telephony.ims.internal.aidl.IImsCallSessionListener;
 import android.telephony.ims.internal.feature.CapabilityChangeRequest;
@@ -30,7 +31,7 @@
 import com.android.ims.internal.IImsUt;
 
 /**
- * See MmTelFeature for more information.
+ * See SmsImplBase for more information.
  * {@hide}
  */
 interface IImsMmTelFeature {
@@ -49,4 +50,10 @@
             IImsCapabilityCallback c);
     oneway void queryCapabilityConfiguration(int capability, int radioTech,
             IImsCapabilityCallback c);
+    // SMS APIs
+    void setSmsListener(IImsSmsListener l);
+    oneway void sendSms(int messageRef, String format, String smsc, boolean retry, in byte[] pdu);
+    oneway void acknowledgeSms(int messageRef, int result);
+    oneway void acknowledgeSmsReport(int messageRef, int result);
+    String getSmsFormat();
 }
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
new file mode 100644
index 0000000..468629a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal.aidl;
+
+/**
+ * See MMTelFeature for more information.
+ * {@hide}
+ */
+interface IImsSmsListener {
+    void onSendSmsResult(in int messageRef, in int status, in int reason);
+    void onSmsStatusReportReceived(in int messageRef, in String format, in byte[] pdu);
+    void onSmsReceived(in String format, in byte[] pdu);
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index f183a57..2f350c8 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -21,11 +21,15 @@
 import android.os.RemoteException;
 import android.telecom.TelecomManager;
 import android.telephony.ims.internal.ImsCallSessionListener;
+import android.telephony.ims.internal.SmsImplBase;
+import android.telephony.ims.internal.SmsImplBase.DeliverStatusResult;
+import android.telephony.ims.internal.SmsImplBase.StatusReportResult;
 import android.telephony.ims.internal.aidl.IImsCallSessionListener;
 import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsMmTelListener;
 import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
 import android.telephony.ims.stub.ImsEcbmImplBase;
 import android.telephony.ims.stub.ImsMultiEndpointImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
@@ -64,6 +68,11 @@
         }
 
         @Override
+        public void setSmsListener(IImsSmsListener l) throws RemoteException {
+            MmTelFeature.this.setSmsListener(l);
+        }
+
+        @Override
         public int getFeatureState() throws RemoteException {
             synchronized (mLock) {
                 return MmTelFeature.this.getFeatureState();
@@ -143,6 +152,34 @@
                 IImsCapabilityCallback c) {
             queryCapabilityConfigurationInternal(capability, radioTech, c);
         }
+
+        @Override
+        public void sendSms(int messageRef, String format, String smsc, boolean retry, byte[] pdu) {
+            synchronized (mLock) {
+                MmTelFeature.this.sendSms(messageRef, format, smsc, retry, pdu);
+            }
+        }
+
+        @Override
+        public void acknowledgeSms(int messageRef, int result) {
+            synchronized (mLock) {
+                MmTelFeature.this.acknowledgeSms(messageRef, result);
+            }
+        }
+
+        @Override
+        public void acknowledgeSmsReport(int messageRef, int result) {
+            synchronized (mLock) {
+                MmTelFeature.this.acknowledgeSmsReport(messageRef, result);
+            }
+        }
+
+        @Override
+        public String getSmsFormat() {
+            synchronized (mLock) {
+                return MmTelFeature.this.getSmsFormat();
+            }
+        }
     };
 
     /**
@@ -171,7 +208,8 @@
                 value = {
                         CAPABILITY_TYPE_VOICE,
                         CAPABILITY_TYPE_VIDEO,
-                        CAPABILITY_TYPE_UT
+                        CAPABILITY_TYPE_UT,
+                        CAPABILITY_TYPE_SMS
                 })
         @Retention(RetentionPolicy.SOURCE)
         public @interface MmTelCapability {}
@@ -191,6 +229,11 @@
          */
         public static final int CAPABILITY_TYPE_UT = 1 << 2;
 
+        /**
+         * This MmTelFeature supports SMS (IR.92)
+         */
+        public static final int CAPABILITY_TYPE_SMS = 1 << 3;
+
         @Override
         public final void addCapabilities(@MmTelCapability int capabilities) {
             super.addCapabilities(capabilities);
@@ -239,6 +282,10 @@
         }
     }
 
+    private void setSmsListener(IImsSmsListener listener) {
+        getSmsImplementation().registerSmsListener(listener);
+    }
+
     private void queryCapabilityConfigurationInternal(int capability, int radioTech,
             IImsCapabilityCallback c) {
         boolean enabled = queryCapabilityConfiguration(capability, radioTech);
@@ -400,6 +447,32 @@
         // Base Implementation - Should be overridden
     }
 
+    private void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+        getSmsImplementation().sendSms(messageRef, format, smsc, isRetry, pdu);
+    }
+
+    private void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+        getSmsImplementation().acknowledgeSms(messageRef, result);
+    }
+
+    private void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+        getSmsImplementation().acknowledgeSmsReport(messageRef, result);
+    }
+
+    private String getSmsFormat() {
+        return getSmsImplementation().getSmsFormat();
+    }
+
+    /**
+     * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
+     * non-functional implementation is returned.
+     *
+     * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider.
+     */
+    protected SmsImplBase getSmsImplementation() {
+        return new SmsImplBase();
+    }
+
     /**{@inheritDoc}*/
     @Override
     public void onFeatureRemoved() {
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 8529f52..f78e7a6 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -51,8 +51,8 @@
     /** @hide */
     public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
             String newServiceId, Date start, Date end) {
-        if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
-                || newLocales == null || newLocales.isEmpty() || TextUtils.isEmpty(newServiceId)
+        if (newNames == null || newClassName == null
+                || newLocales == null || newServiceId == null
                 || start == null || end == null) {
             throw new IllegalArgumentException("Bad ServiceInfo construction");
         }
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 6ad54c1..4f6f68c 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -104,6 +104,9 @@
     // MT : No action from user after alerting the call
     public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
 
+    //Call was blocked by call barring
+    public static final int CODE_CALL_BARRED = 240;
+
     //Call failures for FDN
     public static final int CODE_FDN_BLOCKED = 241;
 
diff --git a/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl b/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl
deleted file mode 100644
index 5068128..0000000
--- a/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl
+++ /dev/null
@@ -1,31 +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.ims.internal;
-
-import com.android.ims.internal.ISmsListener;
-
-/**
- * See SmsFeature for more information.
- *
- * {@hide}
- */
-interface IImsSmsFeature {
-    void registerSmsListener(in ISmsListener listener);
-    void sendSms(in int format, in int messageRef, in boolean retry, in byte[] pdu);
-    void acknowledgeSms(in int messageRef, in int result);
-    int getSmsFormat();
-}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/ISmsListener.aidl b/telephony/java/com/android/ims/internal/ISmsListener.aidl
deleted file mode 100644
index 1266f04..0000000
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ /dev/null
@@ -1,27 +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.ims.internal;
-
-/**
- * See SmsFeature for more information.
- * {@hide}
- */
-interface ISmsListener {
-    void setSentSmsResult(in int messageRef, in int result);
-    void setSentSmsStatusReport(in int format, in byte[] pdu);
-    void deliverSms(in int format, in byte[] pdu);
-}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a9f8f45..416146f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -846,13 +846,13 @@
      * Ask the radio to connect to the input network and change selection mode to manual.
      *
      * @param subId the id of the subscription.
-     * @param operatorInfo the operator to attach to.
-     * @param persistSelection should the selection persist till reboot or its
-     *        turned off? Will also result in notification being not shown to
-     *        the user if the signal is lost.
+     * @param operatorNumeric the PLMN of the operator to attach to.
+     * @param persistSelection Whether the selection will persist until reboot. If true, only allows
+     * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+     * normal network selection next time.
      * @return true if the request suceeded.
      */
-    boolean setNetworkSelectionModeManual(int subId, in OperatorInfo operator,
+    boolean setNetworkSelectionModeManual(int subId, in String operatorNumeric,
             boolean persistSelection);
 
     /**
@@ -870,14 +870,33 @@
      *
      * @param enable true to turn on, else false
      */
-    void setDataEnabled(int subId, boolean enable);
+    void setUserDataEnabled(int subId, boolean enable);
+
+    /**
+     * Get the user enabled state of Mobile Data.
+     *
+     * TODO: remove and use isUserDataEnabled.
+     * This can't be removed now because some vendor codes
+     * calls through ITelephony directly while they should
+     * use TelephonyManager.
+     *
+     * @return true on enabled
+     */
+    boolean getDataEnabled(int subId);
 
     /**
      * Get the user enabled state of Mobile Data.
      *
      * @return true on enabled
      */
-    boolean getDataEnabled(int subId);
+    boolean isUserDataEnabled(int subId);
+
+    /**
+     * Get the overall enabled state of Mobile Data.
+     *
+     * @return true on enabled
+     */
+    boolean isDataEnabled(int subId);
 
     /**
      * Get P-CSCF address from PCO after data connection is established or modified.
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 99a82ad..9f8b3a8 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -32,6 +32,12 @@
 public class IccUtils {
     static final String LOG_TAG="IccUtils";
 
+    // A table mapping from a number to a hex character for fast encoding hex strings.
+    private static final char[] HEX_CHARS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+
     /**
      * Many fields in GSM SIM's are stored as nibble-swizzled BCD
      *
@@ -62,6 +68,41 @@
     }
 
     /**
+     * Converts a bcd byte array to String with offset 0 and byte array length.
+     */
+    public static String bcdToString(byte[] data) {
+        return bcdToString(data, 0, data.length);
+    }
+
+    /**
+     * Converts BCD string to bytes.
+     *
+     * @param bcd This should have an even length. If not, an "0" will be appended to the string.
+     */
+    public static byte[] bcdToBytes(String bcd) {
+        byte[] output = new byte[(bcd.length() + 1) / 2];
+        bcdToBytes(bcd, output);
+        return output;
+    }
+
+    /**
+     * Converts BCD string to bytes and put it into the given byte array.
+     *
+     * @param bcd This should have an even length. If not, an "0" will be appended to the string.
+     * @param bytes If the array size is less than needed, the rest of the BCD string isn't be
+     *     converted. If the array size is more than needed, the rest of array remains unchanged.
+     */
+    public static void bcdToBytes(String bcd, byte[] bytes) {
+        if (bcd.length() % 2 != 0) {
+            bcd += "0";
+        }
+        int size = Math.min(bytes.length * 2, bcd.length());
+        for (int i = 0, j = 0; i + 1 < size; i += 2, j++) {
+            bytes[j] = (byte) (charToByte(bcd.charAt(i + 1)) << 4 | charToByte(bcd.charAt(i)));
+        }
+    }
+
+    /**
      * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
      * Returns a concatenated string of MCC+MNC, stripping
      * all invalid character 'f'
@@ -94,10 +135,10 @@
             int v;
 
             v = data[i] & 0xf;
-            ret.append("0123456789abcdef".charAt(v));
+            ret.append(HEX_CHARS[v]);
 
             v = (data[i] >> 4) & 0xf;
-            ret.append("0123456789abcdef".charAt(v));
+            ret.append(HEX_CHARS[v]);
         }
 
         return ret.toString();
@@ -305,7 +346,7 @@
         return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
     }
 
-    static int
+    public static int
     hexCharToInt(char c) {
         if (c >= '0' && c <= '9') return (c - '0');
         if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
@@ -361,11 +402,11 @@
 
             b = 0x0f & (bytes[i] >> 4);
 
-            ret.append("0123456789abcdef".charAt(b));
+            ret.append(HEX_CHARS[b]);
 
             b = 0x0f & bytes[i];
 
-            ret.append("0123456789abcdef".charAt(b));
+            ret.append(HEX_CHARS[b]);
         }
 
         return ret.toString();
@@ -416,7 +457,6 @@
 
         if ((data[offset] & 0x40) != 0) {
             // FIXME(mkf) add country initials here
-
         }
 
         return ret;
@@ -575,4 +615,239 @@
         }
         return iccId.substring( 0, position );
     }
+
+    /**
+     * Converts a series of bytes to an integer. This method currently only supports positive 32-bit
+     * integers.
+     *
+     * @param src The source bytes.
+     * @param offset The position of the first byte of the data to be converted. The data is base
+     *     256 with the most significant digit first.
+     * @param length The length of the data to be converted. It must be <= 4.
+     * @throws IllegalArgumentException If {@code length} is bigger than 4 or {@code src} cannot be
+     *     parsed as a positive integer.
+     * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+     *     exceeds the bounds of {@code src}.
+     */
+    public static int bytesToInt(byte[] src, int offset, int length) {
+        if (length > 4) {
+            throw new IllegalArgumentException(
+                    "length must be <= 4 (only 32-bit integer supported): " + length);
+        }
+        if (offset < 0 || length < 0 || offset + length > src.length) {
+            throw new IndexOutOfBoundsException(
+                    "Out of the bounds: src=["
+                            + src.length
+                            + "], offset="
+                            + offset
+                            + ", length="
+                            + length);
+        }
+        int result = 0;
+        for (int i = 0; i < length; i++) {
+            result = (result << 8) | (src[offset + i] & 0xFF);
+        }
+        if (result < 0) {
+            throw new IllegalArgumentException(
+                    "src cannot be parsed as a positive integer: " + result);
+        }
+        return result;
+    }
+
+    /**
+     * Converts a series of bytes to a raw long variable which can be both positive and negative.
+     * This method currently only supports 64-bit long variable.
+     *
+     * @param src The source bytes.
+     * @param offset The position of the first byte of the data to be converted. The data is base
+     *     256 with the most significant digit first.
+     * @param length The length of the data to be converted. It must be <= 8.
+     * @throws IllegalArgumentException If {@code length} is bigger than 8.
+     * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+     *     exceeds the bounds of {@code src}.
+     */
+    public static long bytesToRawLong(byte[] src, int offset, int length) {
+        if (length > 8) {
+            throw new IllegalArgumentException(
+                    "length must be <= 8 (only 64-bit long supported): " + length);
+        }
+        if (offset < 0 || length < 0 || offset + length > src.length) {
+            throw new IndexOutOfBoundsException(
+                    "Out of the bounds: src=["
+                            + src.length
+                            + "], offset="
+                            + offset
+                            + ", length="
+                            + length);
+        }
+        long result = 0;
+        for (int i = 0; i < length; i++) {
+            result = (result << 8) | (src[offset + i] & 0xFF);
+        }
+        return result;
+    }
+
+    /**
+     * Converts an integer to a new byte array with base 256 and the most significant digit first.
+     *
+     * @throws IllegalArgumentException If {@code value} is negative.
+     */
+    public static byte[] unsignedIntToBytes(int value) {
+        if (value < 0) {
+            throw new IllegalArgumentException("value must be 0 or positive: " + value);
+        }
+        byte[] bytes = new byte[byteNumForUnsignedInt(value)];
+        unsignedIntToBytes(value, bytes, 0);
+        return bytes;
+    }
+
+    /**
+     * Converts an integer to a new byte array with base 256 and the most significant digit first.
+     * The first byte's highest bit is used for sign. If the most significant digit is larger than
+     * 127, an extra byte (0) will be prepended before it. This method currently doesn't support
+     * negative values.
+     *
+     * @throws IllegalArgumentException If {@code value} is negative.
+     */
+    public static byte[] signedIntToBytes(int value) {
+        if (value < 0) {
+            throw new IllegalArgumentException("value must be 0 or positive: " + value);
+        }
+        byte[] bytes = new byte[byteNumForSignedInt(value)];
+        signedIntToBytes(value, bytes, 0);
+        return bytes;
+    }
+
+    /**
+     * Converts an integer to a series of bytes with base 256 and the most significant digit first.
+     *
+     * @param value The integer to be converted.
+     * @param dest The destination byte array.
+     * @param offset The start offset of the byte array.
+     * @return The number of byte needeed.
+     * @throws IllegalArgumentException If {@code value} is negative.
+     * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}.
+     */
+    public static int unsignedIntToBytes(int value, byte[] dest, int offset) {
+        return intToBytes(value, dest, offset, false);
+    }
+
+    /**
+     * Converts an integer to a series of bytes with base 256 and the most significant digit first.
+     * The first byte's highest bit is used for sign. If the most significant digit is larger than
+     * 127, an extra byte (0) will be prepended before it. This method currently doesn't support
+     * negative values.
+     *
+     * @throws IllegalArgumentException If {@code value} is negative.
+     * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}.
+     */
+    public static int signedIntToBytes(int value, byte[] dest, int offset) {
+        return intToBytes(value, dest, offset, true);
+    }
+
+    /**
+     * Calculates the number of required bytes to represent {@code value}. The bytes will be base
+     * 256 with the most significant digit first.
+     *
+     * @throws IllegalArgumentException If {@code value} is negative.
+     */
+    public static int byteNumForUnsignedInt(int value) {
+        return byteNumForInt(value, false);
+    }
+
+    /**
+     * Calculates the number of required bytes to represent {@code value}. The bytes will be base
+     * 256 with the most significant digit first. If the most significant digit is larger than 127,
+     * an extra byte (0) will be prepended before it. This method currently only supports positive
+     * integers.
+     *
+     * @throws IllegalArgumentException If {@code value} is negative.
+     */
+    public static int byteNumForSignedInt(int value) {
+        return byteNumForInt(value, true);
+    }
+
+    private static int intToBytes(int value, byte[] dest, int offset, boolean signed) {
+        int l = byteNumForInt(value, signed);
+        if (offset < 0 || offset + l > dest.length) {
+            throw new IndexOutOfBoundsException("Not enough space to write. Required bytes: " + l);
+        }
+        for (int i = l - 1, v = value; i >= 0; i--, v >>>= 8) {
+            byte b = (byte) (v & 0xFF);
+            dest[offset + i] = b;
+        }
+        return l;
+    }
+
+    private static int byteNumForInt(int value, boolean signed) {
+        if (value < 0) {
+            throw new IllegalArgumentException("value must be 0 or positive: " + value);
+        }
+        if (signed) {
+            if (value <= 0x7F) {
+                return 1;
+            }
+            if (value <= 0x7FFF) {
+                return 2;
+            }
+            if (value <= 0x7FFFFF) {
+                return 3;
+            }
+        } else {
+            if (value <= 0xFF) {
+                return 1;
+            }
+            if (value <= 0xFFFF) {
+                return 2;
+            }
+            if (value <= 0xFFFFFF) {
+                return 3;
+            }
+        }
+        return 4;
+    }
+
+
+    /**
+     * Counts the number of trailing zero bits of a byte.
+     */
+    public static byte countTrailingZeros(byte b) {
+        if (b == 0) {
+            return 8;
+        }
+        int v = b & 0xFF;
+        byte c = 7;
+        if ((v & 0x0F) != 0) {
+            c -= 4;
+        }
+        if ((v & 0x33) != 0) {
+            c -= 2;
+        }
+        if ((v & 0x55) != 0) {
+            c -= 1;
+        }
+        return c;
+    }
+
+    /**
+     * Converts a byte to a hex string.
+     */
+    public static String byteToHex(byte b) {
+        return new String(new char[] {HEX_CHARS[(b & 0xFF) >>> 4], HEX_CHARS[b & 0xF]});
+    }
+
+    /**
+     * Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a
+     * hex number, 0 will be returned.
+     */
+    private static byte charToByte(char c) {
+        if (c >= 0x30 && c <= 0x39) {
+            return (byte) (c - 0x30);
+        } else if (c >= 0x41 && c <= 0x46) {
+            return (byte) (c - 0x37);
+        } else if (c >= 0x61 && c <= 0x66) {
+            return (byte) (c - 0x57);
+        }
+        return 0;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java
new file mode 100644
index 0000000..1ad0b66
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java
@@ -0,0 +1,151 @@
+/*
+ * 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.internal.telephony.uicc.asn1;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+/**
+ * This represents a decoder helping decode an array of bytes or a hex string into
+ * {@link Asn1Node}s. This class tracks the next position for decoding. This class is not
+ * thread-safe.
+ */
+public final class Asn1Decoder {
+    // Source byte array.
+    private final byte[] mSrc;
+    // Next position of the byte in the source array for decoding.
+    private int mPosition;
+    // Exclusive end of the range in the array for decoding.
+    private final int mEnd;
+
+    /** Creates a decoder on a hex string. */
+    public Asn1Decoder(String hex) {
+        this(IccUtils.hexStringToBytes(hex));
+    }
+
+    /** Creates a decoder on a byte array. */
+    public Asn1Decoder(byte[] src) {
+        this(src, 0, src.length);
+    }
+
+    /**
+     * Creates a decoder on a byte array slice.
+     *
+     * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+     *         exceeds the bounds of {@code bytes}.
+     */
+    public Asn1Decoder(byte[] bytes, int offset, int length) {
+        if (offset < 0 || length < 0 || offset + length > bytes.length) {
+            throw new IndexOutOfBoundsException(
+                    "Out of the bounds: bytes=["
+                            + bytes.length
+                            + "], offset="
+                            + offset
+                            + ", length="
+                            + length);
+        }
+        mSrc = bytes;
+        mPosition = offset;
+        mEnd = offset + length;
+    }
+
+    /** @return The next start position for decoding. */
+    public int getPosition() {
+        return mPosition;
+    }
+
+    /** Returns whether the node has a next node. */
+    public boolean hasNextNode() {
+        return mPosition < mEnd;
+    }
+
+    /**
+     * Parses the next node. If the node is a constructed node, its children will be parsed only
+     * when they are accessed, e.g., though {@link Asn1Node#getChildren()}.
+     *
+     * @return The next decoded {@link Asn1Node}. If success, the next decoding position will also
+     *         be updated. If any error happens, e.g., moving over the end position, {@code null}
+     *         will be returned and the next decoding position won't be modified.
+     * @throws InvalidAsn1DataException If the bytes cannot be parsed.
+     */
+    public Asn1Node nextNode() throws InvalidAsn1DataException {
+        if (mPosition >= mEnd) {
+            throw new IllegalStateException("No bytes to parse.");
+        }
+
+        int offset = mPosition;
+
+        // Extracts the tag.
+        int tagStart = offset;
+        byte b = mSrc[offset++];
+        if ((b & 0x1F) == 0x1F) {
+            // High-tag-number form
+            while (offset < mEnd && (mSrc[offset++] & 0x80) != 0) {
+                // Do nothing.
+            }
+        }
+        if (offset >= mEnd) {
+            // No length bytes or the tag is too long.
+            throw new InvalidAsn1DataException(0, "Invalid length at position: " + offset);
+        }
+        int tag;
+        try {
+            tag = IccUtils.bytesToInt(mSrc, tagStart, offset - tagStart);
+        } catch (IllegalArgumentException e) {
+            // Cannot parse the tag as an integer.
+            throw new InvalidAsn1DataException(0, "Cannot parse tag at position: " + tagStart, e);
+        }
+
+        // Extracts the length.
+        int dataLen;
+        b = mSrc[offset++];
+        if ((b & 0x80) == 0) {
+            // Short-form length
+            dataLen = b;
+        } else {
+            // Long-form length
+            int lenLen = b & 0x7F;
+            if (offset + lenLen > mEnd) {
+                // No enough bytes for the long-form length
+                throw new InvalidAsn1DataException(
+                        tag, "Cannot parse length at position: " + offset);
+            }
+            try {
+                dataLen = IccUtils.bytesToInt(mSrc, offset, lenLen);
+            } catch (IllegalArgumentException e) {
+                // Cannot parse the data length as an integer.
+                throw new InvalidAsn1DataException(
+                        tag, "Cannot parse length at position: " + offset, e);
+            }
+            offset += lenLen;
+        }
+        if (offset + dataLen > mEnd) {
+            // No enough data left.
+            throw new InvalidAsn1DataException(
+                    tag,
+                    "Incomplete data at position: "
+                            + offset
+                            + ", expected bytes: "
+                            + dataLen
+                            + ", actual bytes: "
+                            + (mEnd - offset));
+        }
+
+        Asn1Node root = new Asn1Node(tag, mSrc, offset, dataLen);
+        mPosition = offset + dataLen;
+        return root;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java
new file mode 100644
index 0000000..5eb1d5c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java
@@ -0,0 +1,598 @@
+/*
+ * 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.internal.telephony.uicc.asn1;
+
+import android.annotation.Nullable;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This represents a primitive or constructed data defined by ASN.1. A constructed node can have
+ * child nodes. A non-constructed node can have a value. This class is read-only. To build a node,
+ * you can use the {@link #newBuilder(int)} method to get a {@link Builder} instance. This class is
+ * not thread-safe.
+ */
+public final class Asn1Node {
+    private static final int INT_BYTES = Integer.SIZE / Byte.SIZE;
+    private static final List<Asn1Node> EMPTY_NODE_LIST = Collections.emptyList();
+
+    // Bytes for boolean values.
+    private static final byte[] TRUE_BYTES = new byte[] {-1};
+    private static final byte[] FALSE_BYTES = new byte[] {0};
+
+    /**
+     * This class is used to build an Asn1Node instance of a constructed tag. This class is not
+     * thread-safe.
+     */
+    public static final class Builder {
+        private final int mTag;
+        private final List<Asn1Node> mChildren;
+
+        private Builder(int tag) {
+            if (!isConstructedTag(tag)) {
+                throw new IllegalArgumentException(
+                        "Builder should be created for a constructed tag: " + tag);
+            }
+            mTag = tag;
+            mChildren = new ArrayList<>();
+        }
+
+        /**
+         * Adds a child from an existing node.
+         *
+         * @return This builder.
+         * @throws IllegalArgumentException If the child is a non-existing node.
+         */
+        public Builder addChild(Asn1Node child) {
+            mChildren.add(child);
+            return this;
+        }
+
+        /**
+         * Adds a child from another builder. The child will be built with the call to this method,
+         * and any changes to the child builder after the call to this method doesn't have effect.
+         *
+         * @return This builder.
+         */
+        public Builder addChild(Builder child) {
+            mChildren.add(child.build());
+            return this;
+        }
+
+        /**
+         * Adds children from bytes. This method calls {@link Asn1Decoder} to verify the {@code
+         * encodedBytes} and adds all nodes parsed from it as children.
+         *
+         * @return This builder.
+         * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+         */
+        public Builder addChildren(byte[] encodedBytes) throws InvalidAsn1DataException {
+            Asn1Decoder subDecoder = new Asn1Decoder(encodedBytes, 0, encodedBytes.length);
+            while (subDecoder.hasNextNode()) {
+                mChildren.add(subDecoder.nextNode());
+            }
+            return this;
+        }
+
+        /**
+         * Adds a child of non-constructed tag with an integer as the data.
+         *
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsInteger(int tag, int value) {
+            if (isConstructedTag(tag)) {
+                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+            }
+            byte[] dataBytes = IccUtils.signedIntToBytes(value);
+            addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
+            return this;
+        }
+
+        /**
+         * Adds a child of non-constructed tag with a string as the data.
+         *
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsString(int tag, String value) {
+            if (isConstructedTag(tag)) {
+                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+            }
+            byte[] dataBytes = value.getBytes(StandardCharsets.UTF_8);
+            addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
+            return this;
+        }
+
+        /**
+         * Adds a child of non-constructed tag with a byte array as the data.
+         *
+         * @param value The value will be owned by this node.
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsBytes(int tag, byte[] value) {
+            if (isConstructedTag(tag)) {
+                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+            }
+            addChild(new Asn1Node(tag, value, 0, value.length));
+            return this;
+        }
+
+        /**
+         * Adds a child of non-constructed tag with a byte array as the data from a hex string.
+         *
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsBytesFromHex(int tag, String hex) {
+            return addChildAsBytes(tag, IccUtils.hexStringToBytes(hex));
+        }
+
+        /**
+         * Adds a child of non-constructed tag with bits as the data.
+         *
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsBits(int tag, int value) {
+            if (isConstructedTag(tag)) {
+                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+            }
+            // Always allocate 5 bytes for simplicity.
+            byte[] dataBytes = new byte[INT_BYTES + 1];
+            // Puts the integer into the byte[1-4].
+            value = Integer.reverse(value);
+            int dataLength = 0;
+            for (int i = 1; i < dataBytes.length; i++) {
+                dataBytes[i] = (byte) (value >> ((INT_BYTES - i) * Byte.SIZE));
+                if (dataBytes[i] != 0) {
+                    dataLength = i;
+                }
+            }
+            dataLength++;
+            // The first byte is the number of trailing zeros of the last byte.
+            dataBytes[0] = IccUtils.countTrailingZeros(dataBytes[dataLength - 1]);
+            addChild(new Asn1Node(tag, dataBytes, 0, dataLength));
+            return this;
+        }
+
+        /**
+         * Adds a child of non-constructed tag with a boolean as the data.
+         *
+         * @return This builder.
+         * @throws IllegalStateException If the {@code tag} is not constructed..
+         */
+        public Builder addChildAsBoolean(int tag, boolean value) {
+            if (isConstructedTag(tag)) {
+                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+            }
+            addChild(new Asn1Node(tag, value ? TRUE_BYTES : FALSE_BYTES, 0, 1));
+            return this;
+        }
+
+        /** Builds the node. */
+        public Asn1Node build() {
+            return new Asn1Node(mTag, mChildren);
+        }
+    }
+
+    private final int mTag;
+    private final boolean mConstructed;
+    // Do not use this field directly in the methods other than the constructor and encoding
+    // methods (e.g., toBytes()), but always use getChildren() instead.
+    private final List<Asn1Node> mChildren;
+
+    // Byte array that actually holds the data. For a non-constructed node, this stores its actual
+    // value. If the value is not set, this is null. For constructed node, this stores encoded data
+    // of its children, which will be decoded on the first call to getChildren().
+    private @Nullable byte[] mDataBytes;
+    // Offset of the data in above byte array.
+    private int mDataOffset;
+    // Length of the data in above byte array. If it's a constructed node, this is always the total
+    // length of all its children.
+    private int mDataLength;
+    // Length of the total bytes required to encode this node.
+    private int mEncodedLength;
+
+    /**
+     * Creates a new ASN.1 data node builder with the given tag. The tag is an encoded tag including
+     * the tag class, tag number, and constructed mask.
+     */
+    public static Builder newBuilder(int tag) {
+        return new Builder(tag);
+    }
+
+    private static boolean isConstructedTag(int tag) {
+        // Constructed mask is at the 6th bit.
+        byte[] tagBytes = IccUtils.unsignedIntToBytes(tag);
+        return (tagBytes[0] & 0x20) != 0;
+    }
+
+    private static int calculateEncodedBytesNumForLength(int length) {
+        // Constructed mask is at the 6th bit.
+        int len = 1;
+        if (length > 127) {
+            len += IccUtils.byteNumForUnsignedInt(length);
+        }
+        return len;
+    }
+
+    /**
+     * Creates a node with given data bytes. If it is a constructed node, its children will be
+     * parsed when they are visited.
+     */
+    Asn1Node(int tag, @Nullable byte[] src, int offset, int length) {
+        mTag = tag;
+        // Constructed mask is at the 6th bit.
+        mConstructed = isConstructedTag(tag);
+        mDataBytes = src;
+        mDataOffset = offset;
+        mDataLength = length;
+        mChildren = mConstructed ? new ArrayList<Asn1Node>() : EMPTY_NODE_LIST;
+        mEncodedLength =
+                IccUtils.byteNumForUnsignedInt(mTag)
+                        + calculateEncodedBytesNumForLength(mDataLength)
+                        + mDataLength;
+    }
+
+    /** Creates a constructed node with given children. */
+    private Asn1Node(int tag, List<Asn1Node> children) {
+        mTag = tag;
+        mConstructed = true;
+        mChildren = children;
+
+        mDataLength = 0;
+        int size = children.size();
+        for (int i = 0; i < size; i++) {
+            mDataLength += children.get(i).mEncodedLength;
+        }
+        mEncodedLength =
+                IccUtils.byteNumForUnsignedInt(mTag)
+                        + calculateEncodedBytesNumForLength(mDataLength)
+                        + mDataLength;
+    }
+
+    public int getTag() {
+        return mTag;
+    }
+
+    public boolean isConstructed() {
+        return mConstructed;
+    }
+
+    /**
+     * Tests if a node has a child.
+     *
+     * @param tag The tag of an immediate child.
+     * @param tags The tags of lineal descendant.
+     */
+    public boolean hasChild(int tag, int... tags) throws InvalidAsn1DataException {
+        try {
+            getChild(tag, tags);
+        } catch (TagNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Gets the first child node having the given {@code tag} and {@code tags}.
+     *
+     * @param tag The tag of an immediate child.
+     * @param tags The tags of lineal descendant.
+     * @throws TagNotFoundException If the child cannot be found.
+     */
+    public Asn1Node getChild(int tag, int... tags)
+            throws TagNotFoundException, InvalidAsn1DataException {
+        if (!mConstructed) {
+            throw new TagNotFoundException(tag);
+        }
+        int index = 0;
+        Asn1Node node = this;
+        while (node != null) {
+            List<Asn1Node> children = node.getChildren();
+            int size = children.size();
+            Asn1Node foundChild = null;
+            for (int i = 0; i < size; i++) {
+                Asn1Node child = children.get(i);
+                if (child.getTag() == tag) {
+                    foundChild = child;
+                    break;
+                }
+            }
+            node = foundChild;
+            if (index >= tags.length) {
+                break;
+            }
+            tag = tags[index++];
+        }
+        if (node == null) {
+            throw new TagNotFoundException(tag);
+        }
+        return node;
+    }
+
+    /**
+     * Gets all child nodes which have the given {@code tag}.
+     *
+     * @return If this is primitive or no such children are found, an empty list will be returned.
+     */
+    public List<Asn1Node> getChildren(int tag)
+            throws TagNotFoundException, InvalidAsn1DataException {
+        if (!mConstructed) {
+            return EMPTY_NODE_LIST;
+        }
+
+        List<Asn1Node> children = getChildren();
+        if (children.isEmpty()) {
+            return EMPTY_NODE_LIST;
+        }
+        List<Asn1Node> output = new ArrayList<>();
+        int size = children.size();
+        for (int i = 0; i < size; i++) {
+            Asn1Node child = children.get(i);
+            if (child.getTag() == tag) {
+                output.add(child);
+            }
+        }
+        return output.isEmpty() ? EMPTY_NODE_LIST : output;
+    }
+
+    /**
+     * Gets all child nodes of this node. If it's a constructed node having encoded data, it's
+     * children will be decoded here.
+     *
+     * @return If this is primitive, an empty list will be returned. Do not modify the returned list
+     *     directly.
+     */
+    public List<Asn1Node> getChildren() throws InvalidAsn1DataException {
+        if (!mConstructed) {
+            return EMPTY_NODE_LIST;
+        }
+
+        if (mDataBytes != null) {
+            Asn1Decoder subDecoder = new Asn1Decoder(mDataBytes, mDataOffset, mDataLength);
+            while (subDecoder.hasNextNode()) {
+                mChildren.add(subDecoder.nextNode());
+            }
+            mDataBytes = null;
+            mDataOffset = 0;
+        }
+        return mChildren;
+    }
+
+    /** @return Whether this node has a value. False will be returned for a constructed node. */
+    public boolean hasValue() {
+        return !mConstructed && mDataBytes != null;
+    }
+
+    /**
+     * @return The data as an integer. If the data length is larger than 4, only the first 4 bytes
+     *     will be parsed.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public int asInteger() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        try {
+            return IccUtils.bytesToInt(mDataBytes, mDataOffset, mDataLength);
+        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+        }
+    }
+
+    /**
+     * @return The data as a long variable which can be both positive and negative. If the data
+     *     length is larger than 8, only the first 8 bytes will be parsed.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public long asRawLong() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        try {
+            return IccUtils.bytesToRawLong(mDataBytes, mDataOffset, mDataLength);
+        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+        }
+    }
+
+    /**
+     * @return The data as a string in UTF-8 encoding.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public String asString() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        try {
+            return new String(mDataBytes, mDataOffset, mDataLength, StandardCharsets.UTF_8);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+        }
+    }
+
+    /**
+     * @return The data as a byte array.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public byte[] asBytes() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        byte[] output = new byte[mDataLength];
+        try {
+            System.arraycopy(mDataBytes, mDataOffset, output, 0, mDataLength);
+        } catch (IndexOutOfBoundsException e) {
+            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+        }
+        return output;
+    }
+
+    /**
+     * Gets the data as an integer for BIT STRING. DER actually stores the bits in a reversed order.
+     * The returned integer here has the order fixed (first bit is at the lowest position). This
+     * method currently only support at most 32 bits which fit in an integer.
+     *
+     * @return The data as an integer. If this is constructed, a {@code null} will be returned.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public int asBits() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        int bits;
+        try {
+            bits = IccUtils.bytesToInt(mDataBytes, mDataOffset + 1, mDataLength - 1);
+        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+        }
+        for (int i = mDataLength - 1; i < INT_BYTES; i++) {
+            bits <<= Byte.SIZE;
+        }
+        return Integer.reverse(bits);
+    }
+
+    /**
+     * @return The data as a boolean.
+     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+     */
+    public boolean asBoolean() throws InvalidAsn1DataException {
+        if (mConstructed) {
+            throw new IllegalStateException("Cannot get value of a constructed node.");
+        }
+        if (mDataBytes == null) {
+            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+        }
+        if (mDataLength != 1) {
+            throw new InvalidAsn1DataException(
+                    mTag, "Cannot parse data bytes as boolean: length=" + mDataLength);
+        }
+        if (mDataOffset < 0 || mDataOffset >= mDataBytes.length) {
+            throw new InvalidAsn1DataException(
+                    mTag,
+                    "Cannot parse data bytes.",
+                    new ArrayIndexOutOfBoundsException(mDataOffset));
+        }
+        // ASN.1 has "true" as 0xFF.
+        if (mDataBytes[mDataOffset] == -1) {
+            return Boolean.TRUE;
+        } else if (mDataBytes[mDataOffset] == 0) {
+            return Boolean.FALSE;
+        }
+        throw new InvalidAsn1DataException(
+                mTag, "Cannot parse data bytes as boolean: " + mDataBytes[mDataOffset]);
+    }
+
+    /** @return The number of required bytes for encoding this node in DER. */
+    public int getEncodedLength() {
+        return mEncodedLength;
+    }
+
+    /** @return The number of required bytes for encoding this node's data in DER. */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Writes the DER encoded bytes of this node into a byte array. The number of written bytes is
+     * {@link #getEncodedLength()}.
+     *
+     * @throws IndexOutOfBoundsException If the {@code dest} doesn't have enough space to write.
+     */
+    public void writeToBytes(byte[] dest, int offset) {
+        if (offset < 0 || offset + mEncodedLength > dest.length) {
+            throw new IndexOutOfBoundsException(
+                    "Not enough space to write. Required bytes: " + mEncodedLength);
+        }
+        write(dest, offset);
+    }
+
+    /** Writes the DER encoded bytes of this node into a new byte array. */
+    public byte[] toBytes() {
+        byte[] dest = new byte[mEncodedLength];
+        write(dest, 0);
+        return dest;
+    }
+
+    /** Gets a hex string representing the DER encoded bytes of this node. */
+    public String toHex() {
+        return IccUtils.bytesToHexString(toBytes());
+    }
+
+    /** Gets header (tag + length) as hex string. */
+    public String getHeadAsHex() {
+        String headHex = IccUtils.bytesToHexString(IccUtils.unsignedIntToBytes(mTag));
+        if (mDataLength <= 127) {
+            headHex += IccUtils.byteToHex((byte) mDataLength);
+        } else {
+            byte[] lenBytes = IccUtils.unsignedIntToBytes(mDataLength);
+            headHex += IccUtils.byteToHex((byte) (lenBytes.length | 0x80));
+            headHex += IccUtils.bytesToHexString(lenBytes);
+        }
+        return headHex;
+    }
+
+    /** Returns the new offset where to write the next node data. */
+    private int write(byte[] dest, int offset) {
+        // Writes the tag.
+        offset += IccUtils.unsignedIntToBytes(mTag, dest, offset);
+        // Writes the length.
+        if (mDataLength <= 127) {
+            dest[offset++] = (byte) mDataLength;
+        } else {
+            // Bytes required for encoding the length
+            int lenLen = IccUtils.unsignedIntToBytes(mDataLength, dest, ++offset);
+            dest[offset - 1] = (byte) (lenLen | 0x80);
+            offset += lenLen;
+        }
+        // Writes the data.
+        if (mConstructed && mDataBytes == null) {
+            int size = mChildren.size();
+            for (int i = 0; i < size; i++) {
+                Asn1Node child = mChildren.get(i);
+                offset = child.write(dest, offset);
+            }
+        } else if (mDataBytes != null) {
+            System.arraycopy(mDataBytes, mDataOffset, dest, offset, mDataLength);
+            offset += mDataLength;
+        }
+        return offset;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java b/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java
new file mode 100644
index 0000000..c151468
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java
@@ -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.
+ */
+
+package com.android.internal.telephony.uicc.asn1;
+
+/**
+ * Exception for invalid ASN.1 data in DER encoding which cannot be parsed as a node or a specific
+ * data type.
+ */
+public class InvalidAsn1DataException extends Exception {
+    private final int mTag;
+
+    public InvalidAsn1DataException(int tag, String message) {
+        super(message);
+        mTag = tag;
+    }
+
+    public InvalidAsn1DataException(int tag, String message, Throwable throwable) {
+        super(message, throwable);
+        mTag = tag;
+    }
+
+    /** @return The tag which has the invalid data. */
+    public int getTag() {
+        return mTag;
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + " (tag=" + mTag + ")";
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java b/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java
new file mode 100644
index 0000000..f79021e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.internal.telephony.uicc.asn1;
+
+/**
+ * Exception for getting a child of a {@link Asn1Node} with a non-existing tag.
+ */
+public class TagNotFoundException extends Exception {
+    private final int mTag;
+
+    public TagNotFoundException(int tag) {
+        mTag = tag;
+    }
+
+    /** @return The tag which has the invalid data. */
+    public int getTag() {
+        return mTag;
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + " (tag=" + mTag + ")";
+    }
+}
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index a761a07..7926a77 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -16,7 +16,12 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-android_test_mock_source_files := $(call all-java-files-under, src/android/test/mock)
+# Includes the main framework source to ensure that doclava has access to the
+# visibility information for the base classes of the mock classes. Without it
+# otherwise hidden methods could be visible.
+android_test_mock_source_files := \
+    $(call all-java-files-under, src/android/test/mock) \
+    $(call all-java-files-under, ../core/java) \
 
 # For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
 ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
@@ -67,6 +72,8 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
 android_test_mock_gen_stamp :=
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Archive a copy of the classes.jar in SDK build.
@@ -104,4 +111,91 @@
 	@echo Copying removed.txt
 	$(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE)
 
+# Generate the stub source files for android.test.mock.stubs-system
+# =================================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(android_test_mock_source_files)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/api.txt
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/removed.txt
+
+ANDROID_TEST_MOCK_SYSTEM_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-current.txt
+ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-removed.txt
+
+LOCAL_DROIDDOC_OPTIONS:= \
+    -stubpackages android.test.mock \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/src \
+    -nodocs \
+    -showAnnotation android.annotation.SystemApi \
+    -api $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) \
+    -removedApi $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) \
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-system-api-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_system_gen_stamp := $(full_target)
+
+# Add some additional dependencies
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE): $(full_target)
+
+# Build the android.test.mock.stubs-system library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.stubs-system
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+# Make sure to run droiddoc first to generate the stub source files.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_system_gen_stamp)
+android_test_mock_system_gen_stamp :=
+
+LOCAL_SDK_VERSION := system_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.stubs_system.jar)
+
+# Check that the android.test.mock.stubs-system library has not changed
+# =====================================================================
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+    check-android-test-mock-system-api-current, \
+    $(ANDROID_TEST_MOCK_SYSTEM_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE), \
+    -error 2 -error 3 -error 4 -error 5 -error 6 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+    -error 25 -error 26 -error 27, \
+    cat $(LOCAL_PATH)/api/apicheck_msg_android_test_mock-system.txt, \
+    check-android-test-mock-system-api, \
+    $(call doc-timestamp-for,android-test-mock-system-api-stubs-gen) \
+    ))
+
+.PHONY: check-android-test-mock-system-api
+checkapi: check-android-test-mock-system-api
+
+.PHONY: update-android-test-mock-system-api
+update-api: update-android-test-mock-system-api
+
+update-android-test-mock-system-api: $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) | $(ACP)
+	@echo Copying current.txt
+	$(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_API_FILE)
+	@echo Copying removed.txt
+	$(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE)
+
 endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 3aa350b..48a1f80 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -10,7 +10,6 @@
     ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
-    method public final android.content.IContentProvider getIContentProvider();
     method public java.lang.String getType(android.net.Uri);
     method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
     method public boolean onCreate();
@@ -22,37 +21,26 @@
   public class MockContentResolver extends android.content.ContentResolver {
     ctor public MockContentResolver();
     ctor public MockContentResolver(android.content.Context);
-    method protected android.content.IContentProvider acquireProvider(android.content.Context, java.lang.String);
-    method protected android.content.IContentProvider acquireUnstableProvider(android.content.Context, java.lang.String);
     method public void addProvider(java.lang.String, android.content.ContentProvider);
-    method public boolean releaseProvider(android.content.IContentProvider);
-    method public boolean releaseUnstableProvider(android.content.IContentProvider);
-    method public void unstableProviderDied(android.content.IContentProvider);
   }
 
   public class MockContext extends android.content.Context {
     ctor public MockContext();
     method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
-    method public boolean canLoadUnsafeResources();
     method public int checkCallingOrSelfPermission(java.lang.String);
     method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method public int checkCallingPermission(java.lang.String);
     method public int checkCallingUriPermission(android.net.Uri, int);
     method public int checkPermission(java.lang.String, int, int);
-    method public int checkPermission(java.lang.String, int, int, android.os.IBinder);
     method public int checkSelfPermission(java.lang.String);
     method public int checkUriPermission(android.net.Uri, int, int, int);
-    method public int checkUriPermission(android.net.Uri, int, int, int, android.os.IBinder);
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
-    method public android.content.Context createApplicationContext(android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
     method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Context createPackageContextAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] databaseList();
     method public boolean deleteDatabase(java.lang.String);
     method public boolean deleteFile(java.lang.String);
@@ -68,7 +56,6 @@
     method public android.content.Context getApplicationContext();
     method public android.content.pm.ApplicationInfo getApplicationInfo();
     method public android.content.res.AssetManager getAssets();
-    method public java.lang.String getBasePackageName();
     method public java.io.File getCacheDir();
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
@@ -76,8 +63,6 @@
     method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
-    method public android.view.Display getDisplay();
-    method public android.view.DisplayAdjustments getDisplayAdjustments(int);
     method public java.io.File getExternalCacheDir();
     method public java.io.File[] getExternalCacheDirs();
     method public java.io.File getExternalFilesDir(java.lang.String);
@@ -89,25 +74,19 @@
     method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
-    method public java.lang.String getOpPackageName();
     method public java.lang.String getPackageCodePath();
     method public android.content.pm.PackageManager getPackageManager();
     method public java.lang.String getPackageName();
     method public java.lang.String getPackageResourcePath();
-    method public java.io.File getPreloadsFileCache();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
-    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
-    method public java.io.File getSharedPreferencesPath(java.lang.String);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
-    method public int getUserId();
     method public android.graphics.drawable.Drawable getWallpaper();
     method public int getWallpaperDesiredMinimumHeight();
     method public int getWallpaperDesiredMinimumWidth();
     method public void grantUriPermission(java.lang.String, android.net.Uri, int);
-    method public boolean isCredentialProtectedStorage();
     method public boolean isDeviceProtectedStorage();
     method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
     method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
@@ -120,31 +99,19 @@
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, int);
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, int);
-    method public android.content.Intent registerReceiverAsUser(android.content.BroadcastReceiver, android.os.UserHandle, android.content.IntentFilter, java.lang.String, android.os.Handler);
-    method public void reloadSharedPreferences();
     method public void removeStickyBroadcast(android.content.Intent);
     method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
     method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
-    method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
-    method public void sendBroadcast(android.content.Intent, java.lang.String, int);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int);
-    method public void sendBroadcastMultiplePermissions(android.content.Intent, java.lang.String[]);
     method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
     method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendStickyBroadcast(android.content.Intent);
     method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.os.Bundle);
     method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void setTheme(int);
@@ -155,17 +122,13 @@
     method public void startActivity(android.content.Intent);
     method public void startActivity(android.content.Intent, android.os.Bundle);
     method public android.content.ComponentName startForegroundService(android.content.Intent);
-    method public android.content.ComponentName startForegroundServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public android.content.ComponentName startService(android.content.Intent);
-    method public android.content.ComponentName startServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public boolean stopService(android.content.Intent);
-    method public boolean stopServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
-    method public void updateDisplay(int);
   }
 
   public deprecated class MockCursor implements android.database.Cursor {
@@ -221,8 +184,6 @@
 
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
     ctor public MockPackageManager();
-    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void addPackageToPreferred(java.lang.String);
     method public boolean addPermission(android.content.pm.PermissionInfo);
     method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
@@ -232,19 +193,10 @@
     method public int checkPermission(java.lang.String, java.lang.String);
     method public int checkSignatures(java.lang.String, java.lang.String);
     method public int checkSignatures(int, int);
-    method public void clearApplicationUserData(java.lang.String, android.content.pm.IPackageDataObserver);
-    method public void clearCrossProfileIntentFilters(int);
     method public void clearInstantAppCookie();
     method public void clearPackagePreferredActivities(java.lang.String);
     method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
-    method public void deleteApplicationCacheFiles(java.lang.String, android.content.pm.IPackageDataObserver);
-    method public void deleteApplicationCacheFilesAsUser(java.lang.String, int, android.content.pm.IPackageDataObserver);
-    method public void deletePackage(java.lang.String, android.content.pm.IPackageDeleteObserver, int);
-    method public void deletePackageAsUser(java.lang.String, android.content.pm.IPackageDeleteObserver, int, int);
     method public void extendVerificationTimeout(int, int, long);
-    method public void flushPackageRestrictionsAsUser(int);
-    method public void freeStorage(java.lang.String, long, android.content.IntentSender);
-    method public void freeStorageAndNotify(java.lang.String, long, android.content.pm.IPackageDataObserver);
     method public android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -257,148 +209,75 @@
     method public android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int getApplicationEnabledSetting(java.lang.String);
-    method public boolean getApplicationHiddenSettingAsUser(java.lang.String, android.os.UserHandle);
     method public android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationIcon(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ChangedPackages getChangedPackages(int);
     method public int getComponentEnabledSetting(android.content.ComponentName);
     method public android.graphics.drawable.Drawable getDefaultActivityIcon();
-    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
     method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public android.content.ComponentName getHomeActivities(java.util.List<android.content.pm.ResolveInfo>);
-    method public int getInstallReason(java.lang.String, android.os.UserHandle);
     method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
     method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method public java.lang.String getInstallerPackageName(java.lang.String);
-    method public java.lang.String getInstantAppAndroidId(java.lang.String, android.os.UserHandle);
     method public byte[] getInstantAppCookie();
     method public int getInstantAppCookieMaxBytes();
-    method public int getInstantAppCookieMaxSize();
-    method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
-    method public int getIntentVerificationStatusAsUser(java.lang.String, int);
-    method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
     method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
-    method public int getMoveStatus(int);
     method public java.lang.String getNameForUid(int);
-    method public java.lang.String[] getNamesForUids(int[]);
-    method public java.util.List<android.os.storage.VolumeInfo> getPackageCandidateVolumes(android.content.pm.ApplicationInfo);
-    method public android.os.storage.VolumeInfo getPackageCurrentVolume(android.content.pm.ApplicationInfo);
     method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInfo getPackageInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInstaller getPackageInstaller();
-    method public void getPackageSizeInfoAsUser(java.lang.String, int, android.content.pm.IPackageStatsObserver);
     method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getPackageUidAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getPackageUidAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] getPackagesForUid(int);
     method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
-    method public java.lang.String getPermissionControllerPackageName();
-    method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
     method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
     method public java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
-    method public java.util.List<android.os.storage.VolumeInfo> getPrimaryStorageCandidateVolumes();
-    method public android.os.storage.VolumeInfo getPrimaryStorageCurrentVolume();
     method public android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
     method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.res.Resources getResourcesForApplicationAsUser(java.lang.String, int);
     method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.lang.String getServicesSystemSharedLibraryPackageName();
     method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
-    method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibrariesAsUser(int, int);
-    method public java.lang.String getSharedSystemSharedLibraryPackageName();
-    method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
     method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public java.lang.String[] getSystemSharedLibraryNames();
     method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public int getUidForSharedUser(java.lang.String);
-    method public android.graphics.drawable.Drawable getUserBadgeForDensity(android.os.UserHandle, int);
-    method public android.graphics.drawable.Drawable getUserBadgeForDensityNoBackground(android.os.UserHandle, int);
     method public android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
     method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
     method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
-    method public android.content.pm.VerifierDeviceIdentity getVerifierDeviceIdentity();
     method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public boolean hasSystemFeature(java.lang.String);
     method public boolean hasSystemFeature(java.lang.String, int);
-    method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackageAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void installPackage(android.net.Uri, android.app.PackageInstallObserver, int, java.lang.String);
     method public boolean isInstantApp();
     method public boolean isInstantApp(java.lang.String);
-    method public boolean isPackageAvailable(java.lang.String);
-    method public boolean isPackageSuspendedForUser(java.lang.String, int);
-    method public boolean isPermissionReviewModeEnabled();
     method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
     method public boolean isSafeMode();
-    method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
-    method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
-    method public boolean isUpgrade();
-    method public android.graphics.drawable.Drawable loadItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable loadUnbadgedItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
-    method public int movePackage(java.lang.String, android.os.storage.VolumeInfo);
-    method public int movePrimaryStorage(android.os.storage.VolumeInfo);
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
     method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void registerMoveCallback(android.content.pm.PackageManager.MoveCallback, android.os.Handler);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void removePackageFromPreferred(java.lang.String);
     method public void removePermission(java.lang.String);
-    method public void replacePreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
     method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
-    method public android.content.pm.ResolveInfo resolveActivityAsUser(android.content.Intent, int, int);
     method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
-    method public android.content.pm.ProviderInfo resolveContentProviderAsUser(java.lang.String, int, int);
     method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
-    method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public void setApplicationCategoryHint(java.lang.String, int);
     method public void setApplicationEnabledSetting(java.lang.String, int, int);
-    method public boolean setApplicationHiddenSettingAsUser(java.lang.String, boolean, android.os.UserHandle);
     method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
-    method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
     method public void setInstallerPackageName(java.lang.String, java.lang.String);
-    method public boolean setInstantAppCookie(byte[]);
-    method public java.lang.String[] setPackagesSuspendedAsUser(java.lang.String[], boolean, int);
-    method public void setUpdateAvailable(java.lang.String, boolean);
-    method public boolean shouldShowRequestPermissionRationale(java.lang.String);
-    method public void unregisterMoveCallback(android.content.pm.PackageManager.MoveCallback);
     method public void updateInstantAppCookie(byte[]);
-    method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
-    method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
     method public void verifyPendingInstall(int, int);
   }
 
diff --git a/test-mock/api/android-test-mock-removed.txt b/test-mock/api/android-test-mock-removed.txt
index 5b358cf..bd109a8 100644
--- a/test-mock/api/android-test-mock-removed.txt
+++ b/test-mock/api/android-test-mock-removed.txt
@@ -8,6 +8,7 @@
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
     method public deprecated java.lang.String getDefaultBrowserPackageName(int);
     method public deprecated boolean setDefaultBrowserPackageName(java.lang.String, int);
+    method public boolean setInstantAppCookie(byte[]);
   }
 
 }
diff --git a/test-mock/api/android-test-mock-system-current.txt b/test-mock/api/android-test-mock-system-current.txt
new file mode 100644
index 0000000..20401a5
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-current.txt
@@ -0,0 +1,38 @@
+package android.test.mock {
+
+  public class MockContext extends android.content.Context {
+    method public android.content.Context createCredentialProtectedStorageContext();
+    method public java.io.File getPreloadsFileCache();
+    method public boolean isCredentialProtectedStorage();
+    method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
+    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
+    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+  }
+
+  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
+    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
+    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+    method public android.content.ComponentName getInstantAppInstallerComponent();
+    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
+    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+    method public int getIntentVerificationStatusAsUser(java.lang.String, int);
+    method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
+    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+    method public void setUpdateAvailable(java.lang.String, boolean);
+    method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
+    method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
+    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+  }
+
+}
+
diff --git a/test-mock/api/android-test-mock-system-removed.txt b/test-mock/api/android-test-mock-system-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-removed.txt
diff --git a/test-mock/api/apicheck_msg_android_test_mock-system.txt b/test-mock/api/apicheck_msg_android_test_mock-system.txt
new file mode 100644
index 0000000..3a97117
--- /dev/null
+++ b/test-mock/api/apicheck_msg_android_test_mock-system.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+      errors above.
+
+   2) You can update android-test-mock-current.txt by executing the following command:
+         make update-android-test-mock-system-api
+
+      To submit the revised android-test-mock-system-current.txt to the main Android repository,
+      you will need approval.
+******************************
+
+
+
diff --git a/tests/AppLaunch/Android.mk b/tests/AppLaunch/Android.mk
index 09739e5..917293f 100644
--- a/tests/AppLaunch/Android.mk
+++ b/tests/AppLaunch/Android.mk
@@ -13,6 +13,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
 include $(BUILD_PACKAGE)
 
 # Use the following include to make our test apk.
diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk
index 73181ec..b69e3e4 100644
--- a/tests/Internal/Android.mk
+++ b/tests/Internal/Android.mk
@@ -14,6 +14,7 @@
     android-support-test \
     mockito-target-minus-junit4
 
+LOCAL_JAVA_RESOURCE_DIRS := res
 LOCAL_CERTIFICATE := platform
 
 LOCAL_PACKAGE_NAME := InternalTests
diff --git a/tests/Internal/AndroidManifest.xml b/tests/Internal/AndroidManifest.xml
index a2c95fb..e5a5694 100644
--- a/tests/Internal/AndroidManifest.xml
+++ b/tests/Internal/AndroidManifest.xml
@@ -18,8 +18,24 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.internal.tests">
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.BIND_WALLPAPER" />
     <application>
         <uses-library android:name="android.test.runner" />
+
+        <service android:name="stub.DummyWallpaperService"
+                 android:enabled="true"
+                 android:directBootAware="true"
+                 android:label="Dummy wallpaper"
+                 android:permission="android.permission.BIND_WALLPAPER">
+
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+            </intent-filter>
+
+            <!-- Link to XML that defines the wallpaper info. -->
+            <meta-data android:name="android.service.wallpaper"
+                       android:resource="@xml/livewallpaper" />
+        </service>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/Internal/res/xml/livewallpaper.xml b/tests/Internal/res/xml/livewallpaper.xml
new file mode 100644
index 0000000..dbb0e47
--- /dev/null
+++ b/tests/Internal/res/xml/livewallpaper.xml
@@ -0,0 +1,20 @@
+<?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
+  -->
+<wallpaper
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    androidprv:supportsAmbientMode="true"/>
\ No newline at end of file
diff --git a/tests/Internal/src/android/app/WallpaperInfoTest.java b/tests/Internal/src/android/app/WallpaperInfoTest.java
new file mode 100644
index 0000000..9d26270
--- /dev/null
+++ b/tests/Internal/src/android/app/WallpaperInfoTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Parcel;
+import android.service.wallpaper.WallpaperService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Tests for hidden WallpaperInfo methods.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WallpaperInfoTest {
+
+    @Test
+    public void testSupportsAmbientMode() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+
+        Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        intent.setPackage("com.android.internal.tests");
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+        assertEquals(1, result.size());
+        ResolveInfo info = result.get(0);
+        WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info);
+
+        // Defined as true in the XML
+        assertTrue("supportsAmbientMode should be true, as defined in the XML.",
+                wallpaperInfo.getSupportsAmbientMode());
+        Parcel parcel = Parcel.obtain();
+        wallpaperInfo.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+        WallpaperInfo fromParcel = WallpaperInfo.CREATOR.createFromParcel(parcel);
+        assertTrue("supportsAmbientMode should have been restored from parcelable",
+                fromParcel.getSupportsAmbientMode());
+        parcel.recycle();
+    }
+}
+
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
new file mode 100644
index 0000000..f7ce2c7
--- /dev/null
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.service.wallpaper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class WallpaperServiceTest {
+
+    @Test
+    public void testDeliversAmbientModeChanged() {
+        int[] ambientModeChangedCount = {0};
+        WallpaperService service = new WallpaperService() {
+            @Override
+            public Engine onCreateEngine() {
+                return new Engine() {
+                    @Override
+                    public void onAmbientModeChanged(boolean inAmbientMode) {
+                        ambientModeChangedCount[0]++;
+                    }
+                };
+            }
+        };
+        WallpaperService.Engine engine = service.onCreateEngine();
+        engine.setCreated(true);
+
+        engine.doAmbientModeChanged(false);
+        assertFalse("ambient mode should be false", engine.isInAmbientMode());
+        assertEquals("onAmbientModeChanged should have been called",
+                ambientModeChangedCount[0], 1);
+
+        engine.doAmbientModeChanged(true);
+        assertTrue("ambient mode should be false", engine.isInAmbientMode());
+        assertEquals("onAmbientModeChanged should have been called",
+                ambientModeChangedCount[0], 2);
+    }
+
+}
diff --git a/tests/Internal/src/stub/DummyWallpaperService.java b/tests/Internal/src/stub/DummyWallpaperService.java
new file mode 100644
index 0000000..084c036
--- /dev/null
+++ b/tests/Internal/src/stub/DummyWallpaperService.java
@@ -0,0 +1,29 @@
+/*
+ * 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 stub;
+
+import android.service.wallpaper.WallpaperService;
+
+/**
+ * Dummy wallpaper service only for test purposes, won't draw anything.
+ */
+public class DummyWallpaperService extends WallpaperService {
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+}
diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644
index 0000000..12568a09
--- /dev/null
+++ b/tests/JankBench/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_USE_AAPT2 := true
+
+# omit gradle 'build' dir
+LOCAL_SRC_FILES := $(call all-java-files-under,app/src/main/java)
+
+# use appcompat/support lib from the tree, so improvements/
+# regressions are reflected in test data
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/app/src/main/res \
+
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-design \
+    android-support-v4 \
+    android-support-v7-appcompat \
+    android-support-v7-cardview \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    apache-commons-math \
+    junit
+
+
+LOCAL_PACKAGE_NAME := JankBench
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
new file mode 100644
index 0000000..79aff90
--- /dev/null
+++ b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.benchmark;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+    public ApplicationTest() {
+        super(Application.class);
+    }
+}
diff --git a/tests/JankBench/app/src/main/AndroidManifest.xml b/tests/JankBench/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..58aa66f
--- /dev/null
+++ b/tests/JankBench/app/src/main/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.benchmark">
+
+    <uses-sdk android:minSdkVersion="24" />
+
+    <android:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".app.HomeActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".app.RunLocalBenchmarksActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.benchmark.ACTION_BENCHMARK" />
+            </intent-filter>
+
+            <meta-data
+                android:name="com.android.benchmark.benchmark_group"
+                android:resource="@xml/benchmark" />
+        </activity>
+        <activity android:name=".ui.ListViewScrollActivity" />
+        <activity android:name=".ui.ImageListViewScrollActivity" />
+        <activity android:name=".ui.ShadowGridActivity" />
+        <activity android:name=".ui.TextScrollActivity" />
+        <activity android:name=".ui.EditTextInputActivity" />
+        <activity android:name=".synthetic.MemoryActivity" />
+        <activity android:name=".ui.FullScreenOverdrawActivity"></activity>
+        <activity android:name=".ui.BitmapUploadActivity"></activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
new file mode 100644
index 0000000..b0a97ae0
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
@@ -0,0 +1,40 @@
+/*
+ * 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.benchmark.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.benchmark.R;
+
+/**
+ * Fragment for the Benchmark dashboard
+ */
+public class BenchmarkDashboardFragment extends Fragment {
+
+    public BenchmarkDashboardFragment() {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_dashboard, container, false);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
new file mode 100644
index 0000000..7419b30
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
@@ -0,0 +1,126 @@
+/*
+ * 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.benchmark.app;
+
+import android.graphics.Typeface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+
+/**
+ *
+ */
+public class BenchmarkListAdapter extends BaseExpandableListAdapter {
+
+    private final LayoutInflater mInflater;
+    private final BenchmarkRegistry mRegistry;
+
+    BenchmarkListAdapter(LayoutInflater inflater,
+                         BenchmarkRegistry registry) {
+        mInflater = inflater;
+        mRegistry = registry;
+    }
+
+    @Override
+    public int getGroupCount() {
+        return mRegistry.getGroupCount();
+    }
+
+    @Override
+    public int getChildrenCount(int groupPosition) {
+        return mRegistry.getBenchmarkCount(groupPosition);
+    }
+
+    @Override
+    public Object getGroup(int groupPosition) {
+        return mRegistry.getBenchmarkGroup(groupPosition);
+    }
+
+    @Override
+    public Object getChild(int groupPosition, int childPosition) {
+        BenchmarkGroup benchmarkGroup = mRegistry.getBenchmarkGroup(groupPosition);
+
+        if (benchmarkGroup != null) {
+           return benchmarkGroup.getBenchmarks()[childPosition];
+        }
+
+        return null;
+    }
+
+    @Override
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    @Override
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    @Override
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+        BenchmarkGroup group = (BenchmarkGroup) getGroup(groupPosition);
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.benchmark_list_group_row, null);
+        }
+
+        TextView title = (TextView) convertView.findViewById(R.id.group_name);
+        title.setTypeface(null, Typeface.BOLD);
+        title.setText(group.getTitle());
+        return convertView;
+    }
+
+    @Override
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+                             View convertView, ViewGroup parent) {
+        BenchmarkGroup.Benchmark benchmark =
+                (BenchmarkGroup.Benchmark) getChild(groupPosition, childPosition);
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.benchmark_list_item, null);
+        }
+
+        TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+        name.setText(benchmark.getName());
+        CheckBox enabledBox = (CheckBox) convertView.findViewById(R.id.benchmark_enable_checkbox);
+        enabledBox.setOnClickListener(benchmark);
+        enabledBox.setChecked(benchmark.isEnabled());
+
+        return convertView;
+    }
+
+    @Override
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public int getChildrenHeight() {
+        // TODO
+        return 1024;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
new file mode 100644
index 0000000..79bafd6
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -0,0 +1,150 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.Toast;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+import com.android.benchmark.results.GlobalResultsStore;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class HomeActivity extends AppCompatActivity implements Button.OnClickListener {
+
+    private FloatingActionButton mStartButton;
+    private BenchmarkRegistry mRegistry;
+    private Queue<Intent> mRunnableBenchmarks;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_home);
+
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        mStartButton = (FloatingActionButton) findViewById(R.id.start_button);
+        mStartButton.setActivated(true);
+        mStartButton.setOnClickListener(this);
+
+        mRegistry = new BenchmarkRegistry(this);
+
+        mRunnableBenchmarks = new LinkedList<>();
+
+        ExpandableListView listView = (ExpandableListView) findViewById(R.id.test_list);
+        BenchmarkListAdapter adapter =
+                new BenchmarkListAdapter(LayoutInflater.from(this), mRegistry);
+        listView.setAdapter(adapter);
+
+        adapter.notifyDataSetChanged();
+        ViewGroup.LayoutParams layoutParams = listView.getLayoutParams();
+        layoutParams.height = 2048;
+        listView.setLayoutParams(layoutParams);
+        listView.requestLayout();
+        System.out.println(System.getProperties().stringPropertyNames());
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... voids) {
+                    try {
+                        HomeActivity.this.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                Toast.makeText(HomeActivity.this, "Exporting...", Toast.LENGTH_LONG).show();
+                            }
+                        });
+                        GlobalResultsStore.getInstance(HomeActivity.this).exportToCsv();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void aVoid) {
+                    HomeActivity.this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(HomeActivity.this, "Done", Toast.LENGTH_LONG).show();
+                        }
+                    });
+                }
+            }.execute();
+
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onClick(View v) {
+        final int groupCount = mRegistry.getGroupCount();
+        for (int i = 0; i < groupCount; i++) {
+
+            Intent intent = mRegistry.getBenchmarkGroup(i).getIntent();
+            if (intent != null) {
+                mRunnableBenchmarks.add(intent);
+            }
+        }
+
+        handleNextBenchmark();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+    }
+
+    private void handleNextBenchmark() {
+        Intent nextIntent = mRunnableBenchmarks.peek();
+        startActivityForResult(nextIntent, 0);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
new file mode 100644
index 0000000..1c82d6d
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
@@ -0,0 +1,224 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.benchmark.R;
+
+
+/**
+ * TODO: document your custom view class.
+ */
+public class PerfTimeline extends View {
+    private String mExampleString; // TODO: use a default from R.string...
+    private int mExampleColor = Color.RED; // TODO: use a default from R.color...
+    private float mExampleDimension = 300; // TODO: use a default from R.dimen...
+
+    private TextPaint mTextPaint;
+    private float mTextWidth;
+    private float mTextHeight;
+
+    private Paint mPaintBaseLow;
+    private Paint mPaintBaseHigh;
+    private Paint mPaintValue;
+
+
+    public float[] mLinesLow;
+    public float[] mLinesHigh;
+    public float[] mLinesValue;
+
+    public PerfTimeline(Context context) {
+        super(context);
+        init(null, 0);
+    }
+
+    public PerfTimeline(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs, 0);
+    }
+
+    public PerfTimeline(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(attrs, defStyle);
+    }
+
+    private void init(AttributeSet attrs, int defStyle) {
+        // Load attributes
+        final TypedArray a = getContext().obtainStyledAttributes(
+                attrs, R.styleable.PerfTimeline, defStyle, 0);
+
+        mExampleString = "xx";//a.getString(R.styleable.PerfTimeline_exampleString, "xx");
+        mExampleColor = a.getColor(R.styleable.PerfTimeline_exampleColor, mExampleColor);
+        // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
+        // values that should fall on pixel boundaries.
+        mExampleDimension = a.getDimension(
+                R.styleable.PerfTimeline_exampleDimension,
+                mExampleDimension);
+
+        a.recycle();
+
+        // Set up a default TextPaint object
+        mTextPaint = new TextPaint();
+        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+        mTextPaint.setTextAlign(Paint.Align.LEFT);
+
+        // Update TextPaint and text measurements from attributes
+        invalidateTextPaintAndMeasurements();
+
+        mPaintBaseLow = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintBaseLow.setStyle(Paint.Style.FILL);
+        mPaintBaseLow.setColor(0xff000000);
+
+        mPaintBaseHigh = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintBaseHigh.setStyle(Paint.Style.FILL);
+        mPaintBaseHigh.setColor(0x7f7f7f7f);
+
+        mPaintValue = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintValue.setStyle(Paint.Style.FILL);
+        mPaintValue.setColor(0x7fff0000);
+
+    }
+
+    private void invalidateTextPaintAndMeasurements() {
+        mTextPaint.setTextSize(mExampleDimension);
+        mTextPaint.setColor(mExampleColor);
+        mTextWidth = mTextPaint.measureText(mExampleString);
+
+        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+        mTextHeight = fontMetrics.bottom;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // TODO: consider storing these as member variables to reduce
+        // allocations per draw cycle.
+        int paddingLeft = getPaddingLeft();
+        int paddingTop = getPaddingTop();
+        int paddingRight = getPaddingRight();
+        int paddingBottom = getPaddingBottom();
+
+        int contentWidth = getWidth() - paddingLeft - paddingRight;
+        int contentHeight = getHeight() - paddingTop - paddingBottom;
+
+        // Draw the text.
+        //canvas.drawText(mExampleString,
+        //        paddingLeft + (contentWidth - mTextWidth) / 2,
+        //        paddingTop + (contentHeight + mTextHeight) / 2,
+        //        mTextPaint);
+
+
+
+
+        // Draw the shadow
+        //RectF rf = new RectF(10.f, 10.f, 100.f, 100.f);
+        //canvas.drawOval(rf, mShadowPaint);
+
+        if (mLinesLow != null) {
+            canvas.drawLines(mLinesLow, mPaintBaseLow);
+        }
+        if (mLinesHigh != null) {
+            canvas.drawLines(mLinesHigh, mPaintBaseHigh);
+        }
+        if (mLinesValue != null) {
+            canvas.drawLines(mLinesValue, mPaintValue);
+        }
+
+
+/*
+        // Draw the pie slices
+        for (int i = 0; i < mData.size(); ++i) {
+            Item it = mData.get(i);
+            mPiePaint.setShader(it.mShader);
+            canvas.drawArc(mBounds,
+                    360 - it.mEndAngle,
+                    it.mEndAngle - it.mStartAngle,
+                    true, mPiePaint);
+        }
+*/
+        // Draw the pointer
+        //canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
+        //canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
+    }
+
+    /**
+     * Gets the example string attribute value.
+     *
+     * @return The example string attribute value.
+     */
+    public String getExampleString() {
+        return mExampleString;
+    }
+
+    /**
+     * Sets the view's example string attribute value. In the example view, this string
+     * is the text to draw.
+     *
+     * @param exampleString The example string attribute value to use.
+     */
+    public void setExampleString(String exampleString) {
+        mExampleString = exampleString;
+        invalidateTextPaintAndMeasurements();
+    }
+
+    /**
+     * Gets the example color attribute value.
+     *
+     * @return The example color attribute value.
+     */
+    public int getExampleColor() {
+        return mExampleColor;
+    }
+
+    /**
+     * Sets the view's example color attribute value. In the example view, this color
+     * is the font color.
+     *
+     * @param exampleColor The example color attribute value to use.
+     */
+    public void setExampleColor(int exampleColor) {
+        mExampleColor = exampleColor;
+        invalidateTextPaintAndMeasurements();
+    }
+
+    /**
+     * Gets the example dimension attribute value.
+     *
+     * @return The example dimension attribute value.
+     */
+    public float getExampleDimension() {
+        return mExampleDimension;
+    }
+
+    /**
+     * Sets the view's example dimension attribute value. In the example view, this dimension
+     * is the font size.
+     *
+     * @param exampleDimension The example dimension attribute value to use.
+     */
+    public void setExampleDimension(float exampleDimension) {
+        mExampleDimension = exampleDimension;
+        invalidateTextPaintAndMeasurements();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
new file mode 100644
index 0000000..7641d00
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -0,0 +1,415 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+import com.android.benchmark.synthetic.MemoryActivity;
+import com.android.benchmark.ui.BitmapUploadActivity;
+import com.android.benchmark.ui.EditTextInputActivity;
+import com.android.benchmark.ui.FullScreenOverdrawActivity;
+import com.android.benchmark.ui.ImageListViewScrollActivity;
+import com.android.benchmark.ui.ListViewScrollActivity;
+import com.android.benchmark.ui.ShadowGridActivity;
+import com.android.benchmark.ui.TextScrollActivity;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class RunLocalBenchmarksActivity extends AppCompatActivity {
+
+    public static final int RUN_COUNT = 5;
+
+    private ArrayList<LocalBenchmark> mBenchmarksToRun;
+    private int mBenchmarkCursor;
+    private int mCurrentRunId;
+    private boolean mFinish;
+
+    private Handler mHandler = new Handler();
+
+    private static final int[] ALL_TESTS = new int[] {
+            R.id.benchmark_list_view_scroll,
+            R.id.benchmark_image_list_view_scroll,
+            R.id.benchmark_shadow_grid,
+            R.id.benchmark_text_high_hitrate,
+            R.id.benchmark_text_low_hitrate,
+            R.id.benchmark_edit_text_input,
+            R.id.benchmark_overdraw,
+    };
+
+    public static class LocalBenchmarksList extends ListFragment {
+        private ArrayList<LocalBenchmark> mBenchmarks;
+        private int mRunId;
+
+        public void setBenchmarks(ArrayList<LocalBenchmark> benchmarks) {
+            mBenchmarks = benchmarks;
+        }
+
+        public void setRunId(int id) {
+            mRunId = id;
+        }
+
+        @Override
+        public void onListItemClick(ListView l, View v, int position, long id) {
+            if (getActivity().findViewById(R.id.list_fragment_container) != null) {
+                FragmentManager fm = getActivity().getSupportFragmentManager();
+                UiResultsFragment resultsView = new UiResultsFragment();
+                String testName = BenchmarkRegistry.getBenchmarkName(v.getContext(),
+                        mBenchmarks.get(position).id);
+                resultsView.setRunInfo(testName, mRunId);
+                FragmentTransaction fragmentTransaction = fm.beginTransaction();
+                fragmentTransaction.replace(R.id.list_fragment_container, resultsView);
+                fragmentTransaction.addToBackStack(null);
+                fragmentTransaction.commit();
+            }
+        }
+    }
+
+
+    private class LocalBenchmark {
+        int id;
+        int runCount = 0;
+        int totalCount = 0;
+        ArrayList<String> mResultsUri = new ArrayList<>();
+
+        LocalBenchmark(int id, int runCount) {
+            this.id = id;
+            this.runCount = 0;
+            this.totalCount = runCount;
+        }
+
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_running_list);
+
+        initLocalBenchmarks(getIntent());
+
+        if (findViewById(R.id.list_fragment_container) != null) {
+            FragmentManager fm = getSupportFragmentManager();
+            LocalBenchmarksList listView = new LocalBenchmarksList();
+            listView.setListAdapter(new LocalBenchmarksListAdapter(LayoutInflater.from(this)));
+            listView.setBenchmarks(mBenchmarksToRun);
+            listView.setRunId(mCurrentRunId);
+            fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+        }
+
+        TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+        scoreView.setText("Running tests!");
+    }
+
+    private int translateBenchmarkIndex(int index) {
+        if (index >= 0 && index < ALL_TESTS.length) {
+            return ALL_TESTS[index];
+        }
+
+        return -1;
+    }
+
+    private void initLocalBenchmarks(Intent intent) {
+        mBenchmarksToRun = new ArrayList<>();
+        int[] enabledIds = intent.getIntArrayExtra(BenchmarkGroup.BENCHMARK_EXTRA_ENABLED_TESTS);
+        int runCount = intent.getIntExtra(BenchmarkGroup.BENCHMARK_EXTRA_RUN_COUNT, RUN_COUNT);
+        mFinish = intent.getBooleanExtra(BenchmarkGroup.BENCHMARK_EXTRA_FINISH, false);
+
+        if (enabledIds == null) {
+            // run all tests
+            enabledIds = ALL_TESTS;
+        }
+
+        StringBuilder idString = new StringBuilder();
+        idString.append(runCount);
+        idString.append(System.currentTimeMillis());
+
+        for (int i = 0; i < enabledIds.length; i++) {
+            int id = enabledIds[i];
+            System.out.println("considering " + id);
+            if (!isValidBenchmark(id)) {
+                System.out.println("not valid " + id);
+                id = translateBenchmarkIndex(id);
+                System.out.println("got out " + id);
+                System.out.println("expected: " + R.id.benchmark_overdraw);
+            }
+
+            if (isValidBenchmark(id)) {
+                int localRunCount = runCount;
+                if (isCompute(id)) {
+                    localRunCount = 1;
+                }
+                mBenchmarksToRun.add(new LocalBenchmark(id, localRunCount));
+                idString.append(id);
+            }
+        }
+
+        mBenchmarkCursor = 0;
+        mCurrentRunId = idString.toString().hashCode();
+    }
+
+    private boolean isCompute(int id) {
+        switch (id) {
+            case R.id.benchmark_cpu_gflops:
+            case R.id.benchmark_cpu_heat_soak:
+            case R.id.benchmark_memory_bandwidth:
+            case R.id.benchmark_memory_latency:
+            case R.id.benchmark_power_management:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isValidBenchmark(int benchmarkId) {
+        switch (benchmarkId) {
+            case R.id.benchmark_list_view_scroll:
+            case R.id.benchmark_image_list_view_scroll:
+            case R.id.benchmark_shadow_grid:
+            case R.id.benchmark_text_high_hitrate:
+            case R.id.benchmark_text_low_hitrate:
+            case R.id.benchmark_edit_text_input:
+            case R.id.benchmark_overdraw:
+            case R.id.benchmark_memory_bandwidth:
+            case R.id.benchmark_memory_latency:
+            case R.id.benchmark_power_management:
+            case R.id.benchmark_cpu_heat_soak:
+            case R.id.benchmark_cpu_gflops:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                runNextBenchmark();
+            }
+        }, 1000);
+    }
+
+    private void computeOverallScore() {
+        final TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+        scoreView.setText("Computing score...");
+        new AsyncTask<Void, Void, Integer>()  {
+            @Override
+            protected Integer doInBackground(Void... voids) {
+                GlobalResultsStore gsr =
+                        GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this);
+                ArrayList<Double> testLevelScores = new ArrayList<>();
+                final SummaryStatistics stats = new SummaryStatistics();
+                for (LocalBenchmark b : mBenchmarksToRun) {
+                    HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+                            gsr.loadDetailedResults(mCurrentRunId);
+                    for (ArrayList<UiBenchmarkResult> testResult : detailedResults.values()) {
+                        for (UiBenchmarkResult res : testResult) {
+                            int score = res.getScore();
+                            if (score == 0) {
+                                score = 1;
+                            }
+                            stats.addValue(score);
+                        }
+
+                        testLevelScores.add(stats.getGeometricMean());
+                        stats.clear();
+                    }
+
+                }
+
+                for (double score : testLevelScores) {
+                    stats.addValue(score);
+                }
+
+                return (int)Math.round(stats.getGeometricMean());
+            }
+
+            @Override
+            protected void onPostExecute(Integer score) {
+                TextView view = (TextView)
+                        RunLocalBenchmarksActivity.this.findViewById(R.id.score_text_view);
+                view.setText("Score: " + score);
+            }
+        }.execute();
+    }
+
+    private void runNextBenchmark() {
+        LocalBenchmark benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+        boolean runAgain = false;
+
+        if (benchmark.runCount < benchmark.totalCount) {
+            runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount++);
+        } else if (mBenchmarkCursor + 1 < mBenchmarksToRun.size()) {
+            mBenchmarkCursor++;
+            benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+            runBenchmarkForId(benchmark.id, benchmark.runCount++);
+        } else if (runAgain) {
+            mBenchmarkCursor = 0;
+            initLocalBenchmarks(getIntent());
+
+            runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount);
+        } else if (mFinish) {
+            finish();
+        } else {
+            Log.i("BENCH", "BenchmarkDone!");
+            computeOverallScore();
+        }
+    }
+
+    private void runBenchmarkForId(int id, int iteration) {
+        Intent intent;
+        int syntheticTestId = -1;
+
+        System.out.println("iteration: " + iteration);
+
+        switch (id) {
+            case R.id.benchmark_list_view_scroll:
+                intent = new Intent(getApplicationContext(), ListViewScrollActivity.class);
+                break;
+            case R.id.benchmark_image_list_view_scroll:
+                intent = new Intent(getApplicationContext(), ImageListViewScrollActivity.class);
+                break;
+            case R.id.benchmark_shadow_grid:
+                intent = new Intent(getApplicationContext(), ShadowGridActivity.class);
+                break;
+            case R.id.benchmark_text_high_hitrate:
+                intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+                intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 80);
+                intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+                break;
+            case R.id.benchmark_text_low_hitrate:
+                intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+                intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 20);
+                intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+                break;
+            case R.id.benchmark_edit_text_input:
+                intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
+                break;
+            case R.id.benchmark_overdraw:
+                intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
+                break;
+            case R.id.benchmark_memory_bandwidth:
+                syntheticTestId = 0;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_memory_latency:
+                syntheticTestId = 1;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_power_management:
+                syntheticTestId = 2;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_cpu_heat_soak:
+                syntheticTestId = 3;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_cpu_gflops:
+                syntheticTestId = 4;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+
+            default:
+               intent = null;
+        }
+
+        if (intent != null) {
+            intent.putExtra("com.android.benchmark.RUN_ID", mCurrentRunId);
+            intent.putExtra("com.android.benchmark.ITERATION", iteration);
+            startActivityForResult(intent, id & 0xffff, null);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case R.id.benchmark_shadow_grid:
+            case R.id.benchmark_list_view_scroll:
+            case R.id.benchmark_image_list_view_scroll:
+            case R.id.benchmark_text_high_hitrate:
+            case R.id.benchmark_text_low_hitrate:
+            case R.id.benchmark_edit_text_input:
+                break;
+            default:
+        }
+    }
+
+    class LocalBenchmarksListAdapter extends BaseAdapter {
+
+        private final LayoutInflater mInflater;
+
+        LocalBenchmarksListAdapter(LayoutInflater inflater) {
+            mInflater = inflater;
+        }
+
+        @Override
+        public int getCount() {
+            return mBenchmarksToRun.size();
+        }
+
+        @Override
+        public Object getItem(int i) {
+            return mBenchmarksToRun.get(i);
+        }
+
+        @Override
+        public long getItemId(int i) {
+            return mBenchmarksToRun.get(i).id;
+        }
+
+        @Override
+        public View getView(int i, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.running_benchmark_list_item, null);
+            }
+
+            TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+            name.setText(BenchmarkRegistry.getBenchmarkName(
+                    RunLocalBenchmarksActivity.this, mBenchmarksToRun.get(i).id));
+            return convertView;
+        }
+
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
new file mode 100644
index 0000000..56e94d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
@@ -0,0 +1,167 @@
+/*
+ * 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.benchmark.app;
+
+import android.annotation.TargetApi;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ListFragment;
+import android.util.Log;
+import android.view.FrameMetrics;
+import android.widget.SimpleAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@TargetApi(24)
+public class UiResultsFragment extends ListFragment {
+    private static final String TAG = "UiResultsFragment";
+    private static final int NUM_FIELDS = 20;
+
+    private ArrayList<UiBenchmarkResult> mResults = new ArrayList<>();
+
+    private AsyncTask<Void, Void, ArrayList<Map<String, String>>> mLoadScoresTask =
+            new AsyncTask<Void, Void, ArrayList<Map<String, String>>>() {
+        @Override
+        protected ArrayList<Map<String, String>> doInBackground(Void... voids) {
+            String[] data;
+            if (mResults.size() == 0 || mResults.get(0) == null) {
+                data = new String[] {
+                        "No metrics reported", ""
+                };
+            } else {
+                data = new String[NUM_FIELDS * (1 + mResults.size()) + 2];
+                SummaryStatistics stats = new SummaryStatistics();
+                int totalFrameCount = 0;
+                double totalAvgFrameDuration = 0;
+                double total99FrameDuration = 0;
+                double total95FrameDuration = 0;
+                double total90FrameDuration = 0;
+                double totalLongestFrame = 0;
+                double totalShortestFrame = 0;
+
+                for (int i = 0; i < mResults.size(); i++) {
+                    int start = (i * NUM_FIELDS) + + NUM_FIELDS;
+                    data[(start++)] = "Iteration";
+                    data[(start++)] = "" + i;
+                    data[(start++)] = "Total Frames";
+                    int currentFrameCount = mResults.get(i).getTotalFrameCount();
+                    totalFrameCount += currentFrameCount;
+                    data[(start++)] = Integer.toString(currentFrameCount);
+                    data[(start++)] = "Average frame duration:";
+                    double currentAvgFrameDuration = mResults.get(i).getAverage(FrameMetrics.TOTAL_DURATION);
+                    totalAvgFrameDuration += currentAvgFrameDuration;
+                    data[(start++)] = String.format("%.2f", currentAvgFrameDuration);
+                    data[(start++)] = "Frame duration 99th:";
+                    double current99FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 99);
+                    total99FrameDuration += current99FrameDuration;
+                    data[(start++)] = String.format("%.2f", current99FrameDuration);
+                    data[(start++)] = "Frame duration 95th:";
+                    double current95FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 95);
+                    total95FrameDuration += current95FrameDuration;
+                    data[(start++)] = String.format("%.2f", current95FrameDuration);
+                    data[(start++)] = "Frame duration 90th:";
+                    double current90FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 90);
+                    total90FrameDuration += current90FrameDuration;
+                    data[(start++)] = String.format("%.2f", current90FrameDuration);
+                    data[(start++)] = "Longest frame:";
+                    double longestFrame = mResults.get(i).getMaximum(FrameMetrics.TOTAL_DURATION);
+                    if (totalLongestFrame == 0 || longestFrame > totalLongestFrame) {
+                        totalLongestFrame = longestFrame;
+                    }
+                    data[(start++)] = String.format("%.2f", longestFrame);
+                    data[(start++)] = "Shortest frame:";
+                    double shortestFrame = mResults.get(i).getMinimum(FrameMetrics.TOTAL_DURATION);
+                    if (totalShortestFrame == 0 || totalShortestFrame > shortestFrame) {
+                        totalShortestFrame = shortestFrame;
+                    }
+                    data[(start++)] = String.format("%.2f", shortestFrame);
+                    data[(start++)] = "Score:";
+                    double score = mResults.get(i).getScore();
+                    stats.addValue(score);
+                    data[(start++)] = String.format("%.2f", score);
+                    data[(start++)] = "==============";
+                    data[(start++)] = "============================";
+                };
+
+                int start = 0;
+                data[0] = "Overall: ";
+                data[1] = "";
+                data[(start++)] = "Total Frames";
+                data[(start++)] = Integer.toString(totalFrameCount);
+                data[(start++)] = "Average frame duration:";
+                data[(start++)] = String.format("%.2f", totalAvgFrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 99th:";
+                data[(start++)] = String.format("%.2f", total99FrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 95th:";
+                data[(start++)] = String.format("%.2f", total95FrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 90th:";
+                data[(start++)] = String.format("%.2f", total90FrameDuration / mResults.size());
+                data[(start++)] = "Longest frame:";
+                data[(start++)] = String.format("%.2f", totalLongestFrame);
+                data[(start++)] = "Shortest frame:";
+                data[(start++)] = String.format("%.2f", totalShortestFrame);
+                data[(start++)] = "Score:";
+                data[(start++)] = String.format("%.2f", stats.getGeometricMean());
+                data[(start++)] = "==============";
+                data[(start++)] = "============================";
+            }
+
+            ArrayList<Map<String, String>> dataMap = new ArrayList<>();
+            for (int i = 0; i < data.length - 1; i += 2) {
+                HashMap<String, String> map = new HashMap<>();
+                map.put("name", data[i]);
+                map.put("value", data[i + 1]);
+                dataMap.add(map);
+            }
+
+            return dataMap;
+        }
+
+        @Override
+        protected void onPostExecute(ArrayList<Map<String, String>> dataMap) {
+            setListAdapter(new SimpleAdapter(getActivity(), dataMap, R.layout.results_list_item,
+                    new String[] {"name", "value"}, new int[] { R.id.result_name, R.id.result_value }));
+            setListShown(true);
+        }
+    };
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setListShown(false);
+        mLoadScoresTask.execute();
+    }
+
+    public void setRunInfo(String name, int runId) {
+        mResults = GlobalResultsStore.getInstance(getActivity()).loadTestResults(name, runId);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
new file mode 100644
index 0000000..d91e579
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.benchmark.registry;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the category of a particular benchmark.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({BenchmarkCategory.GENERIC, BenchmarkCategory.UI, BenchmarkCategory.COMPUTE})
+@interface BenchmarkCategory {
+    int GENERIC = 0;
+    int UI = 1;
+    int COMPUTE = 2;
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
new file mode 100644
index 0000000..4cb7716
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
@@ -0,0 +1,151 @@
+/*
+ * 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.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.view.View;
+import android.widget.CheckBox;
+
+/**
+ * Logical grouping of benchmarks
+ */
+public class BenchmarkGroup {
+    public static final String BENCHMARK_EXTRA_ENABLED_TESTS =
+            "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS";
+
+    public static final String BENCHMARK_EXTRA_RUN_COUNT =
+            "com.android.benchmark.EXTRA_RUN_COUNT";
+    public static final String BENCHMARK_EXTRA_FINISH = "com.android.benchmark.FINISH_WHEN_DONE";
+
+    public static class Benchmark implements CheckBox.OnClickListener {
+        /** The name of this individual benchmark test */
+        private final String mName;
+
+        /** The category of this individual benchmark test */
+        private final @BenchmarkCategory int mCategory;
+
+        /** Human-readable description of the benchmark */
+        private final String mDescription;
+
+        private final int mId;
+
+        private boolean mEnabled;
+
+        Benchmark(int id, String name, @BenchmarkCategory int category, String description) {
+            mId = id;
+            mName = name;
+            mCategory = category;
+            mDescription = description;
+            mEnabled = true;
+        }
+
+        public boolean isEnabled() { return mEnabled; }
+
+        public void setEnabled(boolean enabled) { mEnabled = enabled; }
+
+        public int getId() { return mId; }
+
+        public String getDescription() { return mDescription; }
+
+        public @BenchmarkCategory int getCategory() { return mCategory; }
+
+        public String getName() { return mName; }
+
+        @Override
+        public void onClick(View view) {
+            setEnabled(((CheckBox)view).isChecked());
+        }
+    }
+
+    /**
+     * Component for this benchmark group.
+     */
+    private final ComponentName mComponentName;
+
+    /**
+     * Benchmark title, showed in the {@link android.widget.ListView}
+     */
+    private final String mTitle;
+
+    /**
+     * List of all benchmarks exported by this group
+     */
+    private final Benchmark[] mBenchmarks;
+
+    /**
+     * The intent to launch the benchmark
+     */
+    private final Intent mIntent;
+
+    /** Human-readable description of the benchmark group */
+    private final String mDescription;
+
+    BenchmarkGroup(ComponentName componentName, String title,
+                   String description, Benchmark[] benchmarks, Intent intent) {
+        mComponentName = componentName;
+        mTitle = title;
+        mBenchmarks = benchmarks;
+        mDescription = description;
+        mIntent = intent;
+    }
+
+    public Intent getIntent() {
+        int[] enabledBenchmarksIds = getEnabledBenchmarksIds();
+        if (enabledBenchmarksIds.length != 0) {
+            mIntent.putExtra(BENCHMARK_EXTRA_ENABLED_TESTS, enabledBenchmarksIds);
+            return mIntent;
+        }
+
+        return null;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public Benchmark[] getBenchmarks() {
+        return mBenchmarks;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    private int[] getEnabledBenchmarksIds() {
+        int enabledBenchmarkCount = 0;
+        for (int i = 0; i < mBenchmarks.length; i++) {
+            if (mBenchmarks[i].isEnabled()) {
+                enabledBenchmarkCount++;
+            }
+        }
+
+        int writeIndex = 0;
+        int[] enabledBenchmarks = new int[enabledBenchmarkCount];
+        for (int i = 0; i < mBenchmarks.length; i++) {
+            if (mBenchmarks[i].isEnabled()) {
+                enabledBenchmarks[writeIndex++] = mBenchmarks[i].getId();
+            }
+        }
+
+        return enabledBenchmarks;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
new file mode 100644
index 0000000..89c6aed
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -0,0 +1,236 @@
+/*
+ * 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.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.benchmark.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ */
+public class BenchmarkRegistry {
+
+    /** Metadata key for benchmark XML data */
+    private static final String BENCHMARK_GROUP_META_KEY =
+            "com.android.benchmark.benchmark_group";
+
+    /** Intent action specifying an activity that runs a single benchmark test. */
+    private static final String ACTION_BENCHMARK = "com.android.benchmark.ACTION_BENCHMARK";
+    public static final String EXTRA_ID = "com.android.benchmark.EXTRA_ID";
+
+    private static final String TAG_BENCHMARK_GROUP = "com.android.benchmark.BenchmarkGroup";
+    private static final String TAG_BENCHMARK = "com.android.benchmark.Benchmark";
+
+    private List<BenchmarkGroup> mGroups;
+
+    private final Context mContext;
+
+    public BenchmarkRegistry(Context context) {
+        mContext = context;
+        mGroups = new ArrayList<>();
+        loadBenchmarks();
+    }
+
+    private Intent getIntentFromInfo(ActivityInfo inf) {
+        Intent intent = new Intent();
+        intent.setClassName(inf.packageName, inf.name);
+        return intent;
+    }
+
+    public void loadBenchmarks() {
+        Intent intent = new Intent(ACTION_BENCHMARK);
+        intent.setPackage(mContext.getPackageName());
+
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+        for (ResolveInfo inf : resolveInfos) {
+            List<BenchmarkGroup> groups = parseBenchmarkGroup(inf.activityInfo);
+            if (groups != null) {
+                mGroups.addAll(groups);
+            }
+        }
+    }
+
+    private boolean seekToTag(XmlPullParser parser, String tag)
+            throws XmlPullParserException, IOException {
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+            eventType = parser.next();
+        }
+        return eventType != XmlPullParser.END_DOCUMENT && tag.equals(parser.getName());
+    }
+
+    @BenchmarkCategory int getCategory(int category) {
+        switch (category) {
+            case BenchmarkCategory.COMPUTE:
+                return BenchmarkCategory.COMPUTE;
+            case BenchmarkCategory.UI:
+                return BenchmarkCategory.UI;
+            default:
+                return BenchmarkCategory.GENERIC;
+        }
+    }
+
+    private List<BenchmarkGroup> parseBenchmarkGroup(ActivityInfo activityInfo) {
+        PackageManager pm = mContext.getPackageManager();
+
+        ComponentName componentName = new ComponentName(
+                activityInfo.packageName, activityInfo.name);
+
+        SparseArray<List<BenchmarkGroup.Benchmark>> benchmarks = new SparseArray<>();
+        String groupName, groupDescription;
+        try (XmlResourceParser parser = activityInfo.loadXmlMetaData(pm, BENCHMARK_GROUP_META_KEY)) {
+
+            if (!seekToTag(parser, TAG_BENCHMARK_GROUP)) {
+                return null;
+            }
+
+            Resources res = pm.getResourcesForActivity(componentName);
+            AttributeSet attributeSet = Xml.asAttributeSet(parser);
+            TypedArray groupAttribs = res.obtainAttributes(attributeSet, R.styleable.BenchmarkGroup);
+
+            groupName = groupAttribs.getString(R.styleable.BenchmarkGroup_name);
+            groupDescription = groupAttribs.getString(R.styleable.BenchmarkGroup_description);
+            groupAttribs.recycle();
+            parser.next();
+
+            while (seekToTag(parser, TAG_BENCHMARK)) {
+                TypedArray benchAttribs =
+                        res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Benchmark);
+                int id = benchAttribs.getResourceId(R.styleable.Benchmark_id, -1);
+                String testName = benchAttribs.getString(R.styleable.Benchmark_name);
+                String testDescription = benchAttribs.getString(R.styleable.Benchmark_description);
+                int testCategory = benchAttribs.getInt(R.styleable.Benchmark_category,
+                        BenchmarkCategory.GENERIC);
+                int category = getCategory(testCategory);
+                BenchmarkGroup.Benchmark benchmark = new BenchmarkGroup.Benchmark(
+                        id, testName, category, testDescription);
+                List<BenchmarkGroup.Benchmark> benches = benchmarks.get(category);
+                if (benches == null) {
+                    benches = new ArrayList<>();
+                    benchmarks.append(category, benches);
+                }
+
+                benches.add(benchmark);
+
+                benchAttribs.recycle();
+                parser.next();
+            }
+        } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) {
+            return null;
+        }
+
+        List<BenchmarkGroup> result = new ArrayList<>();
+        Intent testIntent = getIntentFromInfo(activityInfo);
+        for (int i = 0; i < benchmarks.size(); i++) {
+            int cat = benchmarks.keyAt(i);
+            List<BenchmarkGroup.Benchmark> thisGroup = benchmarks.get(cat);
+            BenchmarkGroup.Benchmark[] benchmarkArray =
+                    new BenchmarkGroup.Benchmark[thisGroup.size()];
+            thisGroup.toArray(benchmarkArray);
+            result.add(new BenchmarkGroup(componentName,
+                    groupName + " - " + getCategoryString(cat), groupDescription, benchmarkArray,
+                    testIntent));
+        }
+
+        return result;
+    }
+
+    public int getGroupCount() {
+        return mGroups.size();
+    }
+
+    public int getBenchmarkCount(int benchmarkIndex) {
+        BenchmarkGroup group = getBenchmarkGroup(benchmarkIndex);
+        if (group != null) {
+            return group.getBenchmarks().length;
+        }
+        return 0;
+    }
+
+    public BenchmarkGroup getBenchmarkGroup(int benchmarkIndex) {
+        if (benchmarkIndex >= mGroups.size()) {
+            return null;
+        }
+
+        return mGroups.get(benchmarkIndex);
+    }
+
+    public static String getCategoryString(int category) {
+        switch (category) {
+            case BenchmarkCategory.UI:
+                return "UI";
+            case BenchmarkCategory.COMPUTE:
+                return "Compute";
+            case BenchmarkCategory.GENERIC:
+                return "Generic";
+            default:
+                return "";
+        }
+    }
+
+    public static String getBenchmarkName(Context context, int benchmarkId) {
+        switch (benchmarkId) {
+            case R.id.benchmark_list_view_scroll:
+                return context.getString(R.string.list_view_scroll_name);
+            case R.id.benchmark_image_list_view_scroll:
+                return context.getString(R.string.image_list_view_scroll_name);
+            case R.id.benchmark_shadow_grid:
+                return context.getString(R.string.shadow_grid_name);
+            case R.id.benchmark_text_high_hitrate:
+                return context.getString(R.string.text_high_hitrate_name);
+            case R.id.benchmark_text_low_hitrate:
+                return context.getString(R.string.text_low_hitrate_name);
+            case R.id.benchmark_edit_text_input:
+                return context.getString(R.string.edit_text_input_name);
+            case R.id.benchmark_memory_bandwidth:
+                return context.getString(R.string.memory_bandwidth_name);
+            case R.id.benchmark_memory_latency:
+                return context.getString(R.string.memory_latency_name);
+            case R.id.benchmark_power_management:
+                return context.getString(R.string.power_management_name);
+            case R.id.benchmark_cpu_heat_soak:
+                return context.getString(R.string.cpu_heat_soak_name);
+            case R.id.benchmark_cpu_gflops:
+                return context.getString(R.string.cpu_gflops_name);
+            case R.id.benchmark_overdraw:
+                return context.getString(R.string.overdraw_name);
+            default:
+                return "Some Benchmark";
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
new file mode 100644
index 0000000..5d0cba2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
@@ -0,0 +1,389 @@
+/*
+ * 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.benchmark.results;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.view.FrameMetrics;
+import android.widget.Toast;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+
+public class GlobalResultsStore extends SQLiteOpenHelper {
+    private static final int VERSION = 2;
+
+    private static GlobalResultsStore sInstance;
+    private static final String UI_RESULTS_TABLE = "ui_results";
+
+    private final Context mContext;
+
+    private GlobalResultsStore(Context context) {
+        super(context, "BenchmarkResults", null, VERSION);
+        mContext = context;
+    }
+
+    public static GlobalResultsStore getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new GlobalResultsStore(context.getApplicationContext());
+        }
+
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase sqLiteDatabase) {
+        sqLiteDatabase.execSQL("CREATE TABLE " + UI_RESULTS_TABLE + " (" +
+                " _id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                " name TEXT," +
+                " run_id INTEGER," +
+                " iteration INTEGER," +
+                " timestamp TEXT,"  +
+                " unknown_delay REAL," +
+                " input REAL," +
+                " animation REAL," +
+                " layout REAL," +
+                " draw REAL," +
+                " sync REAL," +
+                " command_issue REAL," +
+                " swap_buffers REAL," +
+                " total_duration REAL," +
+                " jank_frame BOOLEAN, " +
+                " device_charging INTEGER);");
+    }
+
+    public void storeRunResults(String testName, int runId, int iteration,
+                                UiBenchmarkResult result) {
+        SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+
+        try {
+            String date = DateFormat.getDateTimeInstance().format(new Date());
+            int jankIndexIndex = 0;
+            int[] sortedJankIndices = result.getSortedJankFrameIndices();
+            int totalFrameCount = result.getTotalFrameCount();
+            for (int frameIdx = 0; frameIdx < totalFrameCount; frameIdx++) {
+                ContentValues cv = new ContentValues();
+                cv.put("name", testName);
+                cv.put("run_id", runId);
+                cv.put("iteration", iteration);
+                cv.put("timestamp", date);
+                cv.put("unknown_delay",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.UNKNOWN_DELAY_DURATION));
+                cv.put("input",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.INPUT_HANDLING_DURATION));
+                cv.put("animation",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.ANIMATION_DURATION));
+                cv.put("layout",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.LAYOUT_MEASURE_DURATION));
+                cv.put("draw",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.DRAW_DURATION));
+                cv.put("sync",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.SYNC_DURATION));
+                cv.put("command_issue",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.COMMAND_ISSUE_DURATION));
+                cv.put("swap_buffers",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.SWAP_BUFFERS_DURATION));
+                cv.put("total_duration",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.TOTAL_DURATION));
+                if (jankIndexIndex < sortedJankIndices.length &&
+                        sortedJankIndices[jankIndexIndex] == frameIdx) {
+                    jankIndexIndex++;
+                    cv.put("jank_frame", true);
+                } else {
+                    cv.put("jank_frame", false);
+                }
+                db.insert(UI_RESULTS_TABLE, null, cv);
+            }
+            db.setTransactionSuccessful();
+            Toast.makeText(mContext, "Score: " + result.getScore()
+                    + " Jank: " + (100 * sortedJankIndices.length) / (float) totalFrameCount + "%",
+                    Toast.LENGTH_LONG).show();
+        } finally {
+            db.endTransaction();
+        }
+
+    }
+
+    public ArrayList<UiBenchmarkResult> loadTestResults(String testName, int runId) {
+        SQLiteDatabase db = getReadableDatabase();
+        ArrayList<UiBenchmarkResult> resultList = new ArrayList<>();
+        try {
+            String[] columnsToQuery = new String[] {
+                    "name",
+                    "run_id",
+                    "iteration",
+                    "unknown_delay",
+                    "input",
+                    "animation",
+                    "layout",
+                    "draw",
+                    "sync",
+                    "command_issue",
+                    "swap_buffers",
+                    "total_duration",
+            };
+
+            Cursor cursor = db.query(
+                    UI_RESULTS_TABLE, columnsToQuery, "run_id=? AND name=?",
+                    new String[] { Integer.toString(runId), testName }, null, null, "iteration");
+
+            double[] values = new double[columnsToQuery.length - 3];
+
+            while (cursor.moveToNext()) {
+                int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+
+                values[0] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("unknown_delay"));
+                values[1] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("input"));
+                values[2] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("animation"));
+                values[3] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("layout"));
+                values[4] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("draw"));
+                values[5] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("sync"));
+                values[6] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("command_issue"));
+                values[7] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("swap_buffers"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+
+                UiBenchmarkResult iterationResult;
+                if (resultList.size() == iteration) {
+                    iterationResult = new UiBenchmarkResult(values);
+                    resultList.add(iteration, iterationResult);
+                } else {
+                    iterationResult = resultList.get(iteration);
+                    iterationResult.update(values);
+                }
+            }
+
+            cursor.close();
+        } finally {
+            db.close();
+        }
+
+        int total = resultList.get(0).getTotalFrameCount();
+        for (int i = 0; i < total; i++) {
+            System.out.println(""+ resultList.get(0).getMetricAtIndex(0, FrameMetrics.TOTAL_DURATION));
+        }
+
+        return resultList;
+    }
+
+    public HashMap<String, ArrayList<UiBenchmarkResult>> loadDetailedResults(int runId) {
+        SQLiteDatabase db = getReadableDatabase();
+        HashMap<String, ArrayList<UiBenchmarkResult>> results = new HashMap<>();
+        try {
+            String[] columnsToQuery = new String[] {
+                    "name",
+                    "run_id",
+                    "iteration",
+                    "unknown_delay",
+                    "input",
+                    "animation",
+                    "layout",
+                    "draw",
+                    "sync",
+                    "command_issue",
+                    "swap_buffers",
+                    "total_duration",
+            };
+
+            Cursor cursor = db.query(
+                    UI_RESULTS_TABLE, columnsToQuery, "run_id=?",
+                    new String[] { Integer.toString(runId) }, null, null, "name, iteration");
+
+            double[] values = new double[columnsToQuery.length - 3];
+            while (cursor.moveToNext()) {
+                int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
+                ArrayList<UiBenchmarkResult> resultList = results.get(name);
+                if (resultList == null) {
+                    resultList = new ArrayList<>();
+                    results.put(name, resultList);
+                }
+
+                values[0] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("unknown_delay"));
+                values[1] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("input"));
+                values[2] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("animation"));
+                values[3] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("layout"));
+                values[4] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("draw"));
+                values[5] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("sync"));
+                values[6] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("command_issue"));
+                values[7] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("swap_buffers"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+
+                UiBenchmarkResult iterationResult;
+                if (resultList.size() == iteration) {
+                    iterationResult = new UiBenchmarkResult(values);
+                    resultList.add(iterationResult);
+                } else {
+                    iterationResult = resultList.get(iteration);
+                    iterationResult.update(values);
+                }
+            }
+
+            cursor.close();
+        } finally {
+            db.close();
+        }
+
+        return results;
+    }
+
+    public void exportToCsv() throws IOException {
+        String path = mContext.getFilesDir() + "/results-" + System.currentTimeMillis() + ".csv";
+        SQLiteDatabase db = getReadableDatabase();
+
+        // stats across metrics for each run and each test
+        HashMap<String, DescriptiveStatistics> stats = new HashMap<>();
+
+        Cursor runIdCursor = db.query(
+                UI_RESULTS_TABLE, new String[] { "run_id" }, null, null, "run_id", null, null);
+
+        while (runIdCursor.moveToNext()) {
+
+            int runId = runIdCursor.getInt(runIdCursor.getColumnIndexOrThrow("run_id"));
+            HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+                    loadDetailedResults(runId);
+
+            writeRawResults(runId, detailedResults);
+
+            DescriptiveStatistics overall = new DescriptiveStatistics();
+            try (FileWriter writer = new FileWriter(path, true)) {
+                writer.write("Run ID, " + runId + "\n");
+                writer.write("Test, Iteration, Score, Jank Penalty, Consistency Bonus, 95th, " +
+                        "90th\n");
+                for (String testName : detailedResults.keySet()) {
+                    ArrayList<UiBenchmarkResult> results = detailedResults.get(testName);
+                    DescriptiveStatistics scoreStats = new DescriptiveStatistics();
+                    DescriptiveStatistics jankPenalty = new DescriptiveStatistics();
+                    DescriptiveStatistics consistencyBonus = new DescriptiveStatistics();
+                    for (int i = 0; i < results.size(); i++) {
+                        UiBenchmarkResult result = results.get(i);
+                        int score = result.getScore();
+                        scoreStats.addValue(score);
+                        overall.addValue(score);
+                        jankPenalty.addValue(result.getJankPenalty());
+                        consistencyBonus.addValue(result.getConsistencyBonus());
+
+                        writer.write(testName);
+                        writer.write(",");
+                        writer.write("" + i);
+                        writer.write(",");
+                        writer.write("" + score);
+                        writer.write(",");
+                        writer.write("" + result.getJankPenalty());
+                        writer.write(",");
+                        writer.write("" + result.getConsistencyBonus());
+                        writer.write(",");
+                        writer.write(Double.toString(
+                                result.getPercentile(FrameMetrics.TOTAL_DURATION, 95)));
+                        writer.write(",");
+                        writer.write(Double.toString(
+                                result.getPercentile(FrameMetrics.TOTAL_DURATION, 90)));
+                        writer.write("\n");
+                    }
+
+                    writer.write("Score CV," +
+                            (100 * scoreStats.getStandardDeviation()
+                                    / scoreStats.getMean()) + "%\n");
+                    writer.write("Jank Penalty CV, " +
+                            (100 * jankPenalty.getStandardDeviation()
+                                    / jankPenalty.getMean()) + "%\n");
+                    writer.write("Consistency Bonus CV, " +
+                            (100 * consistencyBonus.getStandardDeviation()
+                                    / consistencyBonus.getMean()) + "%\n");
+                    writer.write("\n");
+                }
+
+                writer.write("Overall Score CV,"  +
+                        (100 * overall.getStandardDeviation() / overall.getMean()) + "%\n");
+                writer.flush();
+            }
+        }
+
+        runIdCursor.close();
+    }
+
+    private void writeRawResults(int runId,
+                                 HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults) {
+        StringBuilder path = new StringBuilder();
+        path.append(mContext.getFilesDir());
+        path.append("/");
+        path.append(Integer.toString(runId));
+        path.append(".csv");
+        try (FileWriter writer = new FileWriter(path.toString())) {
+            for (String test : detailedResults.keySet()) {
+                writer.write("Test, " + test + "\n");
+                writer.write("iteration, unknown delay, input, animation, layout, draw, sync, " +
+                        "command issue, swap buffers\n");
+                ArrayList<UiBenchmarkResult> runs = detailedResults.get(test);
+                for (int i = 0; i < runs.size(); i++) {
+                    UiBenchmarkResult run = runs.get(i);
+                    for (int j = 0; j < run.getTotalFrameCount(); j++) {
+                        writer.write(Integer.toString(i) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.UNKNOWN_DELAY_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.INPUT_HANDLING_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.ANIMATION_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.LAYOUT_MEASURE_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.DRAW_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.SYNC_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.COMMAND_ISSUE_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.SWAP_BUFFERS_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.TOTAL_DURATION) + "\n");
+                    }
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int currentVersion) {
+        if (oldVersion < VERSION) {
+            sqLiteDatabase.execSQL("ALTER TABLE "
+                    + UI_RESULTS_TABLE + " ADD COLUMN timestamp TEXT;");
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
new file mode 100644
index 0000000..da6e05a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
@@ -0,0 +1,245 @@
+/*
+ * 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.benchmark.results;
+
+import android.annotation.TargetApi;
+import android.view.FrameMetrics;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility for storing and analyzing UI benchmark results.
+ */
+@TargetApi(24)
+public class UiBenchmarkResult {
+    private static final int BASE_SCORE = 100;
+    private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32;
+    private static final int JANK_PENALTY_THRESHOLD_MS = 12;
+    private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS =
+            ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS;
+    private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD =
+            BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS;
+    private static final int CONSISTENCY_BONUS_MAX = 100;
+
+    private static final int METRIC_WAS_JANKY = -1;
+
+    private static final int[] METRICS = new int[] {
+            FrameMetrics.UNKNOWN_DELAY_DURATION,
+            FrameMetrics.INPUT_HANDLING_DURATION,
+            FrameMetrics.ANIMATION_DURATION,
+            FrameMetrics.LAYOUT_MEASURE_DURATION,
+            FrameMetrics.DRAW_DURATION,
+            FrameMetrics.SYNC_DURATION,
+            FrameMetrics.COMMAND_ISSUE_DURATION,
+            FrameMetrics.SWAP_BUFFERS_DURATION,
+            FrameMetrics.TOTAL_DURATION,
+    };
+    public static final int FRAME_PERIOD_MS = 16;
+
+    private final DescriptiveStatistics[] mStoredStatistics;
+
+    public UiBenchmarkResult(List<FrameMetrics> instances) {
+        mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+        insertMetrics(instances);
+    }
+
+    public UiBenchmarkResult(double[] values) {
+        mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+        insertValues(values);
+    }
+
+    public void update(List<FrameMetrics> instances) {
+        insertMetrics(instances);
+    }
+
+    public void update(double[] values) {
+        insertValues(values);
+    }
+
+    public double getAverage(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMean();
+    }
+
+    public double getMinimum(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMin();
+    }
+
+    public double getMaximum(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMax();
+    }
+
+    public int getMaximumIndex(int id) {
+        int pos = getMetricPosition(id);
+        double[] storedMetrics = mStoredStatistics[pos].getValues();
+        int maxIdx = 0;
+        for (int i = 0; i < storedMetrics.length; i++) {
+            if (storedMetrics[i] >= storedMetrics[maxIdx]) {
+                maxIdx = i;
+            }
+        }
+
+        return maxIdx;
+    }
+
+    public double getMetricAtIndex(int index, int metricId) {
+        return mStoredStatistics[getMetricPosition(metricId)].getElement(index);
+    }
+
+    public double getPercentile(int id, int percentile) {
+        if (percentile > 100) percentile = 100;
+        if (percentile < 0) percentile = 0;
+
+        int metricPos = getMetricPosition(id);
+        return mStoredStatistics[metricPos].getPercentile(percentile);
+    }
+
+    public int getTotalFrameCount() {
+        if (mStoredStatistics.length == 0) {
+            return 0;
+        }
+
+        return (int) mStoredStatistics[0].getN();
+    }
+
+    public int getScore() {
+        SummaryStatistics badFramesStats = new SummaryStatistics();
+
+        int totalFrameCount = getTotalFrameCount();
+        for (int i = 0; i < totalFrameCount; i++) {
+            double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION);
+            if (totalDuration >= 12) {
+                badFramesStats.addValue(totalDuration);
+            }
+        }
+
+        int length = getSortedJankFrameIndices().length;
+        double jankFrameCount = 100 * length / (double) totalFrameCount;
+
+        System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount
+                + " StdDev: " + badFramesStats.getStandardDeviation() +
+                " Count Bad: " + badFramesStats.getN() + " Count Jank: " + length);
+
+        return (int) Math.round(
+                (badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation());
+    }
+
+    public int getJankPenalty() {
+        double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]
+                .getPercentile(95);
+        System.out.println("95: " + total95th);
+        double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS;
+        if (aboveThreshold <= 0) {
+            return 0;
+        }
+
+        if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) {
+            return BASE_SCORE;
+        }
+
+        return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold);
+    }
+
+    public int getConsistencyBonus() {
+        DescriptiveStatistics totalDurationStats =
+                mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)];
+
+        double standardDeviation = totalDurationStats.getStandardDeviation();
+        if (standardDeviation == 0) {
+            return CONSISTENCY_BONUS_MAX;
+        }
+
+        // 1 / CV of the total duration.
+        double bonus = totalDurationStats.getMean() / standardDeviation;
+        return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX);
+    }
+
+    public int[] getSortedJankFrameIndices() {
+        ArrayList<Integer> jankFrameIndices = new ArrayList<>();
+        boolean tripleBuffered = false;
+        int totalFrameCount = getTotalFrameCount();
+        int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION);
+
+        for (int i = 0; i < totalFrameCount; i++) {
+            double thisDuration = mStoredStatistics[totalDurationPos].getElement(i);
+            if (!tripleBuffered) {
+                if (thisDuration > FRAME_PERIOD_MS) {
+                    tripleBuffered = true;
+                    jankFrameIndices.add(i);
+                }
+            } else {
+                if (thisDuration > 2 * FRAME_PERIOD_MS) {
+                    tripleBuffered = false;
+                    jankFrameIndices.add(i);
+                }
+            }
+        }
+
+        int[] res = new int[jankFrameIndices.size()];
+        int i = 0;
+        for (Integer index : jankFrameIndices) {
+            res[i++] = index;
+        }
+        return res;
+    }
+
+    private int getMetricPosition(int id) {
+        for (int i = 0; i < METRICS.length; i++) {
+            if (id == METRICS[i]) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    private void insertMetrics(List<FrameMetrics> instances) {
+        for (FrameMetrics frame : instances) {
+            for (int i = 0; i < METRICS.length; i++) {
+                DescriptiveStatistics stats = mStoredStatistics[i];
+                if (stats == null) {
+                    stats = new DescriptiveStatistics();
+                    mStoredStatistics[i] = stats;
+                }
+
+                mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000);
+            }
+        }
+    }
+
+    private void insertValues(double[] values) {
+        if (values.length != METRICS.length) {
+            throw new IllegalArgumentException("invalid values array");
+        }
+
+        for (int i = 0; i < values.length; i++) {
+            DescriptiveStatistics stats = mStoredStatistics[i];
+            if (stats == null) {
+                stats = new DescriptiveStatistics();
+                mStoredStatistics[i] = stats;
+            }
+
+            mStoredStatistics[i].addValue(values[i]);
+        }
+    }
+ }
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
new file mode 100644
index 0000000..aba16d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
@@ -0,0 +1,137 @@
+/*
+ * 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.benchmark.synthetic;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.app.PerfTimeline;
+
+import junit.framework.Test;
+
+
+public class MemoryActivity extends Activity {
+    private TextView mTextStatus;
+    private TextView mTextMin;
+    private TextView mTextMax;
+    private TextView mTextTypical;
+    private PerfTimeline mTimeline;
+
+    TestInterface mTI;
+    int mActiveTest;
+
+    private class SyntheticTestCallback extends TestInterface.TestResultCallback {
+        @Override
+        void onTestResult(int command, float result) {
+            Intent resultIntent = new Intent();
+            resultIntent.putExtra("com.android.benchmark.synthetic.TEST_RESULT", result);
+            setResult(RESULT_OK, resultIntent);
+            finish();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_memory);
+
+        mTextStatus = (TextView) findViewById(R.id.textView_status);
+        mTextMin = (TextView) findViewById(R.id.textView_min);
+        mTextMax = (TextView) findViewById(R.id.textView_max);
+        mTextTypical = (TextView) findViewById(R.id.textView_typical);
+
+        mTimeline = (PerfTimeline) findViewById(R.id.mem_timeline);
+
+        mTI = new TestInterface(mTimeline, 2, new SyntheticTestCallback());
+        mTI.mTextMax = mTextMax;
+        mTI.mTextMin = mTextMin;
+        mTI.mTextStatus = mTextStatus;
+        mTI.mTextTypical = mTextTypical;
+
+        mTimeline.mLinesLow = mTI.mLinesLow;
+        mTimeline.mLinesHigh = mTI.mLinesHigh;
+        mTimeline.mLinesValue = mTI.mLinesValue;
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Intent i = getIntent();
+        mActiveTest = i.getIntExtra("test", 0);
+
+        switch (mActiveTest) {
+            case 0:
+                mTI.runMemoryBandwidth();
+                break;
+            case 1:
+                mTI.runMemoryLatency();
+                break;
+            case 2:
+                mTI.runPowerManagement();
+                break;
+            case 3:
+                mTI.runCPUHeatSoak();
+                break;
+            case 4:
+                mTI.runCPUGFlops();
+                break;
+            default:
+                break;
+
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_memory, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void onCpuBandwidth(View v) {
+
+
+    }
+
+
+
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
new file mode 100644
index 0000000..8f083a2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
@@ -0,0 +1,451 @@
+/*
+ * 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.benchmark.synthetic;
+
+import android.view.View;
+import android.widget.TextView;
+
+import org.apache.commons.math.stat.StatUtils;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+
+public class TestInterface {
+    native long nInit(long options);
+    native long nDestroy(long b);
+    native float nGetData(long b, float[] data);
+    native boolean nRunPowerManagementTest(long b, long options);
+    native boolean nRunCPUHeatSoakTest(long b, long options);
+
+    native boolean nMemTestStart(long b);
+    native float nMemTestBandwidth(long b, long size);
+    native float nMemTestLatency(long b, long size);
+    native void nMemTestEnd(long b);
+
+    native float nGFlopsTest(long b, long opt);
+
+    public static class TestResultCallback {
+        void onTestResult(int command, float result) { }
+    }
+
+    static {
+        System.loadLibrary("nativebench");
+    }
+
+    float[] mLinesLow;
+    float[] mLinesHigh;
+    float[] mLinesValue;
+    TextView mTextStatus;
+    TextView mTextMin;
+    TextView mTextMax;
+    TextView mTextTypical;
+
+    private View mViewToUpdate;
+
+    private LooperThread mLT;
+
+    TestInterface(View v, int runtimeSeconds, TestResultCallback callback) {
+        int buckets = runtimeSeconds * 1000;
+        mLinesLow = new float[buckets * 4];
+        mLinesHigh = new float[buckets * 4];
+        mLinesValue = new float[buckets * 4];
+        mViewToUpdate = v;
+
+        mLT = new LooperThread(this, callback);
+        mLT.start();
+    }
+
+    static class LooperThread extends Thread {
+        public static final int CommandExit = 1;
+        public static final int TestPowerManagement = 2;
+        public static final int TestMemoryBandwidth = 3;
+        public static final int TestMemoryLatency = 4;
+        public static final int TestHeatSoak = 5;
+        public static final int TestGFlops = 6;
+
+        private volatile boolean mRun = true;
+        private TestInterface mTI;
+        private TestResultCallback mCallback;
+
+        Queue<Integer> mCommandQueue = new LinkedList<Integer>();
+
+        LooperThread(TestInterface ti, TestResultCallback callback) {
+            super("BenchmarkTestThread");
+            mTI = ti;
+            mCallback = callback;
+        }
+
+        void runCommand(int command) {
+            Integer i = Integer.valueOf(command);
+
+            synchronized (this) {
+                mCommandQueue.add(i);
+                notifyAll();
+            }
+        }
+
+        public void run() {
+            long b = mTI.nInit(0);
+            if (b == 0) {
+                return;
+            }
+
+            while (mRun) {
+                int command = 0;
+                synchronized (this) {
+                    if (mCommandQueue.isEmpty()) {
+                        try {
+                            wait();
+                        } catch (InterruptedException e) {
+                        }
+                    }
+
+                    if (!mCommandQueue.isEmpty()) {
+                        command = mCommandQueue.remove();
+                    }
+                }
+
+                switch (command) {
+                    case CommandExit:
+                        mRun = false;
+                        break;
+                    case TestPowerManagement:
+                        float score = mTI.testPowerManagement(b);
+                        mCallback.onTestResult(command, 0);
+                        break;
+                    case TestMemoryBandwidth:
+                        mTI.testCPUMemoryBandwidth(b);
+                        break;
+                    case TestMemoryLatency:
+                        mTI.testCPUMemoryLatency(b);
+                        break;
+                    case TestHeatSoak:
+                        mTI.testCPUHeatSoak(b);
+                        break;
+                    case TestGFlops:
+                        mTI.testCPUGFlops(b);
+                        break;
+
+                }
+
+                //mViewToUpdate.post(new Runnable() {
+                  //  public void run() {
+                   //     mViewToUpdate.invalidate();
+                    //}
+                //});
+            }
+
+            mTI.nDestroy(b);
+        }
+
+        void exit() {
+            mRun = false;
+        }
+    }
+
+    void postTextToView(TextView v, String s) {
+        final TextView tv = v;
+        final String ts = s;
+
+        v.post(new Runnable() {
+            public void run() {
+                tv.setText(ts);
+            }
+        });
+
+    }
+
+    float calcAverage(float[] data) {
+        float total = 0.f;
+        for (int ct=0; ct < data.length; ct++) {
+            total += data[ct];
+        }
+        return total / data.length;
+    }
+
+    void makeGraph(float[] data, float[] lines) {
+        for (int ct = 0; ct < data.length; ct++) {
+            lines[ct * 4 + 0] = (float)ct;
+            lines[ct * 4 + 1] = 500.f - data[ct];
+            lines[ct * 4 + 2] = (float)ct;
+            lines[ct * 4 + 3] = 500.f;
+        }
+    }
+
+    float testPowerManagement(long b) {
+        float[] dat = new float[mLinesLow.length / 4];
+        postTextToView(mTextStatus, "Running single-threaded");
+        nRunPowerManagementTest(b, 1);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesLow);
+        mViewToUpdate.postInvalidate();
+        float avgMin = calcAverage(dat);
+
+        postTextToView(mTextMin, "Single threaded " + avgMin + " per second");
+
+        postTextToView(mTextStatus, "Running multi-threaded");
+        nRunPowerManagementTest(b, 4);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesHigh);
+        mViewToUpdate.postInvalidate();
+        float avgMax = calcAverage(dat);
+        postTextToView(mTextMax, "Multi threaded " + avgMax + " per second");
+
+        postTextToView(mTextStatus, "Running typical");
+        nRunPowerManagementTest(b, 0);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesValue);
+        mViewToUpdate.postInvalidate();
+        float avgTypical = calcAverage(dat);
+
+        float ofIdeal = avgTypical / (avgMax + avgMin) * 200.f;
+        postTextToView(mTextTypical, String.format("Typical mix (50/50) %%%2.0f of ideal", ofIdeal));
+        return ofIdeal * (avgMax + avgMin);
+    }
+
+    float testCPUHeatSoak(long b) {
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running heat soak test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        float peak = 0.f;
+        float total = 0.f;
+        float dThroughput = 0;
+        float prev = 0;
+        SummaryStatistics stats = new SummaryStatistics();
+        for (int t = 0; t < 1000; t++) {
+            nRunCPUHeatSoakTest(b, 1);
+            nGetData(b, dat);
+
+            float p = calcAverage(dat);
+            if (prev != 0) {
+                dThroughput += (prev - p);
+            }
+
+            prev = p;
+
+            mLinesLow[t * 4 + 1] = 499.f - p;
+            if (peak < p) {
+                peak = p;
+            }
+            for (float f : dat) {
+                stats.addValue(f);
+            }
+
+            total += p;
+
+            mViewToUpdate.postInvalidate();
+            postTextToView(mTextMin, "Peak " + peak + " per second");
+            postTextToView(mTextMax, "Current " + p + " per second");
+            postTextToView(mTextTypical, "Average " + (total / (t + 1)) + " per second");
+        }
+
+
+        float decreaseOverTime = dThroughput / 1000;
+
+        System.out.println("dthroughput/dt: " + decreaseOverTime);
+
+        float score = (float) (stats.getMean() / (stats.getStandardDeviation() * decreaseOverTime));
+
+        postTextToView(mTextStatus, "Score: " + score);
+        return score;
+    }
+
+    void testCPUMemoryBandwidth(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+                    8, 10, 12, 14, 16, 20, 24, 28,
+                    32, 40, 48, 56, 64, 80, 96, 112,
+                    128, 160, 192, 224, 256, 320, 384, 448,
+                    512, 640, 768, 896, 1024, 1280, 1536, 1792,
+                    2048, 2560, 3584, 4096, 5120, 6144, 7168,
+                    8192, 10240, 12288, 14336, 16384
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Bandwidth test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nMemTestBandwidth(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - (results[i*15+j] * 20.f);
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " GB/s");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " GB/s");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " GB/s");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    void testCPUMemoryLatency(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+                8, 10, 12, 14, 16, 20, 24, 28,
+                32, 40, 48, 56, 64, 80, 96, 112,
+                128, 160, 192, 224, 256, 320, 384, 448,
+                512, 640, 768, 896, 1024, 1280, 1536, 1792,
+                2048, 2560, 3584, 4096, 5120, 6144, 7168,
+                8192, 10240, 12288, 14336, 16384
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Latency test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nMemTestLatency(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+
+                if (ret > 400.f) ret = 400.f;
+                if (ret < 0.f) ret = 0.f;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+                //android.util.Log.e("bench", "test bw " + sizeK[i] + " - " + ret);
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " ns");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " ns");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " ns");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    void testCPUGFlops(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Latency test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nGFlopsTest(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+
+                if (ret > 400.f) ret = 400.f;
+                if (ret < 0.f) ret = 0.f;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " ns");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " ns");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " ns");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    public void runPowerManagement() {
+        mLT.runCommand(mLT.TestPowerManagement);
+    }
+
+    public void runMemoryBandwidth() {
+        mLT.runCommand(mLT.TestMemoryBandwidth);
+    }
+
+    public void runMemoryLatency() {
+        mLT.runCommand(mLT.TestMemoryLatency);
+    }
+
+    public void runCPUHeatSoak() {
+        mLT.runCommand(mLT.TestHeatSoak);
+    }
+
+    public void runCPUGFlops() {
+        mLT.runCommand(mLT.TestGFlops);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
new file mode 100644
index 0000000..f6a528a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -0,0 +1,157 @@
+/*
+ * 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.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+/**
+ *
+ */
+public class BitmapUploadActivity extends AppCompatActivity {
+    private Automator mAutomator;
+
+    public static class UploadView extends View {
+        private int mColorValue;
+        private Bitmap mBitmap;
+        private final DisplayMetrics mMetrics = new DisplayMetrics();
+        private final Rect mRect = new Rect();
+
+        public UploadView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @SuppressWarnings("unused")
+        public void setColorValue(int colorValue) {
+            if (colorValue == mColorValue) return;
+
+            mColorValue = colorValue;
+
+            // modify the bitmap's color to ensure it's uploaded to the GPU
+            mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+            invalidate();
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            getDisplay().getMetrics(mMetrics);
+            int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels);
+            int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720);
+            if (mBitmap == null
+                    || mBitmap.getWidth() != bitmapSize
+                    || mBitmap.getHeight() != bitmapSize) {
+                mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (mBitmap != null) {
+                mRect.set(0, 0, getWidth(), getHeight());
+                canvas.drawBitmap(mBitmap, null, mRect, null);
+            }
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            // animate color to force bitmap uploads
+            return super.onTouchEvent(event);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bitmap_upload);
+
+        final View uploadRoot = findViewById(R.id.upload_root);
+        uploadRoot.setKeepScreenOn(true);
+        uploadRoot.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+                ObjectAnimator colorValueAnimator =
+                        ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+                colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+                colorValueAnimator.setRepeatCount(100);
+                colorValueAnimator.start();
+
+                // animate scene root to guarantee there's a minimum amount of GPU rendering work
+                ObjectAnimator yAnimator = ObjectAnimator.ofFloat(
+                        view, "translationY", 0, 100);
+                yAnimator.setRepeatMode(ValueAnimator.REVERSE);
+                yAnimator.setRepeatCount(100);
+                yAnimator.start();
+
+                return true;
+            }
+        });
+
+        final UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+                    @Override
+                    public void onPostAutomate() {
+                        Intent result = new Intent();
+                        setResult(RESULT_OK, result);
+                        finish();
+                    }
+
+                    @Override
+                    public void onAutomate() {
+                        int[] coordinates = new int[2];
+                        uploadRoot.getLocationOnScreen(coordinates);
+
+                        int x = coordinates[0];
+                        int y = coordinates[1];
+
+                        float width = uploadRoot.getWidth();
+                        float height = uploadRoot.getHeight();
+
+                        float middleX = (x + width) / 5;
+                        float middleY = (y + height) / 5;
+
+                        addInteraction(Interaction.newTap(middleX, middleY));
+                    }
+                });
+
+        mAutomator.start();
+    }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
new file mode 100644
index 0000000..ea6fb58
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
@@ -0,0 +1,160 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class EditTextInputActivity extends AppCompatActivity {
+
+    private Automator mAutomator;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final EditText editText = new EditText(this);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        editText.setWidth(400);
+        editText.setHeight(200);
+        setContentView(editText);
+
+        String testName = getString(R.string.edit_text_input_name);
+
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(testName);
+        }
+
+        mAutomator = new Automator(testName, runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onAutomate() {
+
+                int[] coordinates = new int[2];
+                editText.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = editText.getWidth();
+                float height = editText.getHeight();
+
+                float middleX = (x + width) / 2;
+                float middleY = (y + height) / 2;
+
+                Interaction tap = Interaction.newTap(middleX, middleY);
+                addInteraction(tap);
+
+                int[] alphabet = {
+                        KeyEvent.KEYCODE_A,
+                        KeyEvent.KEYCODE_B,
+                        KeyEvent.KEYCODE_C,
+                        KeyEvent.KEYCODE_D,
+                        KeyEvent.KEYCODE_E,
+                        KeyEvent.KEYCODE_F,
+                        KeyEvent.KEYCODE_G,
+                        KeyEvent.KEYCODE_H,
+                        KeyEvent.KEYCODE_I,
+                        KeyEvent.KEYCODE_J,
+                        KeyEvent.KEYCODE_K,
+                        KeyEvent.KEYCODE_L,
+                        KeyEvent.KEYCODE_M,
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.KEYCODE_O,
+                        KeyEvent.KEYCODE_P,
+                        KeyEvent.KEYCODE_Q,
+                        KeyEvent.KEYCODE_R,
+                        KeyEvent.KEYCODE_S,
+                        KeyEvent.KEYCODE_T,
+                        KeyEvent.KEYCODE_U,
+                        KeyEvent.KEYCODE_V,
+                        KeyEvent.KEYCODE_W,
+                        KeyEvent.KEYCODE_X,
+                        KeyEvent.KEYCODE_Y,
+                        KeyEvent.KEYCODE_Z,
+                        KeyEvent.KEYCODE_SPACE
+                };
+                Interaction typeAlphabet = Interaction.newKeyInput(new int[] {
+                        KeyEvent.KEYCODE_A,
+                        KeyEvent.KEYCODE_B,
+                        KeyEvent.KEYCODE_C,
+                        KeyEvent.KEYCODE_D,
+                        KeyEvent.KEYCODE_E,
+                        KeyEvent.KEYCODE_F,
+                        KeyEvent.KEYCODE_G,
+                        KeyEvent.KEYCODE_H,
+                        KeyEvent.KEYCODE_I,
+                        KeyEvent.KEYCODE_J,
+                        KeyEvent.KEYCODE_K,
+                        KeyEvent.KEYCODE_L,
+                        KeyEvent.KEYCODE_M,
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.KEYCODE_O,
+                        KeyEvent.KEYCODE_P,
+                        KeyEvent.KEYCODE_Q,
+                        KeyEvent.KEYCODE_R,
+                        KeyEvent.KEYCODE_S,
+                        KeyEvent.KEYCODE_T,
+                        KeyEvent.KEYCODE_U,
+                        KeyEvent.KEYCODE_V,
+                        KeyEvent.KEYCODE_W,
+                        KeyEvent.KEYCODE_X,
+                        KeyEvent.KEYCODE_Y,
+                        KeyEvent.KEYCODE_Z,
+                        KeyEvent.KEYCODE_SPACE,
+                });
+
+                for (int i = 0; i < 5; i++) {
+                    addInteraction(typeAlphabet);
+                }
+            }
+        });
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    private String getRunFilename() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append(System.currentTimeMillis());
+        return builder.toString();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
new file mode 100644
index 0000000..95fce38
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class FullScreenOverdrawActivity extends AppCompatActivity {
+
+    private Automator mAutomator;
+
+    private class OverdrawView extends View {
+        Paint paint = new Paint();
+        int mColorValue = 0;
+
+        public OverdrawView(Context context) {
+            super(context);
+        }
+
+        @SuppressWarnings("unused")
+        public void setColorValue(int colorValue) {
+            mColorValue = colorValue;
+            invalidate();
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+            objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
+            objectAnimator.setRepeatCount(100);
+            objectAnimator.start();
+            return super.onTouchEvent(event);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            paint.setColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+            for (int i = 0; i < 10; i++) {
+                canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final OverdrawView overdrawView = new OverdrawView(this);
+        overdrawView.setKeepScreenOn(true);
+        setContentView(overdrawView);
+
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_overdraw);
+
+        mAutomator = new Automator(name, runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+                    @Override
+                    public void onPostAutomate() {
+                        Intent result = new Intent();
+                        setResult(RESULT_OK, result);
+                        finish();
+                    }
+
+                    @Override
+                    public void onAutomate() {
+                        int[] coordinates = new int[2];
+                        overdrawView.getLocationOnScreen(coordinates);
+
+                        int x = coordinates[0];
+                        int y = coordinates[1];
+
+                        float width = overdrawView.getWidth();
+                        float height = overdrawView.getHeight();
+
+                        float middleX = (x + width) / 5;
+                        float middleY = (y + height) / 5;
+
+                        addInteraction(Interaction.newTap(middleX, middleY));
+                    }
+                });
+
+        mAutomator.start();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
new file mode 100644
index 0000000..4644ea1
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
@@ -0,0 +1,162 @@
+/*
+ * 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.benchmark.ui;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+public class ImageListViewScrollActivity extends ListViewScrollActivity {
+
+    private static final int LIST_SIZE = 100;
+
+    private static final int[] IMG_RES_ID = new int[]{
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+    };
+
+    private static Bitmap[] mBitmapCache = new Bitmap[IMG_RES_ID.length];
+
+    private static final String[] WORDS = Utils.buildStringList(LIST_SIZE);
+
+    private HashMap<View, BitmapWorkerTask> mInFlight = new HashMap<>();
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ImageListAdapter();
+    }
+
+    @Override
+    protected String getName() {
+        return getString(R.string.image_list_view_scroll_name);
+    }
+
+    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+        private final WeakReference<ImageView> imageViewReference;
+        private int data = 0;
+        private int cacheIdx = 0;
+        volatile boolean cancelled = false;
+
+        public BitmapWorkerTask(ImageView imageView, int cacheIdx) {
+            // Use a WeakReference to ensure the ImageView can be garbage collected
+            imageViewReference = new WeakReference<>(imageView);
+            this.cacheIdx = cacheIdx;
+        }
+
+        // Decode image in background.
+        @Override
+        protected Bitmap doInBackground(Integer... params) {
+            data = params[0];
+            return Utils.decodeSampledBitmapFromResource(getResources(), data, 100, 100);
+        }
+
+        // Once complete, see if ImageView is still around and set bitmap.
+        @Override
+        protected void onPostExecute(Bitmap bitmap) {
+            if (bitmap != null) {
+                final ImageView imageView = imageViewReference.get();
+                if (imageView != null) {
+                    if (!cancelled) {
+                        imageView.setImageBitmap(bitmap);
+                    }
+                    mBitmapCache[cacheIdx] = bitmap;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        for (int i = 0; i < mBitmapCache.length; i++) {
+            mBitmapCache[i] = null;
+        }
+    }
+
+    class ImageListAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return LIST_SIZE;
+        }
+
+        @Override
+        public Object getItem(int postition) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int postition) {
+            return postition;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(getBaseContext())
+                        .inflate(R.layout.image_scroll_list_item, parent, false);
+            }
+
+            ImageView imageView = (ImageView) convertView.findViewById(R.id.image_scroll_image);
+            BitmapWorkerTask inFlight = mInFlight.get(convertView);
+            if (inFlight != null) {
+                inFlight.cancelled = true;
+                mInFlight.remove(convertView);
+            }
+
+            int cacheIdx = position % IMG_RES_ID.length;
+            Bitmap bitmap = mBitmapCache[(cacheIdx)];
+            if (bitmap == null) {
+                BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, cacheIdx);
+                bitmapWorkerTask.execute(IMG_RES_ID[(cacheIdx)]);
+                mInFlight.put(convertView, bitmapWorkerTask);
+            }
+
+            imageView.setImageBitmap(bitmap);
+
+            TextView textView = (TextView) convertView.findViewById(R.id.image_scroll_text);
+            textView.setText(WORDS[position]);
+
+            return convertView;
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
new file mode 100644
index 0000000..b973bc7
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
@@ -0,0 +1,55 @@
+/*
+ * 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.benchmark.ui;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Window;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+
+/**
+ * Simple list activity base class
+ */
+public abstract class ListActivityBase extends AppCompatActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_fragment);
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(getName());
+        }
+
+        if (findViewById(R.id.list_fragment_container) != null) {
+            FragmentManager fm = getSupportFragmentManager();
+            ListFragment listView = new ListFragment();
+            listView.setListAdapter(createListAdapter());
+            fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+        }
+    }
+
+    protected abstract ListAdapter createListAdapter();
+    protected abstract String getName();
+}
+
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
new file mode 100644
index 0000000..3ffb770
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+import java.util.List;
+
+public class ListViewScrollActivity extends ListActivityBase {
+
+    private static final int LIST_SIZE = 400;
+    private static final int INTERACTION_COUNT = 4;
+
+    private Automator mAutomator;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(getTitle());
+        }
+
+        mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onPostInteraction(List<FrameMetrics> metrics) {}
+
+            @Override
+            public void onAutomate() {
+                FrameLayout v = (FrameLayout) findViewById(R.id.list_fragment_container);
+
+                int[] coordinates = new int[2];
+                v.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = v.getWidth();
+                float height = v.getHeight();
+
+                float middleX = (x + width) / 5;
+                float middleY = (y + height) / 5;
+
+                Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                for (int i = 0; i < INTERACTION_COUNT; i++) {
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                }
+            }
+        });
+
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                Utils.buildStringList(LIST_SIZE));
+    }
+
+    @Override
+    protected String getName() {
+        return getString(R.string.list_view_scroll_name);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
new file mode 100644
index 0000000..68f75a3
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
@@ -0,0 +1,107 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class ShadowGridActivity extends AppCompatActivity {
+    private Automator mAutomator;
+    public static class MyListFragment extends ListFragment {
+	    @Override
+	    public void onViewCreated(View view, Bundle savedInstanceState) {
+		    super.onViewCreated(view, savedInstanceState);
+		    getListView().setDivider(null);
+	    }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        FragmentManager fm = getSupportFragmentManager();
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            ListFragment listFragment = new MyListFragment();
+
+            listFragment.setListAdapter(new ArrayAdapter<>(this,
+                    R.layout.card_row, R.id.card_text, Utils.buildStringList(200)));
+            fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+
+            String testName = getString(R.string.shadow_grid_name);
+
+            mAutomator = new Automator(testName, runId, iteration, getWindow(),
+                    new Automator.AutomateCallback() {
+                @Override
+                public void onPostAutomate() {
+                    Intent result = new Intent();
+                    setResult(RESULT_OK, result);
+                    finish();
+                }
+
+                @Override
+                public void onAutomate() {
+                    ListView v = (ListView) findViewById(android.R.id.list);
+
+                    int[] coordinates = new int[2];
+                    v.getLocationOnScreen(coordinates);
+
+                    int x = coordinates[0];
+                    int y = coordinates[1];
+
+                    float width = v.getWidth();
+                    float height = v.getHeight();
+
+                    float middleX = (x + width) / 2;
+                    float middleY = (y + height) / 2;
+
+                    Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                    Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                }
+            });
+            mAutomator.start();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
new file mode 100644
index 0000000..fcd168e
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
@@ -0,0 +1,118 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+
+public class TextScrollActivity extends ListActivityBase {
+
+    public static final String EXTRA_HIT_RATE = ".TextScrollActivity.EXTRA_HIT_RATE";
+
+    private static final int PARAGRAPH_COUNT = 200;
+
+    private int mHitPercentage = 100;
+    private Automator mAutomator;
+    private String mName;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mHitPercentage = getIntent().getIntExtra(EXTRA_HIT_RATE,
+                mHitPercentage);
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+        final int id = getIntent().getIntExtra(BenchmarkRegistry.EXTRA_ID, -1);
+
+        if (id == -1) {
+            finish();
+            return;
+        }
+
+        mName = BenchmarkRegistry.getBenchmarkName(this, id);
+
+        mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onAutomate() {
+                ListView v = (ListView) findViewById(android.R.id.list);
+
+                int[] coordinates = new int[2];
+                v.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = v.getWidth();
+                float height = v.getHeight();
+
+                float middleX = (x + width) / 2;
+                float middleY = (y + height) / 2;
+
+                Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+            }
+        });
+
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                Utils.buildParagraphListWithHitPercentage(PARAGRAPH_COUNT, 80));
+    }
+
+    @Override
+    protected String getName() {
+        return mName;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
new file mode 100644
index 0000000..39f9206
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
@@ -0,0 +1,135 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.util.Random;
+
+public class Utils {
+
+    private static final int RANDOM_WORD_LENGTH = 10;
+
+    public static String getRandomWord(Random random, int length) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            char base = random.nextBoolean() ? 'A' : 'a';
+            char nextChar = (char)(random.nextInt(26) + base);
+            builder.append(nextChar);
+        }
+        return builder.toString();
+    }
+
+    public static String[] buildStringList(int count) {
+        Random random = new Random(0);
+        String[] result = new String[count];
+        for (int i = 0; i < count; i++) {
+            result[i] = getRandomWord(random, RANDOM_WORD_LENGTH);
+        }
+
+        return result;
+    }
+
+     // a small number of strings reused frequently, expected to hit
+    // in the word-granularity text layout cache
+    static final String[] CACHE_HIT_STRINGS = new String[] {
+            "a",
+            "small",
+            "number",
+            "of",
+            "strings",
+            "reused",
+            "frequently"
+    };
+
+    private static final int WORDS_IN_PARAGRAPH = 150;
+
+    // misses are fairly long 'words' to ensure they miss
+    private static final int PARAGRAPH_MISS_MIN_LENGTH = 4;
+    private static final int PARAGRAPH_MISS_MAX_LENGTH = 9;
+
+    static String[] buildParagraphListWithHitPercentage(int paragraphCount, int hitPercentage) {
+        if (hitPercentage < 0 || hitPercentage > 100) throw new IllegalArgumentException();
+
+        String[] strings = new String[paragraphCount];
+        Random random = new Random(0);
+        for (int i = 0; i < strings.length; i++) {
+            StringBuilder result = new StringBuilder();
+            for (int word = 0; word < WORDS_IN_PARAGRAPH; word++) {
+                if (word != 0) {
+                    result.append(" ");
+                }
+                if (random.nextInt(100) < hitPercentage) {
+                    // add a common word, which is very likely to hit in the cache
+                    result.append(CACHE_HIT_STRINGS[random.nextInt(CACHE_HIT_STRINGS.length)]);
+                } else {
+                    // construct a random word, which will *most likely* miss
+                    int length = PARAGRAPH_MISS_MIN_LENGTH;
+                    length += random.nextInt(PARAGRAPH_MISS_MAX_LENGTH - PARAGRAPH_MISS_MIN_LENGTH);
+
+                    result.append(getRandomWord(random, length));
+                }
+            }
+            strings[i] = result.toString();
+        }
+
+        return strings;
+    }
+
+
+    public static int calculateInSampleSize(
+            BitmapFactory.Options options, int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            final int halfHeight = height / 2;
+            final int halfWidth = width / 2;
+
+            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+            // height and width larger than the requested height and width.
+            while ((halfHeight / inSampleSize) > reqHeight
+                    && (halfWidth / inSampleSize) > reqWidth) {
+                inSampleSize *= 2;
+            }
+        }
+
+        return inSampleSize;
+    }
+
+    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
+                                                   int reqWidth, int reqHeight) {
+
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeResource(res, resId, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeResource(res, resId, options);
+    }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
new file mode 100644
index 0000000..1efd6bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
@@ -0,0 +1,269 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@TargetApi(24)
+public class Automator extends HandlerThread
+        implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener {
+    public static final long FRAME_PERIOD_MILLIS = 16;
+
+    private static final int PRE_READY_STATE_COUNT = 3;
+    private static final String TAG = "Benchmark.Automator";
+    private final AtomicInteger mReadyState;
+
+    private AutomateCallback mCallback;
+    private Window mWindow;
+    private AutomatorHandler mHandler;
+    private CollectorThread mCollectorThread;
+    private int mRunId;
+    private int mIteration;
+    private String mTestName;
+
+    public static class AutomateCallback {
+        public void onAutomate() {}
+        public void onPostInteraction(List<FrameMetrics> metrics) {}
+        public void onPostAutomate() {}
+
+        protected final void addInteraction(Interaction interaction) {
+            if (mInteractions == null) {
+                return;
+            }
+
+            mInteractions.add(interaction);
+        }
+
+        protected final void setInteractions(List<Interaction> interactions) {
+            mInteractions = interactions;
+        }
+
+        private List<Interaction> mInteractions;
+    }
+
+    private static final class AutomatorHandler extends Handler {
+        public static final int MSG_NEXT_INTERACTION = 0;
+        public static final int MSG_ON_AUTOMATE = 1;
+        public static final int MSG_ON_POST_INTERACTION = 2;
+        private final String mTestName;
+        private final int mRunId;
+        private final int mIteration;
+
+        private Instrumentation mInstrumentation;
+        private volatile boolean mCancelled;
+        private CollectorThread mCollectorThread;
+        private AutomateCallback mCallback;
+        private Window mWindow;
+
+        LinkedList<Interaction> mInteractions;
+        private UiBenchmarkResult mResults;
+
+        AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread,
+                         AutomateCallback callback, String testName, int runId, int iteration) {
+            super(looper);
+
+            mInstrumentation = new Instrumentation();
+
+            mCallback = callback;
+            mWindow = window;
+            mCollectorThread = collectorThread;
+            mInteractions = new LinkedList<>();
+            mTestName = testName;
+            mRunId = runId;
+            mIteration = iteration;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCancelled) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_NEXT_INTERACTION:
+                    if (!nextInteraction()) {
+                        stopCollector();
+                        writeResults();
+                        mCallback.onPostAutomate();
+                    }
+                    break;
+                case MSG_ON_AUTOMATE:
+                    mCollectorThread.attachToWindow(mWindow);
+                    mCallback.setInteractions(mInteractions);
+                    mCallback.onAutomate();
+                    postNextInteraction();
+                    break;
+                case MSG_ON_POST_INTERACTION:
+                    List<FrameMetrics> collectedStats = (List<FrameMetrics>)msg.obj;
+                    persistResults(collectedStats);
+                    mCallback.onPostInteraction(collectedStats);
+                    postNextInteraction();
+                    break;
+            }
+        }
+
+        public void cancel() {
+            mCancelled = true;
+            stopCollector();
+        }
+
+        private void stopCollector() {
+            mCollectorThread.quitCollector();
+        }
+
+        private boolean nextInteraction() {
+
+            Interaction interaction = mInteractions.poll();
+            if (interaction != null) {
+                doInteraction(interaction);
+                return true;
+            }
+            return false;
+        }
+
+        private void doInteraction(Interaction interaction) {
+            if (mCancelled) {
+                return;
+            }
+
+            mCollectorThread.markInteractionStart();
+
+            if (interaction.getType() == Interaction.Type.KEY_EVENT) {
+                for (int code : interaction.getKeyCodes()) {
+                    if (!mCancelled) {
+                        mInstrumentation.sendKeyDownUpSync(code);
+                    } else {
+                        break;
+                    }
+                }
+            } else {
+                for (MotionEvent event : interaction.getEvents()) {
+                    if (!mCancelled) {
+                        mInstrumentation.sendPointerSync(event);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+
+        protected void postNextInteraction() {
+            final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION);
+            sendMessage(msg);
+        }
+
+        private void persistResults(List<FrameMetrics> stats) {
+            if (stats.isEmpty()) {
+                return;
+            }
+
+            if (mResults == null) {
+                mResults = new UiBenchmarkResult(stats);
+            } else {
+                mResults.update(stats);
+            }
+        }
+
+        private void writeResults() {
+            GlobalResultsStore.getInstance(mWindow.getContext())
+                    .storeRunResults(mTestName, mRunId, mIteration, mResults);
+        }
+    }
+
+    private void initHandler() {
+        mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback,
+                mTestName, mRunId, mIteration);
+        mWindow = null;
+        mCallback = null;
+        mCollectorThread = null;
+        mTestName = null;
+        mRunId = 0;
+        mIteration = 0;
+    }
+
+    @Override
+    public final void onGlobalLayout() {
+        if (!mCollectorThread.isAlive()) {
+            mCollectorThread.start();
+            mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            mReadyState.decrementAndGet();
+        }
+    }
+
+    @Override
+    public void onCollectorThreadReady() {
+        if (mReadyState.decrementAndGet() == 0) {
+            initHandler();
+            postOnAutomate();
+        }
+    }
+
+    @Override
+    protected void onLooperPrepared() {
+        if (mReadyState.decrementAndGet() == 0) {
+            initHandler();
+            postOnAutomate();
+        }
+    }
+
+    @Override
+    public void onPostInteraction(List<FrameMetrics> stats) {
+        Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats);
+        mHandler.sendMessage(m);
+    }
+
+    protected void postOnAutomate() {
+        final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE);
+        mHandler.sendMessage(msg);
+    }
+
+    public void cancel() {
+        mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION);
+        mHandler.cancel();
+        mHandler = null;
+    }
+
+    public Automator(String testName, int runId, int iteration,
+                     Window window, AutomateCallback callback) {
+        super("AutomatorThread");
+
+        mTestName = testName;
+        mRunId = runId;
+        mIteration = iteration;
+        mCallback = callback;
+        mWindow = window;
+        mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this);
+        mCollectorThread = new CollectorThread(this);
+        mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
new file mode 100644
index 0000000..806c704
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
@@ -0,0 +1,164 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.FrameMetrics;
+import android.view.Window;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+final class CollectorThread extends HandlerThread {
+    private FrameStatsCollector mCollector;
+    private Window mAttachedWindow;
+    private List<FrameMetrics> mFrameTimingStats;
+    private long mLastFrameTime;
+    private WatchdogHandler mWatchdog;
+    private WeakReference<CollectorListener> mListener;
+
+    private volatile boolean mCollecting;
+
+
+    interface CollectorListener {
+        void onCollectorThreadReady();
+        void onPostInteraction(List<FrameMetrics> stats);
+    }
+
+    private final class WatchdogHandler extends Handler {
+        private static final long SCHEDULE_INTERVAL_MILLIS = 20 * Automator.FRAME_PERIOD_MILLIS;
+
+        private static final int MSG_SCHEDULE = 0;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (!mCollecting) {
+                return;
+            }
+
+            long currentTime = SystemClock.uptimeMillis();
+            if (mLastFrameTime + SCHEDULE_INTERVAL_MILLIS <= currentTime) {
+                // haven't seen a frame in a while, interaction is probably done
+                mCollecting = false;
+                CollectorListener listener = mListener.get();
+                if (listener != null) {
+                    listener.onPostInteraction(mFrameTimingStats);
+                }
+            } else {
+                schedule();
+            }
+        }
+
+        public void schedule() {
+            sendMessageDelayed(obtainMessage(MSG_SCHEDULE), SCHEDULE_INTERVAL_MILLIS);
+        }
+
+        public void deschedule() {
+            removeMessages(MSG_SCHEDULE);
+        }
+    }
+
+    static boolean tripleBuffered = false;
+    static int janks = 0;
+    static int total = 0;
+    @TargetApi(24)
+    private class FrameStatsCollector implements Window.OnFrameMetricsAvailableListener {
+        @Override
+        public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+            if (!mCollecting) {
+                return;
+            }
+            mFrameTimingStats.add(new FrameMetrics(frameMetrics));
+            mLastFrameTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    public CollectorThread(CollectorListener listener) {
+        super("FrameStatsCollectorThread");
+        mFrameTimingStats = new LinkedList<>();
+        mListener = new WeakReference<>(listener);
+    }
+
+    @TargetApi(24)
+    public void attachToWindow(Window window) {
+        if (mAttachedWindow != null) {
+            mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+        }
+
+        mAttachedWindow = window;
+        window.addOnFrameMetricsAvailableListener(mCollector, new Handler(getLooper()));
+    }
+
+    @TargetApi(24)
+    public synchronized void detachFromWindow() {
+        if (mAttachedWindow != null) {
+            mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+        }
+
+        mAttachedWindow = null;
+    }
+
+    @TargetApi(24)
+    @Override
+    protected void onLooperPrepared() {
+        super.onLooperPrepared();
+        mCollector = new FrameStatsCollector();
+        mWatchdog = new WatchdogHandler();
+
+        CollectorListener listener = mListener.get();
+        if (listener != null) {
+            listener.onCollectorThreadReady();
+        }
+    }
+
+    public boolean quitCollector() {
+        stopCollecting();
+        detachFromWindow();
+        System.out.println("Jank Percentage: " + (100 * janks/ (double) total) + "%");
+        tripleBuffered = false;
+        total = 0;
+        janks = 0;
+        return quit();
+    }
+
+    void stopCollecting() {
+        if (!mCollecting) {
+            return;
+        }
+
+        mCollecting = false;
+        mWatchdog.deschedule();
+
+
+    }
+
+    public void markInteractionStart() {
+        mLastFrameTime = 0;
+        mFrameTimingStats.clear();
+        mCollecting = true;
+
+        mWatchdog.schedule();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
new file mode 100644
index 0000000..1fd0998
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
@@ -0,0 +1,86 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.support.annotation.IntDef;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class FrameTimingStats {
+    @IntDef ({
+            Index.FLAGS,
+            Index.INTENDED_VSYNC,
+            Index.VSYNC,
+            Index.OLDEST_INPUT_EVENT,
+            Index.NEWEST_INPUT_EVENT,
+            Index.HANDLE_INPUT_START,
+            Index.ANIMATION_START,
+            Index.PERFORM_TRAVERSALS_START,
+            Index.DRAW_START,
+            Index.SYNC_QUEUED,
+            Index.SYNC_START,
+            Index.ISSUE_DRAW_COMMANDS_START,
+            Index.SWAP_BUFFERS,
+            Index.FRAME_COMPLETED,
+    })
+    public @interface Index {
+        int FLAGS = 0;
+        int INTENDED_VSYNC = 1;
+        int VSYNC = 2;
+        int OLDEST_INPUT_EVENT = 3;
+        int NEWEST_INPUT_EVENT = 4;
+        int HANDLE_INPUT_START = 5;
+        int ANIMATION_START = 6;
+        int PERFORM_TRAVERSALS_START = 7;
+        int DRAW_START = 8;
+        int SYNC_QUEUED = 9;
+        int SYNC_START = 10;
+        int ISSUE_DRAW_COMMANDS_START = 11;
+        int SWAP_BUFFERS = 12;
+        int FRAME_COMPLETED = 13;
+
+        int FRAME_STATS_COUNT = 14; // must always be last
+    }
+
+    private final long[] mStats;
+
+    FrameTimingStats(long[] stats) {
+        mStats = Arrays.copyOf(stats, Index.FRAME_STATS_COUNT);
+    }
+
+    public FrameTimingStats(DataInputStream inputStream) throws IOException {
+        mStats = new long[Index.FRAME_STATS_COUNT];
+        update(inputStream);
+    }
+
+    public void update(DataInputStream inputStream) throws IOException {
+        for (int i = 0; i < mStats.length; i++) {
+            mStats[i] = inputStream.readLong();
+        }
+    }
+
+    public long get(@Index int index) {
+        return mStats[index];
+    }
+
+    public long[] data() {
+        return mStats;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
new file mode 100644
index 0000000..370fed2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
@@ -0,0 +1,187 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.os.SystemClock;
+import android.support.annotation.IntDef;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encodes a UI interaction as a series of MotionEvents
+ */
+public class Interaction {
+    private static final int STEP_COUNT = 20;
+    // TODO: scale to device display density
+    private static final int DEFAULT_FLING_SIZE_PX = 500;
+    private static final int DEFAULT_FLING_DURATION_MS = 20;
+    private static final int DEFAULT_TAP_DURATION_MS = 20;
+    private List<MotionEvent> mEvents;
+
+    // Interaction parameters
+    private final float[] mXPositions;
+    private final float[] mYPositions;
+    private final long mDuration;
+    private final int[] mKeyCodes;
+    private final @Interaction.Type int mType;
+
+    @IntDef({
+            Interaction.Type.TAP,
+            Interaction.Type.FLING,
+            Interaction.Type.PINCH,
+            Interaction.Type.KEY_EVENT})
+    public @interface Type {
+        int TAP = 0;
+        int FLING = 1;
+        int PINCH = 2;
+        int KEY_EVENT = 3;
+    }
+
+    public static Interaction newFling(float startX, float startY,
+                                       float endX, float endY, long duration) {
+       return new Interaction(Interaction.Type.FLING, new float[]{startX, endX},
+               new float[]{startY, endY}, duration);
+    }
+
+    public static Interaction newFlingDown(float startX, float startY) {
+        return new Interaction(Interaction.Type.FLING,
+                new float[]{startX, startX},
+                new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newFlingUp(float startX, float startY) {
+        return new Interaction(Interaction.Type.FLING,
+                new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX},
+                        DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newTap(float startX, float startY) {
+        return new Interaction(Interaction.Type.TAP,
+                new float[]{startX, startX}, new float[]{startY, startY},
+                DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newKeyInput(int[] keyCodes) {
+        return new Interaction(keyCodes);
+    }
+
+    public List<MotionEvent> getEvents() {
+        switch (mType) {
+            case Type.FLING:
+                mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+                break;
+            case Type.PINCH:
+                break;
+            case Type.TAP:
+                mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+                break;
+        }
+
+        return mEvents;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public int[] getKeyCodes() {
+        return mKeyCodes;
+    }
+
+    private static List<MotionEvent> createInterpolatedEventList(
+            float[] xPos, float[] yPos, long duration) {
+        long startTime = SystemClock.uptimeMillis() + 100;
+        List<MotionEvent> result = new ArrayList<>();
+
+        float startX = xPos[0];
+        float startY = yPos[0];
+
+        MotionEvent downEvent = MotionEvent.obtain(
+                startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0);
+        result.add(downEvent);
+
+        for (int i = 1; i < xPos.length; i++) {
+            float endX = xPos[i];
+            float endY = yPos[i];
+            float stepX = (endX - startX) / STEP_COUNT;
+            float stepY = (endY - startY) / STEP_COUNT;
+            float stepT = duration / STEP_COUNT;
+
+            for (int j = 0; j < STEP_COUNT; j++) {
+                long deltaT = Math.round(j * stepT);
+                long deltaX = Math.round(j * stepX);
+                long deltaY = Math.round(j * stepY);
+                MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT,
+                        MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0);
+                result.add(moveEvent);
+            }
+
+            startX = endX;
+            startY = endY;
+        }
+
+        float lastX = xPos[xPos.length - 1];
+        float lastY = yPos[yPos.length - 1];
+        MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration,
+                MotionEvent.ACTION_UP, lastX, lastY, 0);
+        result.add(lastEvent);
+
+        return result;
+    }
+
+    private Interaction(@Interaction.Type int type,
+                        float[] xPos, float[] yPos, long duration) {
+        mType = type;
+        mXPositions = xPos;
+        mYPositions = yPos;
+        mDuration = duration;
+        mKeyCodes = null;
+    }
+
+    private Interaction(int[] codes) {
+        mKeyCodes = codes;
+        mType = Type.KEY_EVENT;
+        mYPositions = null;
+        mXPositions = null;
+        mDuration = 0;
+    }
+
+    private Interaction(@Interaction.Type int type,
+                        List<Float> xPositions, List<Float> yPositions, long duration) {
+        if (xPositions.size() != yPositions.size()) {
+            throw new IllegalArgumentException("must have equal number of x and y positions");
+        }
+
+        int current = 0;
+        mXPositions = new float[xPositions.size()];
+        for (float p : xPositions) {
+            mXPositions[current++] = p;
+        }
+
+        current = 0;
+        mYPositions = new float[yPositions.size()];
+        for (float p : xPositions) {
+            mXPositions[current++] = p;
+        }
+
+        mType = type;
+        mDuration = duration;
+        mKeyCodes = null;
+    }
+}
diff --git a/tests/JankBench/app/src/main/jni/Android.mk b/tests/JankBench/app/src/main/jni/Android.mk
new file mode 100644
index 0000000..8ba874de
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+LOCAL_SDK_VERSION := 26
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS = -Wno-unused-parameter
+
+LOCAL_MODULE:= libnativebench
+
+LOCAL_SRC_FILES := \
+	Bench.cpp \
+	WorkerPool.cpp \
+	test.cpp
+
+LOCAL_LDLIBS := -llog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/JankBench/app/src/main/jni/Application.mk b/tests/JankBench/app/src/main/jni/Application.mk
new file mode 100644
index 0000000..09bc0ac
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Application.mk
@@ -0,0 +1,17 @@
+# 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.
+
+APP_ABI := armeabi 
+
+APP_MODULES := nativebench
diff --git a/tests/JankBench/app/src/main/jni/Bench.cpp b/tests/JankBench/app/src/main/jni/Bench.cpp
new file mode 100644
index 0000000..fbb4f11
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.cpp
@@ -0,0 +1,373 @@
+/*
+ * 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.
+ */
+
+#include <android/log.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "Bench.h"
+
+
+Bench::Bench()
+{
+    mTimeBucket = NULL;
+    mTimeBuckets = 0;
+    mTimeBucketDivisor = 1;
+
+    mMemLatencyLastSize = 0;
+    mMemDst = NULL;
+    mMemSrc = NULL;
+    mMemLoopCount = 0;
+}
+
+
+Bench::~Bench()
+{
+}
+
+uint64_t Bench::getTimeNanos() const
+{
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+uint64_t Bench::getTimeMillis() const
+{
+    return getTimeNanos() / 1000000;
+}
+
+
+void Bench::testWork(void *usr, uint32_t idx)
+{
+    Bench *b = (Bench *)usr;
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i   %p", idx, b);
+
+    float f1 = 0.f;
+    float f2 = 0.f;
+    float f3 = 0.f;
+    float f4 = 0.f;
+
+    float *ipk = b->mIpKernel[idx];
+    volatile float *src = b->mSrcBuf[idx];
+    volatile float *out = b->mOutBuf[idx];
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %p %p %p", ipk, src, out);
+
+    do {
+
+        for (int i = 0; i < 1024; i++) {
+            f1 += src[i * 4] * ipk[i];
+            f2 += src[i * 4 + 1] * ipk[i];
+            f3 += src[i * 4 + 2] * ipk[i];
+            f4 += sqrtf(f1 + f2 + f3);
+        }
+        out[0] = f1;
+        out[1] = f2;
+        out[2] = f3;
+        out[3] = f4;
+
+    } while (b->incTimeBucket());
+}
+
+bool Bench::initIP() {
+    int workers = mWorkers.getWorkerCount();
+
+    mIpKernel = new float *[workers];
+    mSrcBuf = new float *[workers];
+    mOutBuf = new float *[workers];
+
+    for (int i = 0; i < workers; i++) {
+        mIpKernel[i] = new float[1024];
+        mSrcBuf[i] = new float[4096];
+        mOutBuf[i] = new float[4];
+    }
+
+    return true;
+}
+
+bool Bench::runPowerManagementTest(uint64_t options) {
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt x %i", options);
+
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(2 * 1000);
+
+    usleep(2 * 1000 * 1000);
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2  b %i", mTimeBuckets);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    bool useMT = false;
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2.1  b %i", mTimeBuckets);
+    mTimeEndGroupNanos = mTimeStartNanos;
+    do  {
+        // Advance 8ms
+        mTimeEndGroupNanos += 8 * 1000 * 1000;
+
+        int threads = useMT ? 1 : 0;
+        useMT = !useMT;
+        if ((options & 0x1f) != 0) {
+            threads = options & 0x1f;
+        }
+
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "threads %i", threads);
+
+        mWorkers.launchWork(testWork, this, threads);
+    } while (mTimeEndGroupNanos <= mTimeEndNanos);
+
+    return true;
+}
+
+bool Bench::allocateBuckets(size_t bucketCount) {
+    if (bucketCount == mTimeBuckets) {
+        return true;
+    }
+
+    if (mTimeBucket != NULL) {
+        delete[] mTimeBucket;
+        mTimeBucket = NULL;
+    }
+
+    mTimeBuckets = bucketCount;
+    if (mTimeBuckets > 0) {
+        mTimeBucket = new uint32_t[mTimeBuckets];
+    }
+
+    return true;
+}
+
+bool Bench::init() {
+    mWorkers.init();
+
+    initIP();
+    //ALOGV("%p Launching thread(s), CPUs %i", mRSC, mWorkers.mCount + 1);
+
+    return true;
+}
+
+bool Bench::incTimeBucket() const {
+    uint64_t time = getTimeNanos();
+    uint64_t bucket = (time - mTimeStartNanos) / mTimeBucketDivisor;
+
+    if (bucket >= mTimeBuckets) {
+        return false;
+    }
+
+    __sync_fetch_and_add(&mTimeBucket[bucket], 1);
+
+    return time < mTimeEndGroupNanos;
+}
+
+void Bench::getData(float *data, size_t count) const {
+    if (count > mTimeBuckets) {
+        count = mTimeBuckets;
+    }
+    for (size_t ct = 0; ct < count; ct++) {
+        data[ct] = (float)mTimeBucket[ct];
+    }
+}
+
+bool Bench::runCPUHeatSoak(uint64_t /* options */)
+{
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(1000);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    mTimeEndGroupNanos = mTimeEndNanos;
+    mWorkers.launchWork(testWork, this, 0);
+    return true;
+}
+
+float Bench::runMemoryBandwidthTest(uint64_t size)
+{
+    uint64_t t1 = getTimeMillis();
+    for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+        memcpy(mMemDst, mMemSrc, size);
+    }
+    double dt = getTimeMillis() - t1;
+    dt /= 1000;
+
+    double bw = ((double)size) * mMemLoopCount / dt;
+    bw /= 1024 * 1024 * 1024;
+
+    float targetTime = 0.2f;
+    if (dt > targetTime) {
+        mMemLoopCount = (size_t)((double)mMemLoopCount / (dt / targetTime));
+    }
+
+    return (float)bw;
+}
+
+float Bench::runMemoryLatencyTest(uint64_t size)
+{
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "latency %i", (int)size);
+    void ** sp = (void **)mMemSrc;
+    size_t maxIndex = size / sizeof(void *);
+    size_t loops = ((maxIndex / 2) & (~3));
+    //loops = 10;
+
+    if (size != mMemLatencyLastSize) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "latency build %i %i", (int)maxIndex, loops);
+        mMemLatencyLastSize = size;
+        memset((void *)mMemSrc, 0, mMemLatencyLastSize);
+
+        size_t lastIdx = 0;
+        for (size_t ct = 0; ct < loops; ct++) {
+            size_t ni = rand() * rand();
+            ni = ni % maxIndex;
+            while ((sp[ni] != NULL) || (ni == lastIdx)) {
+                ni++;
+                if (ni >= maxIndex) {
+                    ni = 1;
+                }
+    //            __android_log_print(ANDROID_LOG_INFO, "bench", "gen ni loop %i %i", lastIdx, ni);
+            }
+      //      __android_log_print(ANDROID_LOG_INFO, "bench", "gen ct = %i  %i  %i  %p  %p", (int)ct, lastIdx, ni, &sp[lastIdx], &sp[ni]);
+            sp[lastIdx] = &sp[ni];
+            lastIdx = ni;
+        }
+        sp[lastIdx] = 0;
+    }
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "latency testing");
+
+    uint64_t t1 = getTimeNanos();
+    for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+        size_t lc = 1;
+        volatile void *p = sp[0];
+        while (p != NULL) {
+            // Unroll once to minimize branching overhead.
+            void **pn = (void **)p;
+            p = pn[0];
+            pn = (void **)p;
+            p = pn[0];
+        }
+    }
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "v %i %i", loops * mMemLoopCount, v);
+
+    double dt = getTimeNanos() - t1;
+    double dts = dt / 1000000000;
+    double lat = dt / (loops * mMemLoopCount);
+    __android_log_print(ANDROID_LOG_INFO, "bench", "latency ret %f", lat);
+
+    float targetTime = 0.2f;
+    if (dts > targetTime) {
+        mMemLoopCount = (size_t)((double)mMemLoopCount / (dts / targetTime));
+        if (mMemLoopCount < 1) {
+            mMemLoopCount = 1;
+        }
+    }
+
+    return (float)lat;
+}
+
+bool Bench::startMemTests()
+{
+    mMemSrc = (uint8_t *)malloc(1024*1024*64);
+    mMemDst = (uint8_t *)malloc(1024*1024*64);
+
+    memset(mMemSrc, 0, 1024*1024*16);
+    memset(mMemDst, 0, 1024*1024*16);
+
+    mMemLoopCount = 1;
+    uint64_t start = getTimeMillis();
+    while((getTimeMillis() - start) < 500) {
+        memcpy(mMemDst, mMemSrc, 1024);
+        mMemLoopCount++;
+    }
+    mMemLatencyLastSize = 0;
+    return true;
+}
+
+void Bench::endMemTests()
+{
+    free(mMemSrc);
+    free(mMemDst);
+    mMemSrc = NULL;
+    mMemDst = NULL;
+    mMemLatencyLastSize = 0;
+}
+
+void Bench::GflopKernelC() {
+    int halfKX = (mGFlop.kernelXSize / 2);
+    for (int x = halfKX; x < (mGFlop.imageXSize - halfKX - 1); x++) {
+        const float * krnPtr = mGFlop.kernelBuffer;
+        float sum = 0.f;
+
+        int srcInc = mGFlop.imageXSize - mGFlop.kernelXSize;
+        const float * srcPtr = &mGFlop.srcBuffer[x - halfKX];
+
+        for (int ix = 0; ix < mGFlop.kernelXSize; ix++) {
+            sum += srcPtr[0] * krnPtr[0];
+            krnPtr++;
+            srcPtr++;
+        }
+
+        float * dstPtr = &mGFlop.dstBuffer[x];
+        dstPtr[0] = sum;
+
+    }
+
+}
+
+void Bench::GflopKernelC_y3() {
+}
+
+float Bench::runGFlopsTest(uint64_t /* options */)
+{
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(1000);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    mTimeEndGroupNanos = mTimeEndNanos;
+    mWorkers.launchWork(testWork, this, 0);
+
+    // Simulate image convolve
+    mGFlop.kernelXSize = 27;
+    mGFlop.imageXSize = 1024 * 1024;
+
+    mGFlop.srcBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+    mGFlop.dstBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+    mGFlop.kernelBuffer = (float *)malloc(mGFlop.kernelXSize * sizeof(float));
+
+    double ops = mGFlop.kernelXSize;
+    ops = ops * 2.f - 1.f;
+    ops *= mGFlop.imageXSize;
+
+    uint64_t t1 = getTimeNanos();
+    GflopKernelC();
+    double dt = getTimeNanos() - t1;
+
+    dt /= 1000.f * 1000.f * 1000.f;
+
+    double gflops = ops / dt / 1000000000.f;
+
+    __android_log_print(ANDROID_LOG_INFO, "bench", "v %f %f %f", dt, ops, gflops);
+
+    return (float)gflops;
+}
+
+
diff --git a/tests/JankBench/app/src/main/jni/Bench.h b/tests/JankBench/app/src/main/jni/Bench.h
new file mode 100644
index 0000000..43a9066
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_BENCH_H
+#define ANDROID_BENCH_H
+
+#include <pthread.h>
+
+#include "WorkerPool.h"
+
+#include <string.h>
+
+
+
+class Bench {
+public:
+    Bench();
+    ~Bench();
+
+    struct GFlop {
+        int kernelXSize;
+        //int kernelYSize;
+        int imageXSize;
+        //int imageYSize;
+
+        float *srcBuffer;
+        float *kernelBuffer;
+        float *dstBuffer;
+
+
+    };
+    GFlop mGFlop;
+
+    bool init();
+
+    bool runPowerManagementTest(uint64_t options);
+    bool runCPUHeatSoak(uint64_t options);
+
+    bool startMemTests();
+    void endMemTests();
+    float runMemoryBandwidthTest(uint64_t options);
+    float runMemoryLatencyTest(uint64_t options);
+
+    float runGFlopsTest(uint64_t options);
+
+    void getData(float *data, size_t count) const;
+
+
+    void finish();
+
+    void setPriority(int32_t p);
+    void destroyWorkerThreadResources();
+
+    uint64_t getTimeNanos() const;
+    uint64_t getTimeMillis() const;
+
+    // Adds a work unit completed to the timeline and returns
+    // true if the test is ongoing, false if time is up
+    bool incTimeBucket() const;
+
+
+protected:
+    WorkerPool mWorkers;
+
+    bool mExit;
+    bool mPaused;
+
+    static void testWork(void *usr, uint32_t idx);
+
+private:
+    uint8_t * volatile mMemSrc;
+    uint8_t * volatile mMemDst;
+    size_t mMemLoopCount;
+    size_t mMemLatencyLastSize;
+
+
+    float ** mIpKernel;
+    float * volatile * mSrcBuf;
+    float * volatile * mOutBuf;
+    uint32_t * mTimeBucket;
+
+    uint64_t mTimeStartNanos;
+    uint64_t mTimeEndNanos;
+    uint64_t mTimeBucketDivisor;
+    uint32_t mTimeBuckets;
+
+    uint64_t mTimeEndGroupNanos;
+
+    bool initIP();
+    void GflopKernelC();
+    void GflopKernelC_y3();
+
+    bool allocateBuckets(size_t);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.cpp b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
new file mode 100644
index 0000000..a92ac91
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+#include "WorkerPool.h"
+//#include <atomic>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <android/log.h>
+
+
+//static pthread_key_t gThreadTLSKey = 0;
+//static uint32_t gThreadTLSKeyCount = 0;
+//static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+WorkerPool::Signal::Signal() {
+    mSet = true;
+}
+
+WorkerPool::Signal::~Signal() {
+    pthread_mutex_destroy(&mMutex);
+    pthread_cond_destroy(&mCondition);
+}
+
+bool WorkerPool::Signal::init() {
+    int status = pthread_mutex_init(&mMutex, NULL);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool mutex init failure");
+        return false;
+    }
+
+    status = pthread_cond_init(&mCondition, NULL);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool condition init failure");
+        pthread_mutex_destroy(&mMutex);
+        return false;
+    }
+
+    return true;
+}
+
+void WorkerPool::Signal::set() {
+    int status;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for set condition.", status);
+        return;
+    }
+
+    mSet = true;
+
+    status = pthread_cond_signal(&mCondition);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i on set condition.", status);
+    }
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for set condition.", status);
+    }
+}
+
+bool WorkerPool::Signal::wait(uint64_t timeout) {
+    int status;
+    bool ret = false;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for condition.", status);
+        return false;
+    }
+
+    if (!mSet) {
+        if (!timeout) {
+            status = pthread_cond_wait(&mCondition, &mMutex);
+        } else {
+#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
+            status = pthread_cond_timeout_np(&mCondition, &mMutex, timeout / 1000000);
+#else
+            // This is safe it will just make things less reponsive
+            status = pthread_cond_wait(&mCondition, &mMutex);
+#endif
+        }
+    }
+
+    if (!status) {
+        mSet = false;
+        ret = true;
+    } else {
+#ifndef RS_SERVER
+        if (status != ETIMEDOUT) {
+            __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i waiting for condition.", status);
+        }
+#endif
+    }
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for condition.", status);
+    }
+
+    return ret;
+}
+
+
+
+WorkerPool::WorkerPool() {
+    mExit = false;
+    mRunningCount = 0;
+    mLaunchCount = 0;
+    mCount = 0;
+    mThreadId = NULL;
+    mNativeThreadId = NULL;
+    mLaunchSignals = NULL;
+    mLaunchCallback = NULL;
+
+
+}
+
+
+WorkerPool::~WorkerPool() {
+__android_log_print(ANDROID_LOG_INFO, "bench", "~wp");
+    mExit = true;
+    mLaunchData = NULL;
+    mLaunchCallback = NULL;
+    mRunningCount = mCount;
+
+    __sync_synchronize();
+    for (uint32_t ct = 0; ct < mCount; ct++) {
+        mLaunchSignals[ct].set();
+    }
+    void *res;
+    for (uint32_t ct = 0; ct < mCount; ct++) {
+        pthread_join(mThreadId[ct], &res);
+    }
+    //rsAssert(__sync_fetch_and_or(&mRunningCount, 0) == 0);
+    free(mThreadId);
+    free(mNativeThreadId);
+    delete[] mLaunchSignals;
+}
+
+bool WorkerPool::init(int threadCount) {
+    int cpu = sysconf(_SC_NPROCESSORS_CONF);
+    if (threadCount > 0) {
+        cpu = threadCount;
+    }
+    if (cpu < 1) {
+        return false;
+    }
+    mCount = (uint32_t)cpu;
+
+    __android_log_print(ANDROID_LOG_INFO, "Bench", "ThreadLaunch %i", mCount);
+
+    mThreadId = (pthread_t *) calloc(mCount, sizeof(pthread_t));
+    mNativeThreadId = (pid_t *) calloc(mCount, sizeof(pid_t));
+    mLaunchSignals = new Signal[mCount];
+    mLaunchCallback = NULL;
+
+    mCompleteSignal.init();
+    mRunningCount = mCount;
+    mLaunchCount = 0;
+    __sync_synchronize();
+
+    pthread_attr_t threadAttr;
+    int status = pthread_attr_init(&threadAttr);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "Failed to init thread attribute.");
+        return false;
+    }
+
+    for (uint32_t ct=0; ct < mCount; ct++) {
+        status = pthread_create(&mThreadId[ct], &threadAttr, helperThreadProc, this);
+        if (status) {
+            mCount = ct;
+            __android_log_print(ANDROID_LOG_INFO, "bench", "Created fewer than expected number of threads.");
+            return false;
+        }
+    }
+    while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+        usleep(100);
+    }
+
+    pthread_attr_destroy(&threadAttr);
+    return true;
+}
+
+void * WorkerPool::helperThreadProc(void *vwp) {
+    WorkerPool *wp = (WorkerPool *)vwp;
+
+    uint32_t idx = __sync_fetch_and_add(&wp->mLaunchCount, 1);
+
+    wp->mLaunchSignals[idx].init();
+    wp->mNativeThreadId[idx] = gettid();
+
+    while (!wp->mExit) {
+        wp->mLaunchSignals[idx].wait();
+        if (wp->mLaunchCallback) {
+           // idx +1 is used because the calling thread is always worker 0.
+           wp->mLaunchCallback(wp->mLaunchData, idx);
+        }
+        __sync_fetch_and_sub(&wp->mRunningCount, 1);
+        wp->mCompleteSignal.set();
+    }
+
+    //ALOGV("RS helperThread exited %p idx=%i", dc, idx);
+    return NULL;
+}
+
+
+void WorkerPool::waitForAll() const {
+}
+
+void WorkerPool::waitFor(uint64_t) const {
+}
+
+
+
+uint64_t WorkerPool::launchWork(WorkerCallback_t cb, void *usr, int maxThreads) {
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 1");
+    mLaunchData = usr;
+    mLaunchCallback = cb;
+
+    if (maxThreads < 1) {
+        maxThreads = mCount;
+    }
+    if ((uint32_t)maxThreads > mCount) {
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "launchWork max > count", maxThreads, mCount);
+        maxThreads = mCount;
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 2  %i  %i  %i", maxThreads, mRunningCount, mCount);
+    mRunningCount = maxThreads;
+    __sync_synchronize();
+
+    for (int ct = 0; ct < maxThreads; ct++) {
+        mLaunchSignals[ct].set();
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3    %i", mRunningCount);
+    while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3.1    %i", mRunningCount);
+        mCompleteSignal.wait();
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 4    %i", mRunningCount);
+    return 0;
+
+}
+
+
+
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.h b/tests/JankBench/app/src/main/jni/WorkerPool.h
new file mode 100644
index 0000000..f8985d2
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WORKER_POOL_H
+#define ANDROID_WORKER_POOL_H
+
+#include <pthread.h>
+#include <string.h>
+
+
+
+class WorkerPool {
+public:
+    WorkerPool();
+    ~WorkerPool();
+
+    typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
+
+    bool init(int threadCount = -1);
+    int getWorkerCount() const {return mCount;}
+
+    void waitForAll() const;
+    void waitFor(uint64_t) const;
+    uint64_t launchWork(WorkerCallback_t cb, void *usr, int maxThreads = -1);
+
+
+
+
+protected:
+    class Signal {
+    public:
+        Signal();
+        ~Signal();
+
+        bool init();
+        void set();
+
+        // returns true if the signal occured
+        // false for timeout
+        bool wait(uint64_t timeout = 0);
+
+    protected:
+        bool mSet;
+        pthread_mutex_t mMutex;
+        pthread_cond_t mCondition;
+    };
+
+    bool mExit;
+    volatile int mRunningCount;
+    volatile int mLaunchCount;
+    uint32_t mCount;
+    pthread_t *mThreadId;
+    pid_t *mNativeThreadId;
+    Signal mCompleteSignal;
+    Signal *mLaunchSignals;
+    WorkerCallback_t mLaunchCallback;
+    void *mLaunchData;
+
+
+
+
+private:
+    //static void * threadProc(void *);
+    static void * helperThreadProc(void *);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/test.cpp b/tests/JankBench/app/src/main/jni/test.cpp
new file mode 100644
index 0000000..e163daa
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/test.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+//#include <fcntl.h>
+//#include <unistd.h>
+#include <math.h>
+#include <inttypes.h>
+#include <time.h>
+#include <android/log.h>
+
+#include "jni.h"
+#include "Bench.h"
+
+#define FUNC(name) Java_com_android_benchmark_synthetic_TestInterface_##name
+
+static uint64_t GetTime() {
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+extern "C" {
+
+jlong Java_com_android_benchmark_synthetic_TestInterface_nInit(JNIEnv *_env, jobject _this, jlong options) {
+    Bench *b = new Bench();
+    bool ret = b->init();
+
+    if (ret) {
+        return (jlong)b;
+    }
+
+    delete b;
+    return 0;
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nDestroy(JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+
+    delete b;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunPowerManagementTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+    Bench *b = (Bench *)_b;
+    return b->runPowerManagementTest(options);
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunCPUHeatSoakTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+    Bench *b = (Bench *)_b;
+    return b->runCPUHeatSoak(options);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGetData(
+        JNIEnv *_env, jobject _this, jlong _b, jfloatArray data) {
+    Bench *b = (Bench *)_b;
+
+    jsize len = _env->GetArrayLength(data);
+    float * ptr = _env->GetFloatArrayElements(data, 0);
+
+    b->getData(ptr, len);
+
+    _env->ReleaseFloatArrayElements(data, (jfloat *)ptr, 0);
+
+    return 0;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nMemTestStart(
+        JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+    return b->startMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestBandwidth(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runMemoryBandwidthTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGFlopsTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runGFlopsTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestLatency(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runMemoryLatencyTest(opt);
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nMemTestEnd(
+        JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+    b->endMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemoryTest(
+        JNIEnv *_env, jobject _this, jint subtest) {
+
+    uint8_t * volatile m1 = (uint8_t *)malloc(1024*1024*64);
+    uint8_t * m2 = (uint8_t *)malloc(1024*1024*64);
+
+    memset(m1, 0, 1024*1024*16);
+    memset(m2, 0, 1024*1024*16);
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i  %p  %p", subtest, m1, m2);
+
+
+    size_t loopCount = 0;
+    uint64_t start = GetTime();
+    while((GetTime() - start) < 1000000000) {
+        memcpy(m1, m2, subtest);
+        loopCount++;
+    }
+    if (loopCount == 0) {
+        loopCount = 1;
+    }
+
+    size_t count = loopCount;
+    uint64_t t1 = GetTime();
+    while (loopCount > 0) {
+        memcpy(m1, m2, subtest);
+        loopCount--;
+    }
+    uint64_t t2 = GetTime();
+
+    double dt = t2 - t1;
+    dt /= 1000 * 1000 * 1000;
+    double bw = ((double)subtest) * count / dt;
+
+    bw /= 1024 * 1024 * 1024;
+
+    __android_log_print(ANDROID_LOG_INFO, "bench", "size %i, bw %f", subtest, bw);
+
+    free (m1);
+    free (m2);
+    return (float)bw;
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(
+        JNIEnv *_env, jobject _this, jint bytes) {
+    uint8_t *p = (uint8_t *)malloc(bytes);
+    memset(p, 0, bytes);
+    return (jlong)p;
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(
+        JNIEnv *_env, jobject _this, jlong ptr) {
+    free((void *)ptr);
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestMalloc(
+        JNIEnv *_env, jobject _this, jint bytes) {
+    return Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(_env, _this, bytes);
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestFree(
+        JNIEnv *_env, jobject _this, jlong ptr) {
+    Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(_env, _this, ptr);
+}
+
+}; // extern "C"
diff --git a/tests/JankBench/app/src/main/res/drawable/ic_play.png b/tests/JankBench/app/src/main/res/drawable/ic_play.png
new file mode 100644
index 0000000..13ed283
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/ic_play.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg
new file mode 100644
index 0000000..33c1fed
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img1.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg
new file mode 100644
index 0000000..1ea97f2
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img2.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg
new file mode 100644
index 0000000..ff99269
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img3.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg
new file mode 100644
index 0000000..d9cbd2f
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img4.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
new file mode 100644
index 0000000..6b3c899
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/upload_root"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="10dp"
+    android:clipToPadding="false">
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+        <view class="com.android.benchmark.ui.BitmapUploadActivity$UploadView"
+            android:id="@+id/upload_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/activity_home.xml b/tests/JankBench/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..c4f4299
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".app.HomeActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
new file mode 100644
index 0000000..0aaadde
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".app.HomeActivity"
+    android:orientation="vertical">
+
+    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/list_fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_memory.xml b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
new file mode 100644
index 0000000..fd5cadc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,49 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="com.android.benchmark.synthetic.MemoryActivity">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:text="Large Text"
+            android:id="@+id/textView_status" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_min" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_max" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_typical" />
+
+        <view
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            class="com.android.benchmark.app.PerfTimeline"
+            android:id="@+id/mem_timeline" />
+
+    </LinearLayout>
+</RelativeLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_running_list.xml b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
new file mode 100644
index 0000000..7b7b930
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        tools:context=".app.HomeActivity"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/score_text_view"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            android:layout_width="match_parent"
+            android:layout_height="30dp"
+            />
+
+        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/list_fragment_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
new file mode 100644
index 0000000..5375dbc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/group_name"
+        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+        android:textSize="17dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
new file mode 100644
index 0000000..5282e14
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:paddingLeft="?android:expandableListPreferredChildPaddingLeft"
+    android:layout_width="match_parent"
+    android:layout_height="55dip">
+
+
+    <CheckBox
+        android:id="@+id/benchmark_enable_checkbox"
+        android:checked="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/benchmark_name"
+        android:textSize="17dp"
+        android:paddingLeft="10dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/card_row.xml b/tests/JankBench/app/src/main/res/layout/card_row.xml
new file mode 100644
index 0000000..215f9df
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/card_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp"
+    android:clipToPadding="false"
+    android:background="@null">
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+        <TextView
+            android:id="@+id/card_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="match_parent"
+        android:layout_width="10dp" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/content_main.xml b/tests/JankBench/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..201bd66a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/fragment_start_button"
+    android:name="com.android.benchmark.app.BenchmarkDashboardFragment"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:layout="@layout/fragment_dashboard" />
+
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..f3100c7
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/main_content"
+    android:layout_width="match_parent"
+    android:layout_height="fill_parent"
+    android:fitsSystemWindows="true">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/appbar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_backdrop_height"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        android:fitsSystemWindows="true">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll"
+            android:fitsSystemWindows="true"
+            app:contentScrim="?attr/colorPrimary"
+            app:expandedTitleMarginStart="48dp"
+            app:expandedTitleMarginEnd="64dp">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+                app:layout_collapseMode="parallax" />
+
+            <ImageView
+                android:id="@+id/backdrop"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:src="@mipmap/ic_launcher"
+                android:scaleType="centerCrop"
+                android:fitsSystemWindows="true"
+                app:layout_collapseMode="parallax" />
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <android.support.v4.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical" >
+
+            <ExpandableListView
+                android:id="@+id/test_list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+
+        </LinearLayout>
+
+    </android.support.v4.widget.NestedScrollView>
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/start_button"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:src="@drawable/ic_play"
+        app:layout_anchor="@id/appbar"
+        app:layout_anchorGravity="bottom|right|end"
+        android:layout_margin="@dimen/fab_margin"
+        android:clickable="true"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
new file mode 100644
index 0000000..74d9891
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ExpandableListView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
new file mode 100644
index 0000000..c1662ea
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView
+        android:id="@+id/image_scroll_image"
+        android:scaleType="centerCrop"
+        android:layout_width="100dp"
+        android:layout_height="100dp" />
+
+    <TextView
+        android:id="@+id/image_scroll_text"
+        android:layout_gravity="right"
+        android:textSize="12sp"
+        android:paddingLeft="20dp"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/results_list_item.xml b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
new file mode 100644
index 0000000..f38b147
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal" android:layout_width="match_parent"
+    android:padding="8dp"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/result_name"
+        android:textSize="16sp"
+        android:layout_gravity="left"
+        android:layout_width="200dp"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/result_value"
+        android:textSize="16sp"
+        android:layout_gravity="right"
+        android:layout_width="200dp"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
new file mode 100644
index 0000000..8a9d015
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/benchmark_name"
+        android:textSize="17sp"
+        android:paddingLeft="?android:listPreferredItemPaddingLeft"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/menu/menu_main.xml b/tests/JankBench/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..1633acd
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".app.HomeActivity">
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_export"
+        app:showAsAction="never" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/menu/menu_memory.xml b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
new file mode 100644
index 0000000..f2df7c9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" tools:context="com.android.benchmark.Memory">
+    <item android:id="@+id/action_settings" android:title="@string/action_export"
+        android:orderInCategory="100" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..99ed094
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..e783e5d
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/attrs.xml b/tests/JankBench/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a4286f1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<resources>
+    <!-- Root tag for benchmarks -->
+    <declare-styleable name="AndroidBenchmarks" />
+
+    <declare-styleable name="BenchmarkGroup">
+        <attr name="name" format="reference|string" />
+        <attr name="description" format="reference|string" />
+    </declare-styleable>
+
+    <declare-styleable name="Benchmark">
+        <attr name="name" />
+        <attr name="description" />
+        <attr name="id" format="reference" />
+        <attr name="category" format="enum">
+            <enum name="generic" value="0" />
+            <enum name="ui" value="1" />
+            <enum name="compute" value="2" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="PerfTimeline"><attr name="exampleString" format="string"/>
+        <attr name="exampleDimension" format="dimension"/>
+        <attr name="exampleColor" format="color"/>
+        <attr name="exampleDrawable" format="color|reference"/>
+    </declare-styleable>
+
+</resources>
+
diff --git a/tests/JankBench/app/src/main/res/values/colors.xml b/tests/JankBench/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..59156ee
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/dimens.xml b/tests/JankBench/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9da649a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="detail_backdrop_height">256dp</dimen>
+    <dimen name="card_margin">16dp</dimen>
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+    <dimen name="app_bar_height">200dp</dimen>
+    <dimen name="item_width">200dp</dimen>
+    <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..6801fd9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<resources>
+    <item name="benchmark_list_view_scroll" type="id" />
+    <item name="benchmark_image_list_view_scroll" type="id" />
+    <item name="benchmark_shadow_grid" type="id" />
+    <item name="benchmark_text_high_hitrate" type="id" />
+    <item name="benchmark_text_low_hitrate" type="id" />
+    <item name="benchmark_edit_text_input" type="id" />
+    <item name="benchmark_overdraw" type="id" />
+    <item name="benchmark_memory_bandwidth" type="id" />
+    <item name="benchmark_memory_latency" type="id" />
+    <item name="benchmark_power_management" type="id" />
+    <item name="benchmark_cpu_heat_soak" type="id" />
+    <item name="benchmark_cpu_gflops" type="id" />
+</resources>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..270adf8
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -0,0 +1,51 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <string name="app_name">Benchmark</string>
+
+    <string name="action_export">Export to CSV</string>
+
+    <string name="list_view_scroll_name">List View Fling</string>
+    <string name="list_view_scroll_description">Tests list view fling performance</string>
+    <string name="image_list_view_scroll_name">Image List View Fling</string>
+    <string name="image_list_view_scroll_description">Tests list view fling performance with images</string>
+    <string name="shadow_grid_name">Shadow Grid Fling</string>
+    <string name="shadow_grid_description">Tests shadow grid fling performance with images</string>
+    <string name="text_high_hitrate_name">High-hitrate text render</string>
+    <string name="text_high_hitrate_description">Tests high hitrate text rendering</string>
+    <string name="text_low_hitrate_name">Low-hitrate text render</string>
+    <string name="text_low_hitrate_description">Tests low-hitrate text rendering</string>
+    <string name="edit_text_input_name">Edit Text Input</string>
+    <string name="edit_text_input_description">Tests edit text input</string>
+    <string name="overdraw_name">Overdraw Test</string>
+    <string name="overdraw_description">Tests how the device handles overdraw</string>
+    <string name="memory_bandwidth_name">Memory Bandwidth</string>
+    <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
+    <string name="memory_latency_name">Memory Latency</string>
+    <string name="memory_latency_description">Test device\'s memory latency</string>
+    <string name="power_management_name">Power Management</string>
+    <string name="power_management_description">Test device\'s power management</string>
+    <string name="cpu_heat_soak_name">CPU Heat Soak</string>
+    <string name="cpu_heat_soak_description">How hot can we make it?</string>
+    <string name="cpu_gflops_name">CPU GFlops</string>
+    <string name="cpu_gflops_description">How many gigaflops can the device attain?</string>
+
+    <string name="benchmark_category_ui">UI</string>
+    <string name="benchmark_category_compute">Compute</string>
+    <string name="benchmark_category_generic">Generic</string>
+    <string name="title_activity_image_list_view_scroll">ImageListViewScroll</string>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/styles.xml b/tests/JankBench/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..25ce730
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/styles.xml
@@ -0,0 +1,43 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+    <style name="Widget.CardContent" parent="android:Widget">
+        <item name="android:paddingLeft">16dp</item>
+        <item name="android:paddingRight">16dp</item>
+        <item name="android:paddingTop">24dp</item>
+        <item name="android:paddingBottom">24dp</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
new file mode 100644
index 0000000..07c453c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and limitations under the
+  ~ License.
+  ~
+  -->
+
+<com.android.benchmark.BenchmarkGroup
+    xmlns:benchmark="http://schemas.android.com/apk/res-auto"
+    benchmark:description="Benchmarks of the Android system"
+    benchmark:name="Android Benchmarks">
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/list_view_scroll_name"
+        benchmark:id="@id/benchmark_list_view_scroll"
+        benchmark:category="ui"
+        benchmark:description="@string/list_view_scroll_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/image_list_view_scroll_name"
+        benchmark:id="@id/benchmark_image_list_view_scroll"
+        benchmark:category="ui"
+        benchmark:description="@string/image_list_view_scroll_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/shadow_grid_name"
+        benchmark:id="@id/benchmark_shadow_grid"
+        benchmark:category="ui"
+        benchmark:description="@string/shadow_grid_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/text_low_hitrate_name"
+        benchmark:id="@id/benchmark_text_low_hitrate"
+        benchmark:category="ui"
+        benchmark:description="@string/text_low_hitrate_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/text_high_hitrate_name"
+        benchmark:id="@id/benchmark_text_high_hitrate"
+        benchmark:category="ui"
+        benchmark:description="@string/text_high_hitrate_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/edit_text_input_name"
+        benchmark:id="@id/benchmark_edit_text_input"
+        benchmark:category="ui"
+        benchmark:description="@string/edit_text_input_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/overdraw_name"
+        benchmark:id="@id/benchmark_overdraw"
+        benchmark:category="ui"
+        benchmark:description="@string/overdraw_description" />
+
+    <!--
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/memory_bandwidth_name"
+        benchmark:id="@id/benchmark_memory_bandwidth"
+        benchmark:category="compute"
+        benchmark:description="@string/memory_bandwidth_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/memory_latency_name"
+        benchmark:id="@id/benchmark_memory_latency"
+        benchmark:category="compute"
+        benchmark:description="@string/memory_latency_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/power_management_name"
+        benchmark:id="@id/benchmark_power_management"
+        benchmark:category="compute"
+        benchmark:description="@string/power_management_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/cpu_heat_soak_name"
+        benchmark:id="@id/benchmark_cpu_heat_soak"
+        benchmark:category="compute"
+        benchmark:description="@string/cpu_heat_soak_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/cpu_gflops_name"
+        benchmark:id="@id/benchmark_cpu_gflops"
+        benchmark:category="compute"
+        benchmark:description="@string/cpu_gflops_description" />
+        -->
+
+</com.android.benchmark.BenchmarkGroup>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
new file mode 100644
index 0000000..4464e87
--- /dev/null
+++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.benchmark;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}
diff --git a/tests/JankBench/scripts/adbutil.py b/tests/JankBench/scripts/adbutil.py
new file mode 100644
index 0000000..ad9f7d9
--- /dev/null
+++ b/tests/JankBench/scripts/adbutil.py
@@ -0,0 +1,62 @@
+import subprocess
+import re
+import threading
+
+ATRACE_PATH="/android/catapult/systrace/systrace/systrace.py"
+
+class AdbError(RuntimeError):
+    def __init__(self, arg):
+        self.args = arg
+
+def am(serial, cmd, args):
+    if not isinstance(args, list):
+        args = [args]
+    full_args = ["am"] + [cmd] + args
+    __call_adb(serial, full_args, False)
+
+def pm(serial, cmd, args):
+    if not isinstance(args, list):
+        args = [args]
+    full_args = ["pm"] + [cmd] + args
+    __call_adb(serial, full_args, False)
+
+def dumpsys(serial, topic):
+    return __call_adb(serial, ["dumpsys"] + [topic], True)
+
+def trace(serial,
+        tags = ["gfx", "sched", "view", "freq", "am", "wm", "power", "load", "memreclaim"],
+        time = "10"):
+    args = [ATRACE_PATH, "-e", serial, "-t" + time, "-b32768"] + tags
+    subprocess.call(args)
+
+def wake(serial):
+    output = dumpsys(serial, "power")
+    wakefulness = re.search('mWakefulness=([a-zA-Z]+)', output)
+    if wakefulness.group(1) != "Awake":
+        __call_adb(serial, ["input", "keyevent", "KEYCODE_POWER"], False)
+
+def root(serial):
+    subprocess.call(["adb", "-s", serial, "root"])
+
+def pull(serial, path, dest):
+    subprocess.call(["adb", "-s", serial, "wait-for-device", "pull"] + [path] + [dest])
+
+def shell(serial, cmd):
+    __call_adb(serial, cmd, False)
+
+def track_logcat(serial, awaited_string, callback):
+    threading.Thread(target=__track_logcat, name=serial + "-waiter", args=(serial, awaited_string, callback)).start()
+
+def __call_adb(serial, args, block):
+    full_args = ["adb", "-s", serial, "wait-for-device", "shell"] + args
+    print full_args
+    output = None
+    try:
+        if block:
+            output = subprocess.check_output(full_args)
+        else:
+            subprocess.call(full_args)
+    except subprocess.CalledProcessError:
+        raise AdbError("Error calling " + " ".join(args))
+
+    return output
diff --git a/tests/JankBench/scripts/collect.py b/tests/JankBench/scripts/collect.py
new file mode 100755
index 0000000..87a0594
--- /dev/null
+++ b/tests/JankBench/scripts/collect.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+from math import log10, floor
+import matplotlib
+
+matplotlib.use("Agg")
+
+import matplotlib.pyplot as plt
+import pylab
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, iteration, total_duration from ui_results "
+                   "where total_duration >= 16 order by run_id, name, iteration")
+QUERY_PERCENT_JANK = ("select run_id, name, iteration, sum(jank_frame) as jank_count, count (*) as total "
+                      "from ui_results group by run_id, name, iteration")
+
+SKIP_TESTS = [
+    # "BMUpload",
+    # "Low-hitrate text render",
+    # "High-hitrate text render",
+    # "Edit Text Input",
+    # "List View Fling"
+]
+
+INCLUDE_TESTS = [
+    #"BMUpload"
+    #"Shadow Grid Fling"
+    #"Image List View Fling"
+    #"Edit Text Input"
+]
+
+class IterationResult:
+    def __init__(self):
+        self.durations = []
+        self.jank_count = 0
+        self.total_count = 0
+
+
+def get_scoremap(dbpath):
+    db = sqlite3.connect(dbpath)
+    rows = db.execute(QUERY_BAD_FRAME)
+
+    scoremap = {}
+    for row in rows:
+        run_id = row[0]
+        name = row[1]
+        iteration = row[2]
+        total_duration = row[3]
+
+        if not run_id in scoremap:
+            scoremap[run_id] = {}
+
+        if not name in scoremap[run_id]:
+            scoremap[run_id][name] = {}
+
+        if not iteration in scoremap[run_id][name]:
+            scoremap[run_id][name][iteration] = IterationResult()
+
+        scoremap[run_id][name][iteration].durations.append(float(total_duration))
+
+    for row in db.execute(QUERY_PERCENT_JANK):
+        run_id = row[0]
+        name = row[1]
+        iteration = row[2]
+        jank_count = row[3]
+        total_count = row[4]
+
+        if run_id in scoremap.keys() and name in scoremap[run_id].keys() and iteration in scoremap[run_id][name].keys():
+            scoremap[run_id][name][iteration].jank_count = long(jank_count)
+            scoremap[run_id][name][iteration].total_count = long(total_count)
+
+    db.close()
+    return scoremap
+
+def round_to_2(val):
+    return val
+    if val == 0:
+        return val
+    return round(val , -int(floor(log10(abs(val)))) + 1)
+
+def score_device(name, serial, pull = False, verbose = False):
+    dbpath = OUT_PATH + name + ".db"
+
+    if pull:
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+
+    scoremap = None
+    try:
+        scoremap = get_scoremap(dbpath)
+    except sqlite3.DatabaseError:
+        print "Database corrupt, fetching..."
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+        scoremap = get_scoremap(dbpath)
+
+    per_test_score = {}
+    per_test_sample_count = {}
+    global_overall = {}
+
+    for run_id in iter(scoremap):
+        overall = []
+        if len(scoremap[run_id]) < 1:
+            if verbose:
+                print "Skipping short run %s" % run_id
+            continue
+        print "Run: %s" % run_id
+        for test in iter(scoremap[run_id]):
+            if test in SKIP_TESTS:
+                continue
+            if INCLUDE_TESTS and test not in INCLUDE_TESTS:
+                continue
+            if verbose:
+                print "\t%s" % test
+            scores = []
+            means = []
+            stddevs = []
+            pjs = []
+            sample_count = 0
+            hit_min_count = 0
+            # try pooling together all iterations
+            for iteration in iter(scoremap[run_id][test]):
+                res = scoremap[run_id][test][iteration]
+                stddev = round_to_2(numpy.std(res.durations))
+                mean = round_to_2(numpy.mean(res.durations))
+                sample_count += len(res.durations)
+                pj = round_to_2(100 * res.jank_count / float(res.total_count))
+                score = stddev * mean * pj
+                score = 100 * len(res.durations) / float(res.total_count)
+                if score == 0:
+                    score = 1
+                scores.append(score)
+                means.append(mean)
+                stddevs.append(stddev)
+                pjs.append(pj)
+                if verbose:
+                    print "\t%s: Score = %f x %f x %f = %f (%d samples)" % (iteration, stddev, mean, pj, score, len(res.durations))
+
+            if verbose:
+                print "\tHit min: %d" % hit_min_count
+                print "\tMean Variation: %0.2f%%" % (100 * scipy.stats.variation(means))
+                print "\tStdDev Variation: %0.2f%%" % (100 * scipy.stats.variation(stddevs))
+                print "\tPJ Variation: %0.2f%%" % (100 * scipy.stats.variation(pjs))
+
+            geo_run = numpy.mean(scores)
+            if test not in per_test_score:
+                per_test_score[test] = []
+
+            if test not in per_test_sample_count:
+                per_test_sample_count[test] = []
+
+            sample_count /= len(scoremap[run_id][test])
+
+            per_test_score[test].append(geo_run)
+            per_test_sample_count[test].append(int(sample_count))
+            overall.append(geo_run)
+
+            if not verbose:
+                print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+            else:
+                print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+                print ""
+
+        global_overall[run_id] = scipy.stats.gmean(overall)
+        print "Run Overall: %f" % global_overall[run_id]
+        print ""
+
+    print ""
+    print "Variability (CV) - %s:" % name
+
+    worst_offender_test = None
+    worst_offender_variation = 0
+    for test in per_test_score:
+        variation = 100 * scipy.stats.variation(per_test_score[test])
+        if worst_offender_variation < variation:
+            worst_offender_test = test
+            worst_offender_variation = variation
+        print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, variation, numpy.mean(per_test_sample_count[test]))
+
+    print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+    print ""
+
+    return {
+            "overall": global_overall.values(),
+            "worst_offender_test": (name, worst_offender_test, worst_offender_variation)
+            }
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-p", dest='pull', action="store_true")
+    parser.add_option("-d", dest='device', action="store")
+    parser.add_option("-v", dest='verbose', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return options
+
+def main():
+    options = parse_options(sys.argv)
+    if options.device != None:
+        score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+    else:
+        device_scores = []
+        worst_offenders = []
+        for name, serial in DEVICES.iteritems():
+            print "======== %s =========" % name
+            result = score_device(name, serial, options.pull, options.verbose)
+            device_scores.append((name, result["overall"]))
+            worst_offenders.append(result["worst_offender_test"])
+
+
+        device_scores.sort(cmp=(lambda x, y: cmp(x[1], y[1])))
+        print "Ranking by max overall score:"
+        for name, score in device_scores:
+            plt.plot([0, 1, 2, 3, 4, 5], score, label=name)
+            print "\t%s: %s" % (name, score)
+
+        plt.ylabel("Jank %")
+        plt.xlabel("Iteration")
+        plt.title("Jank Percentage")
+        plt.legend()
+        pylab.savefig("holy.png", bbox_inches="tight")
+
+        print "Worst offender tests:"
+        for device, test, variation in worst_offenders:
+            print "\t%s: %s %.2f%%" % (device, test, variation)
+
+if __name__ == "__main__":
+    main()
+
diff --git a/tests/JankBench/scripts/devices.py b/tests/JankBench/scripts/devices.py
new file mode 100644
index 0000000..c8266c0
--- /dev/null
+++ b/tests/JankBench/scripts/devices.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+DEVICES = {
+        'bullhead': '00606a370e3ca155',
+        'volantis': 'HT4BTWV00612',
+        'angler': '84B5T15A29021748',
+        'seed': '1285c85e',
+        'ryu': '5A27000599',
+        'shamu': 'ZX1G22W24R',
+}
+
diff --git a/tests/JankBench/scripts/external/__init__.py b/tests/JankBench/scripts/external/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/JankBench/scripts/external/__init__.py
diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py
new file mode 100644
index 0000000..518f546
--- /dev/null
+++ b/tests/JankBench/scripts/external/statistics.py
@@ -0,0 +1,638 @@
+##  Module statistics.py
+##
+##  Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>.
+##
+##  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.
+
+
+"""
+Basic statistics module.
+
+This module provides functions for calculating statistics of data, including
+averages, variance, and standard deviation.
+
+Calculating averages
+--------------------
+
+==================  =============================================
+Function            Description
+==================  =============================================
+mean                Arithmetic mean (average) of data.
+median              Median (middle value) of data.
+median_low          Low median of data.
+median_high         High median of data.
+median_grouped      Median, or 50th percentile, of grouped data.
+mode                Mode (most common value) of data.
+==================  =============================================
+
+Calculate the arithmetic mean ("the average") of data:
+
+>>> mean([-1.0, 2.5, 3.25, 5.75])
+2.625
+
+
+Calculate the standard median of discrete data:
+
+>>> median([2, 3, 4, 5])
+3.5
+
+
+Calculate the median, or 50th percentile, of data grouped into class intervals
+centred on the data values provided. E.g. if your data points are rounded to
+the nearest whole number:
+
+>>> median_grouped([2, 2, 3, 3, 3, 4])  #doctest: +ELLIPSIS
+2.8333333333...
+
+This should be interpreted in this way: you have two data points in the class
+interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in
+the class interval 3.5-4.5. The median of these data points is 2.8333...
+
+
+Calculating variability or spread
+---------------------------------
+
+==================  =============================================
+Function            Description
+==================  =============================================
+pvariance           Population variance of data.
+variance            Sample variance of data.
+pstdev              Population standard deviation of data.
+stdev               Sample standard deviation of data.
+==================  =============================================
+
+Calculate the standard deviation of sample data:
+
+>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75])  #doctest: +ELLIPSIS
+4.38961843444...
+
+If you have previously calculated the mean, you can pass it as the optional
+second argument to the four "spread" functions to avoid recalculating it:
+
+>>> data = [1, 2, 2, 4, 4, 4, 5, 6]
+>>> mu = mean(data)
+>>> pvariance(data, mu)
+2.5
+
+
+Exceptions
+----------
+
+A single exception is defined: StatisticsError is a subclass of ValueError.
+
+"""
+
+__all__ = [ 'StatisticsError',
+            'pstdev', 'pvariance', 'stdev', 'variance',
+            'median',  'median_low', 'median_high', 'median_grouped',
+            'mean', 'mode',
+          ]
+
+
+import collections
+import math
+
+from fractions import Fraction
+from decimal import Decimal
+from itertools import groupby
+
+
+
+# === Exceptions ===
+
+class StatisticsError(ValueError):
+    pass
+
+
+# === Private utilities ===
+
+def _sum(data, start=0):
+    """_sum(data [, start]) -> (type, sum, count)
+
+    Return a high-precision sum of the given numeric data as a fraction,
+    together with the type to be converted to and the count of items.
+
+    If optional argument ``start`` is given, it is added to the total.
+    If ``data`` is empty, ``start`` (defaulting to 0) is returned.
+
+
+    Examples
+    --------
+
+    >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75)
+    (<class 'float'>, Fraction(11, 1), 5)
+
+    Some sources of round-off error will be avoided:
+
+    >>> _sum([1e50, 1, -1e50] * 1000)  # Built-in sum returns zero.
+    (<class 'float'>, Fraction(1000, 1), 3000)
+
+    Fractions and Decimals are also supported:
+
+    >>> from fractions import Fraction as F
+    >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)])
+    (<class 'fractions.Fraction'>, Fraction(63, 20), 4)
+
+    >>> from decimal import Decimal as D
+    >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")]
+    >>> _sum(data)
+    (<class 'decimal.Decimal'>, Fraction(6963, 10000), 4)
+
+    Mixed types are currently treated as an error, except that int is
+    allowed.
+    """
+    count = 0
+    n, d = _exact_ratio(start)
+    partials = {d: n}
+    partials_get = partials.get
+    T = _coerce(int, type(start))
+    for typ, values in groupby(data, type):
+        T = _coerce(T, typ)  # or raise TypeError
+        for n,d in map(_exact_ratio, values):
+            count += 1
+            partials[d] = partials_get(d, 0) + n
+    if None in partials:
+        # The sum will be a NAN or INF. We can ignore all the finite
+        # partials, and just look at this special one.
+        total = partials[None]
+        assert not _isfinite(total)
+    else:
+        # Sum all the partial sums using builtin sum.
+        # FIXME is this faster if we sum them in order of the denominator?
+        total = sum(Fraction(n, d) for d, n in sorted(partials.items()))
+    return (T, total, count)
+
+
+def _isfinite(x):
+    try:
+        return x.is_finite()  # Likely a Decimal.
+    except AttributeError:
+        return math.isfinite(x)  # Coerces to float first.
+
+
+def _coerce(T, S):
+    """Coerce types T and S to a common type, or raise TypeError.
+
+    Coercion rules are currently an implementation detail. See the CoerceTest
+    test class in test_statistics for details.
+    """
+    # See http://bugs.python.org/issue24068.
+    assert T is not bool, "initial type T is bool"
+    # If the types are the same, no need to coerce anything. Put this
+    # first, so that the usual case (no coercion needed) happens as soon
+    # as possible.
+    if T is S:  return T
+    # Mixed int & other coerce to the other type.
+    if S is int or S is bool:  return T
+    if T is int:  return S
+    # If one is a (strict) subclass of the other, coerce to the subclass.
+    if issubclass(S, T):  return S
+    if issubclass(T, S):  return T
+    # Ints coerce to the other type.
+    if issubclass(T, int):  return S
+    if issubclass(S, int):  return T
+    # Mixed fraction & float coerces to float (or float subclass).
+    if issubclass(T, Fraction) and issubclass(S, float):
+        return S
+    if issubclass(T, float) and issubclass(S, Fraction):
+        return T
+    # Any other combination is disallowed.
+    msg = "don't know how to coerce %s and %s"
+    raise TypeError(msg % (T.__name__, S.__name__))
+
+
+def _exact_ratio(x):
+    """Return Real number x to exact (numerator, denominator) pair.
+
+    >>> _exact_ratio(0.25)
+    (1, 4)
+
+    x is expected to be an int, Fraction, Decimal or float.
+    """
+    try:
+        # Optimise the common case of floats. We expect that the most often
+        # used numeric type will be builtin floats, so try to make this as
+        # fast as possible.
+        if type(x) is float:
+            return x.as_integer_ratio()
+        try:
+            # x may be an int, Fraction, or Integral ABC.
+            return (x.numerator, x.denominator)
+        except AttributeError:
+            try:
+                # x may be a float subclass.
+                return x.as_integer_ratio()
+            except AttributeError:
+                try:
+                    # x may be a Decimal.
+                    return _decimal_to_ratio(x)
+                except AttributeError:
+                    # Just give up?
+                    pass
+    except (OverflowError, ValueError):
+        # float NAN or INF.
+        assert not math.isfinite(x)
+        return (x, None)
+    msg = "can't convert type '{}' to numerator/denominator"
+    raise TypeError(msg.format(type(x).__name__))
+
+
+# FIXME This is faster than Fraction.from_decimal, but still too slow.
+def _decimal_to_ratio(d):
+    """Convert Decimal d to exact integer ratio (numerator, denominator).
+
+    >>> from decimal import Decimal
+    >>> _decimal_to_ratio(Decimal("2.6"))
+    (26, 10)
+
+    """
+    sign, digits, exp = d.as_tuple()
+    if exp in ('F', 'n', 'N'):  # INF, NAN, sNAN
+        assert not d.is_finite()
+        return (d, None)
+    num = 0
+    for digit in digits:
+        num = num*10 + digit
+    if exp < 0:
+        den = 10**-exp
+    else:
+        num *= 10**exp
+        den = 1
+    if sign:
+        num = -num
+    return (num, den)
+
+
+def _convert(value, T):
+    """Convert value to given numeric type T."""
+    if type(value) is T:
+        # This covers the cases where T is Fraction, or where value is
+        # a NAN or INF (Decimal or float).
+        return value
+    if issubclass(T, int) and value.denominator != 1:
+        T = float
+    try:
+        # FIXME: what do we do if this overflows?
+        return T(value)
+    except TypeError:
+        if issubclass(T, Decimal):
+            return T(value.numerator)/T(value.denominator)
+        else:
+            raise
+
+
+def _counts(data):
+    # Generate a table of sorted (value, frequency) pairs.
+    table = collections.Counter(iter(data)).most_common()
+    if not table:
+        return table
+    # Extract the values with the highest frequency.
+    maxfreq = table[0][1]
+    for i in range(1, len(table)):
+        if table[i][1] != maxfreq:
+            table = table[:i]
+            break
+    return table
+
+
+# === Measures of central tendency (averages) ===
+
+def mean(data):
+    """Return the sample arithmetic mean of data.
+
+    >>> mean([1, 2, 3, 4, 4])
+    2.8
+
+    >>> from fractions import Fraction as F
+    >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])
+    Fraction(13, 21)
+
+    >>> from decimal import Decimal as D
+    >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])
+    Decimal('0.5625')
+
+    If ``data`` is empty, StatisticsError will be raised.
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 1:
+        raise StatisticsError('mean requires at least one data point')
+    T, total, count = _sum(data)
+    assert count == n
+    return _convert(total/n, T)
+
+
+# FIXME: investigate ways to calculate medians without sorting? Quickselect?
+def median(data):
+    """Return the median (middle value) of numeric data.
+
+    When the number of data points is odd, return the middle data point.
+    When the number of data points is even, the median is interpolated by
+    taking the average of the two middle values:
+
+    >>> median([1, 3, 5])
+    3
+    >>> median([1, 3, 5, 7])
+    4.0
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    if n%2 == 1:
+        return data[n//2]
+    else:
+        i = n//2
+        return (data[i - 1] + data[i])/2
+
+
+def median_low(data):
+    """Return the low median of numeric data.
+
+    When the number of data points is odd, the middle value is returned.
+    When it is even, the smaller of the two middle values is returned.
+
+    >>> median_low([1, 3, 5])
+    3
+    >>> median_low([1, 3, 5, 7])
+    3
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    if n%2 == 1:
+        return data[n//2]
+    else:
+        return data[n//2 - 1]
+
+
+def median_high(data):
+    """Return the high median of data.
+
+    When the number of data points is odd, the middle value is returned.
+    When it is even, the larger of the two middle values is returned.
+
+    >>> median_high([1, 3, 5])
+    3
+    >>> median_high([1, 3, 5, 7])
+    5
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    return data[n//2]
+
+
+def median_grouped(data, interval=1):
+    """Return the 50th percentile (median) of grouped continuous data.
+
+    >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5])
+    3.7
+    >>> median_grouped([52, 52, 53, 54])
+    52.5
+
+    This calculates the median as the 50th percentile, and should be
+    used when your data is continuous and grouped. In the above example,
+    the values 1, 2, 3, etc. actually represent the midpoint of classes
+    0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in
+    class 3.5-4.5, and interpolation is used to estimate it.
+
+    Optional argument ``interval`` represents the class interval, and
+    defaults to 1. Changing the class interval naturally will change the
+    interpolated 50th percentile value:
+
+    >>> median_grouped([1, 3, 3, 5, 7], interval=1)
+    3.25
+    >>> median_grouped([1, 3, 3, 5, 7], interval=2)
+    3.5
+
+    This function does not check whether the data points are at least
+    ``interval`` apart.
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    elif n == 1:
+        return data[0]
+    # Find the value at the midpoint. Remember this corresponds to the
+    # centre of the class interval.
+    x = data[n//2]
+    for obj in (x, interval):
+        if isinstance(obj, (str, bytes)):
+            raise TypeError('expected number but got %r' % obj)
+    try:
+        L = x - interval/2  # The lower limit of the median interval.
+    except TypeError:
+        # Mixed type. For now we just coerce to float.
+        L = float(x) - float(interval)/2
+    cf = data.index(x)  # Number of values below the median interval.
+    # FIXME The following line could be more efficient for big lists.
+    f = data.count(x)  # Number of data points in the median interval.
+    return L + interval*(n/2 - cf)/f
+
+
+def mode(data):
+    """Return the most common data point from discrete or nominal data.
+
+    ``mode`` assumes discrete data, and returns a single value. This is the
+    standard treatment of the mode as commonly taught in schools:
+
+    >>> mode([1, 1, 2, 3, 3, 3, 3, 4])
+    3
+
+    This also works with nominal (non-numeric) data:
+
+    >>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
+    'red'
+
+    If there is not exactly one most common value, ``mode`` will raise
+    StatisticsError.
+    """
+    # Generate a table of sorted (value, frequency) pairs.
+    table = _counts(data)
+    if len(table) == 1:
+        return table[0][0]
+    elif table:
+        raise StatisticsError(
+                'no unique mode; found %d equally common values' % len(table)
+                )
+    else:
+        raise StatisticsError('no mode for empty data')
+
+
+# === Measures of spread ===
+
+# See http://mathworld.wolfram.com/Variance.html
+#     http://mathworld.wolfram.com/SampleVariance.html
+#     http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+#
+# Under no circumstances use the so-called "computational formula for
+# variance", as that is only suitable for hand calculations with a small
+# amount of low-precision data. It has terrible numeric properties.
+#
+# See a comparison of three computational methods here:
+# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/
+
+def _ss(data, c=None):
+    """Return sum of square deviations of sequence data.
+
+    If ``c`` is None, the mean is calculated in one pass, and the deviations
+    from the mean are calculated in a second pass. Otherwise, deviations are
+    calculated from ``c`` as given. Use the second case with care, as it can
+    lead to garbage results.
+    """
+    if c is None:
+        c = mean(data)
+    T, total, count = _sum((x-c)**2 for x in data)
+    # The following sum should mathematically equal zero, but due to rounding
+    # error may not.
+    U, total2, count2 = _sum((x-c) for x in data)
+    assert T == U and count == count2
+    total -=  total2**2/len(data)
+    assert not total < 0, 'negative sum of square deviations: %f' % total
+    return (T, total)
+
+
+def variance(data, xbar=None):
+    """Return the sample variance of data.
+
+    data should be an iterable of Real-valued numbers, with at least two
+    values. The optional argument xbar, if given, should be the mean of
+    the data. If it is missing or None, the mean is automatically calculated.
+
+    Use this function when your data is a sample from a population. To
+    calculate the variance from the entire population, see ``pvariance``.
+
+    Examples:
+
+    >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
+    >>> variance(data)
+    1.3720238095238095
+
+    If you have already calculated the mean of your data, you can pass it as
+    the optional second argument ``xbar`` to avoid recalculating it:
+
+    >>> m = mean(data)
+    >>> variance(data, m)
+    1.3720238095238095
+
+    This function does not check that ``xbar`` is actually the mean of
+    ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or
+    impossible results.
+
+    Decimals and Fractions are supported:
+
+    >>> from decimal import Decimal as D
+    >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+    Decimal('31.01875')
+
+    >>> from fractions import Fraction as F
+    >>> variance([F(1, 6), F(1, 2), F(5, 3)])
+    Fraction(67, 108)
+
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 2:
+        raise StatisticsError('variance requires at least two data points')
+    T, ss = _ss(data, xbar)
+    return _convert(ss/(n-1), T)
+
+
+def pvariance(data, mu=None):
+    """Return the population variance of ``data``.
+
+    data should be an iterable of Real-valued numbers, with at least one
+    value. The optional argument mu, if given, should be the mean of
+    the data. If it is missing or None, the mean is automatically calculated.
+
+    Use this function to calculate the variance from the entire population.
+    To estimate the variance from a sample, the ``variance`` function is
+    usually a better choice.
+
+    Examples:
+
+    >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25]
+    >>> pvariance(data)
+    1.25
+
+    If you have already calculated the mean of the data, you can pass it as
+    the optional second argument to avoid recalculating it:
+
+    >>> mu = mean(data)
+    >>> pvariance(data, mu)
+    1.25
+
+    This function does not check that ``mu`` is actually the mean of ``data``.
+    Giving arbitrary values for ``mu`` may lead to invalid or impossible
+    results.
+
+    Decimals and Fractions are supported:
+
+    >>> from decimal import Decimal as D
+    >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+    Decimal('24.815')
+
+    >>> from fractions import Fraction as F
+    >>> pvariance([F(1, 4), F(5, 4), F(1, 2)])
+    Fraction(13, 72)
+
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 1:
+        raise StatisticsError('pvariance requires at least one data point')
+    ss = _ss(data, mu)
+    T, ss = _ss(data, mu)
+    return _convert(ss/n, T)
+
+
+def stdev(data, xbar=None):
+    """Return the square root of the sample variance.
+
+    See ``variance`` for arguments and other details.
+
+    >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+    1.0810874155219827
+
+    """
+    var = variance(data, xbar)
+    try:
+        return var.sqrt()
+    except AttributeError:
+        return math.sqrt(var)
+
+
+def pstdev(data, mu=None):
+    """Return the square root of the population variance.
+
+    See ``pvariance`` for arguments and other details.
+
+    >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+    0.986893273527251
+
+    """
+    var = pvariance(data, mu)
+    try:
+        return var.sqrt()
+    except AttributeError:
+        return math.sqrt(var)
diff --git a/tests/JankBench/scripts/itr_collect.py b/tests/JankBench/scripts/itr_collect.py
new file mode 100755
index 0000000..76499a4
--- /dev/null
+++ b/tests/JankBench/scripts/itr_collect.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, total_duration from ui_results "
+                   "where total_duration >=12 order by run_id, name")
+QUERY_PERCENT_JANK = ("select run_id, name, sum(jank_frame) as jank_count, count (*) as total "
+                      "from ui_results group by run_id, name")
+
+class IterationResult:
+    def __init__(self):
+        self.durations = []
+        self.jank_count = 0
+        self.total_count = 0
+
+
+def get_scoremap(dbpath):
+    db = sqlite3.connect(dbpath)
+    rows = db.execute(QUERY_BAD_FRAME)
+
+    scoremap = {}
+    for row in rows:
+        run_id = row[0]
+        name = row[1]
+        total_duration = row[2]
+
+        if not run_id in scoremap:
+            scoremap[run_id] = {}
+
+        if not name in scoremap[run_id]:
+            scoremap[run_id][name] = IterationResult()
+
+
+        scoremap[run_id][name].durations.append(float(total_duration))
+
+    for row in db.execute(QUERY_PERCENT_JANK):
+        run_id = row[0]
+        name = row[1]
+        jank_count = row[2]
+        total_count = row[3]
+
+        if run_id in scoremap.keys() and name in scoremap[run_id].keys():
+            scoremap[run_id][name].jank_count = long(jank_count)
+            scoremap[run_id][name].total_count = long(total_count)
+
+
+    db.close()
+    return scoremap
+
+def score_device(name, serial, pull = False, verbose = False):
+    dbpath = OUT_PATH + name + ".db"
+
+    if pull:
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+
+    scoremap = None
+    try:
+        scoremap = get_scoremap(dbpath)
+    except sqlite3.DatabaseError:
+        print "Database corrupt, fetching..."
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+        scoremap = get_scoremap(dbpath)
+
+    per_test_score = {}
+    per_test_sample_count = {}
+    global_overall = {}
+
+    for run_id in iter(scoremap):
+        overall = []
+        if len(scoremap[run_id]) < 1:
+            if verbose:
+                print "Skipping short run %s" % run_id
+            continue
+        print "Run: %s" % run_id
+        for test in iter(scoremap[run_id]):
+            if verbose:
+                print "\t%s" % test
+            scores = []
+            sample_count = 0
+            res = scoremap[run_id][test]
+            stddev = numpy.std(res.durations)
+            mean = numpy.mean(res.durations)
+            sample_count = len(res.durations)
+            pj = 100 * res.jank_count / float(res.total_count)
+            score = stddev * mean *pj
+            if score == 0:
+                score = 1
+            scores.append(score)
+            if verbose:
+                print "\tScore = %f x %f x %f = %f (%d samples)" % (stddev, mean, pj, score, len(res.durations))
+
+            geo_run = scipy.stats.gmean(scores)
+            if test not in per_test_score:
+                per_test_score[test] = []
+
+            if test not in per_test_sample_count:
+                per_test_sample_count[test] = []
+
+            per_test_score[test].append(geo_run)
+            per_test_sample_count[test].append(int(sample_count))
+            overall.append(geo_run)
+
+            if not verbose:
+                print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+            else:
+                print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+                print ""
+
+        global_overall[run_id] = scipy.stats.gmean(overall)
+        print "Run Overall: %f" % global_overall[run_id]
+        print ""
+
+    print ""
+    print "Variability (CV) - %s:" % name
+
+    for test in per_test_score:
+        print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, 100 * scipy.stats.variation(per_test_score[test]), numpy.mean(per_test_sample_count[test]))
+
+    print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+    print ""
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-p", dest='pull', action="store_true")
+    parser.add_option("-d", dest='device', action="store")
+    parser.add_option("-v", dest='verbose', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return options
+
+def main():
+    options = parse_options(sys.argv)
+    if options.device != None:
+        score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+    else:
+        for name, serial in DEVICES.iteritems():
+            print "======== %s =========" % name
+            score_device(name, serial, options.pull, options.verbose)
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/JankBench/scripts/runall.py b/tests/JankBench/scripts/runall.py
new file mode 100755
index 0000000..d9a0662
--- /dev/null
+++ b/tests/JankBench/scripts/runall.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import time
+
+import adbutil
+from devices import DEVICES
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-c", dest='clear', action="store_true")
+    parser.add_option("-d", dest='device', action="store",)
+    parser.add_option("-t", dest='trace', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return (options, categories)
+
+def clear_data(device = None):
+    if device != None:
+        dev = DEVICES[device]
+        adbutil.root(dev)
+        adbutil.pm(dev, "clear", "com.android.benchmark")
+    else:
+        for name, dev in DEVICES.iteritems():
+            print("Clearing " + name)
+            adbutil.root(dev)
+            adbutil.pm(dev, "clear", "com.android.benchmark")
+
+def start_device(name, dev):
+    print("Go " + name + "!")
+    try:
+        adbutil.am(dev, "force-stop", "com.android.benchmark")
+        adbutil.wake(dev)
+        adbutil.am(dev, "start",
+            ["-n", "\"com.android.benchmark/.app.RunLocalBenchmarksActivity\"",
+            "--eia", "\"com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS\"", "\"0,1,2,3,4,5,6\"",
+            "--ei", "\"com.android.benchmark.EXTRA_RUN_COUNT\"", "\"5\""])
+    except adbutil.AdbError:
+        print "Couldn't launch " + name + "."
+
+def start_benchmark(device, trace):
+    if device != None:
+        start_device(device, DEVICES[device])
+        if trace:
+            time.sleep(3)
+            adbutil.trace(DEVICES[device])
+    else:
+        if trace:
+            print("Note: -t only valid with -d, can't trace")
+        for name, dev in DEVICES.iteritems():
+            start_device(name, dev)
+
+def main():
+    options, categories = parse_options(sys.argv)
+    if options.clear:
+        print options.device
+        clear_data(options.device)
+    else:
+        start_benchmark(options.device, options.trace)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/SurfaceComposition/Android.mk b/tests/SurfaceComposition/Android.mk
index 1962791..f59458d 100644
--- a/tests/SurfaceComposition/Android.mk
+++ b/tests/SurfaceComposition/Android.mk
@@ -29,7 +29,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := SurfaceComposition
 
diff --git a/tests/WindowAnimationJank/Android.mk b/tests/WindowAnimationJank/Android.mk
index 8aac8a1..7800a80 100644
--- a/tests/WindowAnimationJank/Android.mk
+++ b/tests/WindowAnimationJank/Android.mk
@@ -29,6 +29,8 @@
     ub-janktesthelper \
     junit
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_PACKAGE)
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 558dbb6..473dc538 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -73,18 +73,18 @@
     }
 
     @Test
-    public void testToSafeString() {
+    public void testToOuiString() {
         String[][] macs = {
-            {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"},
-            {"33:33:aa:bb:cc:dd", "33:33:aa:00:00:00"},
-            {"06:00:00:00:00:00", "06:00:00:00:00:00"},
-            {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"}
+            {"07:00:d3:56:8a:c4", "07:00:d3"},
+            {"33:33:aa:bb:cc:dd", "33:33:aa"},
+            {"06:00:00:00:00:00", "06:00:00"},
+            {"07:00:d3:56:8a:c4", "07:00:d3"}
         };
 
         for (String[] pair : macs) {
             String mac = pair[0];
             String expected = pair[1];
-            assertEquals(expected, MacAddress.fromString(mac).toSafeString());
+            assertEquals(expected, MacAddress.fromString(mac).toOuiString());
         }
     }
 
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 8683c12..5d1e10e 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -40,10 +41,14 @@
 import android.net.IpSecUdpEncapResponse;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.StructStat;
+
+import dalvik.system.SocketTagger;
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
@@ -56,6 +61,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 
 /** Unit tests for {@link IpSecService}. */
 @SmallTest
@@ -411,4 +417,84 @@
             mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
         }
     }
+
+    @Test
+    public void testUidFdtagger() throws Exception {
+        SocketTagger actualSocketTagger = SocketTagger.get();
+
+        try {
+            FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+            // Has to be done after socket creation because BlockGuardOS calls tag on new sockets
+            SocketTagger mockSocketTagger = mock(SocketTagger.class);
+            SocketTagger.set(mockSocketTagger);
+
+            mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID);
+            verify(mockSocketTagger).tag(eq(sockFd));
+        } finally {
+            SocketTagger.set(actualSocketTagger);
+        }
+    }
+
+    /**
+     * Checks if two file descriptors point to the same file.
+     *
+     * <p>According to stat.h documentation, the correct way to check for equivalent or duplicated
+     * file descriptors is to check their inode and device. These two entries uniquely identify any
+     * file.
+     */
+    private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) {
+        try {
+            StructStat fd1Stat = Os.fstat(fd1);
+            StructStat fd2Stat = Os.fstat(fd2);
+
+            return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev;
+        } catch (ErrnoException e) {
+            return false;
+        }
+    }
+
+    @Test
+    public void testOpenUdpEncapSocketTagsSocket() throws Exception {
+        IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
+        IpSecService testIpSecService =
+                new IpSecService(mMockContext, mMockIpSecSrvConfig, mockTagger);
+
+        IpSecUdpEncapResponse udpEncapResp =
+                testIpSecService.openUdpEncapsulationSocket(0, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+
+        FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+        ArgumentMatcher<FileDescriptor> fdMatcher =
+                (argFd) -> {
+                    return fileDescriptorsEqual(sockFd, argFd);
+                };
+        verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid()));
+
+        testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+        udpEncapResp.fileDescriptor.close();
+    }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+        FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+        ArgumentMatcher<FileDescriptor> fdMatcher = (arg) -> {
+                    try {
+                        StructStat sockStat = Os.fstat(sockFd);
+                        StructStat argStat = Os.fstat(arg);
+
+                        return sockStat.st_ino == argStat.st_ino
+                                && sockStat.st_dev == argStat.st_dev;
+                    } catch (ErrnoException e) {
+                        return false;
+                    }
+                };
+
+        verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+    }
 }
diff --git a/tests/testables/Android.mk b/tests/testables/Android.mk
index 7fcfc6e..4c4d2b4 100644
--- a/tests/testables/Android.mk
+++ b/tests/testables/Android.mk
@@ -24,10 +24,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    mockito-target-minus-junit4
+    android-support-test
 
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.mock
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.mock mockito-target-minus-junit4
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index 543c652..a76616f 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -24,9 +24,12 @@
 LOCAL_SRC_FILES := $(call all-java-files-under,java)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    mockito-target-minus-junit4
+    android-support-test
 
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    android.test.base \
+    android.test.mock \
+    mockito-target-minus-junit4 \
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0f6fb50..5831875 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -294,36 +294,42 @@
         printer->Print("/");
         printer->Print(entry->name);
 
-        switch (entry->symbol_status.state) {
-          case SymbolState::kPublic:
+        switch (entry->visibility.level) {
+          case Visibility::Level::kPublic:
             printer->Print(" PUBLIC");
             break;
-          case SymbolState::kPrivate:
+          case Visibility::Level::kPrivate:
             printer->Print(" _PRIVATE_");
             break;
-          case SymbolState::kUndefined:
+          case Visibility::Level::kUndefined:
             // Print nothing.
             break;
         }
 
+        if (entry->overlayable) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
-        printer->Indent();
-        for (const auto& value : entry->values) {
-          printer->Print("(");
-          printer->Print(value->config.to_string());
-          printer->Print(") ");
-          value->value->Accept(&headline_printer);
-          if (options.show_sources && !value->value->GetSource().path.empty()) {
-            printer->Print(" src=");
-            printer->Print(value->value->GetSource().to_string());
-          }
-          printer->Println();
+        if (options.show_values) {
           printer->Indent();
-          value->value->Accept(&body_printer);
+          for (const auto& value : entry->values) {
+            printer->Print("(");
+            printer->Print(value->config.to_string());
+            printer->Print(") ");
+            value->value->Accept(&headline_printer);
+            if (options.show_sources && !value->value->GetSource().path.empty()) {
+              printer->Print(" src=");
+              printer->Print(value->value->GetSource().to_string());
+            }
+            printer->Println();
+            printer->Indent();
+            value->value->Accept(&body_printer);
+            printer->Undent();
+          }
           printer->Undent();
         }
-        printer->Undent();
       }
       printer->Undent();
     }
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 3c1ee4c..6209a04 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -29,6 +29,7 @@
 
 struct DebugPrintTableOptions {
   bool show_sources = false;
+  bool show_values = true;
 };
 
 struct Debug {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4cc60a8..24b28dd 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -30,7 +30,7 @@
 #include "util/Util.h"
 #include "xml/XmlPullParser.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 
@@ -90,9 +90,12 @@
   ConfigDescription config;
   std::string product;
   Source source;
+
   ResourceId id;
-  Maybe<SymbolState> symbol_state;
+  Visibility::Level visibility_level = Visibility::Level::kUndefined;
   bool allow_new = false;
+  bool overlayable = false;
+
   std::string comment;
   std::unique_ptr<Value> value;
   std::list<ParsedResource> child_resources;
@@ -106,24 +109,41 @@
     res->comment = trimmed_comment.to_string();
   }
 
-  if (res->symbol_state) {
-    Symbol symbol;
-    symbol.state = res->symbol_state.value();
-    symbol.source = res->source;
-    symbol.comment = res->comment;
-    symbol.allow_new = res->allow_new;
-    if (!table->SetSymbolState(res->name, res->id, symbol, diag)) {
+  if (res->visibility_level != Visibility::Level::kUndefined) {
+    Visibility visibility;
+    visibility.level = res->visibility_level;
+    visibility.source = res->source;
+    visibility.comment = res->comment;
+    if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
       return false;
     }
   }
 
-  if (res->value) {
+  if (res->allow_new) {
+    AllowNew allow_new;
+    allow_new.source = res->source;
+    allow_new.comment = res->comment;
+    if (!table->SetAllowNew(res->name, allow_new, diag)) {
+      return false;
+    }
+  }
+
+  if (res->overlayable) {
+    Overlayable overlayable;
+    overlayable.source = res->source;
+    overlayable.comment = res->comment;
+    if (!table->SetOverlayable(res->name, overlayable, diag)) {
+      return false;
+    }
+  }
+
+  if (res->value != nullptr) {
     // Attach the comment, source and config to the value.
     res->value->SetComment(std::move(res->comment));
     res->value->SetSource(std::move(res->source));
 
-    if (!table->AddResource(res->name, res->id, res->config, res->product, std::move(res->value),
-                            diag)) {
+    if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
+                                  std::move(res->value), diag)) {
       return false;
     }
   }
@@ -601,8 +621,7 @@
 
   // Process the raw value.
   std::unique_ptr<Item> processed_item =
-      ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
-                                              on_create_reference);
+      ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
   if (processed_item) {
     // Fix up the reference.
     if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
@@ -689,8 +708,7 @@
   return true;
 }
 
-bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
-                                 ParsedResource* out_resource) {
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (out_resource->config != ConfigDescription::DefaultConfig()) {
     diag_->Warn(DiagMessage(out_resource->source)
                 << "ignoring configuration '" << out_resource->config << "' for <public> tag");
@@ -728,7 +746,7 @@
     out_resource->value = util::make_unique<Id>();
   }
 
-  out_resource->symbol_state = SymbolState::kPublic;
+  out_resource->visibility_level = Visibility::Level::kPublic;
   return true;
 }
 
@@ -818,7 +836,7 @@
       child_resource.id = next_id;
       child_resource.comment = std::move(comment);
       child_resource.source = item_source;
-      child_resource.symbol_state = SymbolState::kPublic;
+      child_resource.visibility_level = Visibility::Level::kPublic;
       out_resource->child_resources.push_back(std::move(child_resource));
 
       next_id.id += 1;
@@ -864,7 +882,7 @@
     return false;
   }
 
-  out_resource->symbol_state = SymbolState::kPrivate;
+  out_resource->visibility_level = Visibility::Level::kPrivate;
   return true;
 }
 
@@ -920,8 +938,12 @@
         continue;
       }
 
-      // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay
-      // the resource (system/app).
+      ParsedResource child_resource;
+      child_resource.name.type = *type;
+      child_resource.name.entry = maybe_name.value().to_string();
+      child_resource.source = item_source;
+      child_resource.overlayable = true;
+      out_resource->child_resources.push_back(std::move(child_resource));
 
       xml::XmlPullParser::SkipCurrentElement(parser);
     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
@@ -932,10 +954,9 @@
   return !error;
 }
 
-bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
-                                      ParsedResource* out_resource) {
+bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (ParseSymbolImpl(parser, out_resource)) {
-    out_resource->symbol_state = SymbolState::kUndefined;
+    out_resource->visibility_level = Visibility::Level::kUndefined;
     out_resource->allow_new = true;
     return true;
   }
@@ -1395,9 +1416,8 @@
                                            ParsedResource* out_resource) {
   out_resource->name.type = ResourceType::kStyleable;
 
-  // Declare-styleable is kPrivate by default, because it technically only
-  // exists in R.java.
-  out_resource->symbol_state = SymbolState::kPublic;
+  // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
+  out_resource->visibility_level = Visibility::Level::kPublic;
 
   // Declare-styleable only ends up in default config;
   if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 9a5cd3e..618c8ed 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -29,8 +29,8 @@
 using ::aapt::io::StringInputStream;
 using ::aapt::test::StrValueEq;
 using ::aapt::test::ValueEq;
-using ::android::ResTable_map;
 using ::android::Res_value;
+using ::android::ResTable_map;
 using ::android::StringPiece;
 using ::testing::Eq;
 using ::testing::IsEmpty;
@@ -38,6 +38,7 @@
 using ::testing::NotNull;
 using ::testing::Pointee;
 using ::testing::SizeIs;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -482,7 +483,7 @@
   Maybe<ResourceTable::SearchResult> result =
       table_.FindResource(test::ParseNameOrDie("styleable/foo"));
   ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
 
   Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
   ASSERT_THAT(attr, NotNull());
@@ -718,6 +719,26 @@
   EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041)));
 }
 
+TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
+  std::string input = R"(
+      <!-- private -->
+      <java-symbol type="string" name="foo" />
+      <!-- public -->
+      <public type="string" name="foo" id="0x01020000" />
+      <!-- private2 -->
+      <java-symbol type="string" name="foo" />)";
+  ASSERT_TRUE(TestParse(input));
+
+  Maybe<ResourceTable::SearchResult> result =
+      table_.FindResource(test::ParseNameOrDie("string/foo"));
+  ASSERT_TRUE(result);
+
+  ResourceEntry* entry = result.value().entry;
+  ASSERT_THAT(entry, NotNull());
+  EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kPublic));
+  EXPECT_THAT(entry->visibility.comment, StrEq("public"));
+}
+
 TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
   ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)"));
   ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)"));
@@ -731,8 +752,8 @@
   ASSERT_TRUE(result);
   const ResourceEntry* entry = result.value().entry;
   ASSERT_THAT(entry, NotNull());
-  EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined));
-  EXPECT_TRUE(entry->symbol_status.allow_new);
+  EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(entry->allow_new);
 }
 
 TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
@@ -833,6 +854,22 @@
         <item type="string" name="bar" />
       </overlayable>)";
   ASSERT_TRUE(TestParse(input));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      table_.FindResource(test::ParseNameOrDie("string/bar"));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(search_result.value().entry->overlayable);
+}
+
+TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
+  std::string input = R"(
+      <overlayable>
+        <item type="string" name="foo" />
+        <item type="string" name="foo" />
+      </overlayable>)";
+  EXPECT_FALSE(TestParse(input));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 0304e21..3172892 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -22,6 +22,7 @@
 #include <tuple>
 
 #include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 #include "androidfw/ResourceTypes.h"
 
 #include "ConfigDescription.h"
@@ -33,6 +34,7 @@
 
 using ::aapt::text::IsValidResourceEntryName;
 using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
@@ -45,7 +47,7 @@
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
-ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
   const auto last = packages.end();
   auto iter = std::lower_bound(packages.begin(), last, name,
                                less_than_struct_with_name<ResourceTablePackage>);
@@ -55,7 +57,7 @@
   return nullptr;
 }
 
-ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const {
   for (auto& package : packages) {
     if (package->id && package->id.value() == id) {
       return package.get();
@@ -206,30 +208,23 @@
   return results;
 }
 
-/**
- * The default handler for collisions.
- *
- * Typically, a weak value will be overridden by a strong value. An existing
- * weak
- * value will not be overridden by an incoming weak value.
- *
- * There are some exceptions:
- *
- * Attributes: There are two types of Attribute values: USE and DECL.
- *
- * USE is anywhere an Attribute is declared without a format, and in a place
- * that would
- * be legal to declare if the Attribute already existed. This is typically in a
- * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
- * weak.
- *
- * DECL is an absolute declaration of an Attribute and specifies an explicit
- * format.
- *
- * A DECL will override a USE without error. Two DECLs must match in their
- * format for there to be
- * no error.
- */
+// The default handler for collisions.
+//
+// Typically, a weak value will be overridden by a strong value. An existing weak
+// value will not be overridden by an incoming weak value.
+//
+// There are some exceptions:
+//
+// Attributes: There are two types of Attribute values: USE and DECL.
+//
+// USE is anywhere an Attribute is declared without a format, and in a place that would
+// be legal to declare if the Attribute already existed. This is typically in a
+// <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
+//
+// DECL is an absolute declaration of an Attribute and specifies an explicit format.
+//
+// A DECL will override a USE without error. Two DECLs must match in their format for there to be
+// no error.
 ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
                                                                     Value* incoming) {
   Attribute* existing_attr = ValueCast<Attribute>(existing);
@@ -287,14 +282,14 @@
   return CollisionResult::kConflict;
 }
 
-static StringPiece ValidateName(const StringPiece& name) {
+static StringPiece ResourceNameValidator(const StringPiece& name) {
   if (!IsValidResourceEntryName(name)) {
     return name;
   }
   return {};
 }
 
-static StringPiece SkipValidateName(const StringPiece& /*name*/) {
+static StringPiece SkipNameValidator(const StringPiece& /*name*/) {
   return {};
 }
 
@@ -303,17 +298,14 @@
                                 const StringPiece& product,
                                 std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-  return AddResourceImpl(name, {}, config, product, std::move(value), ValidateName,
+  return AddResourceImpl(name, {}, config, product, std::move(value), ResourceNameValidator,
                          ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResource(const ResourceNameRef& name,
-                                const ResourceId& res_id,
-                                const ConfigDescription& config,
-                                const StringPiece& product,
-                                std::unique_ptr<Value> value,
-                                IDiagnostics* diag) {
-  return AddResourceImpl(name, res_id, config, product, std::move(value), ValidateName,
+bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+                                      const ConfigDescription& config, const StringPiece& product,
+                                      std::unique_ptr<Value> value, IDiagnostics* diag) {
+  return AddResourceImpl(name, res_id, config, product, std::move(value), ResourceNameValidator,
                          ResolveValueCollision, diag);
 }
 
@@ -322,14 +314,14 @@
                                      const Source& source,
                                      const StringPiece& path,
                                      IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, nullptr, ValidateName, diag);
+  return AddFileReferenceImpl(name, config, source, path, nullptr, ResourceNameValidator, diag);
 }
 
-bool ResourceTable::AddFileReferenceAllowMangled(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const Source& source, const StringPiece& path, io::IFile* file,
-    IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, file, SkipValidateName, diag);
+bool ResourceTable::AddFileReferenceMangled(const ResourceNameRef& name,
+                                            const ConfigDescription& config, const Source& source,
+                                            const StringPiece& path, io::IFile* file,
+                                            IDiagnostics* diag) {
+  return AddFileReferenceImpl(name, config, source, path, file, SkipNameValidator, diag);
 }
 
 bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name,
@@ -344,88 +336,85 @@
                          name_validator, ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
-                                            const ConfigDescription& config,
-                                            const StringPiece& product,
-                                            std::unique_ptr<Value> value,
-                                            IDiagnostics* diag) {
-  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                                       const StringPiece& product, std::unique_ptr<Value> value,
+                                       IDiagnostics* diag) {
+  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator,
                          ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
-                                            const ResourceId& id,
-                                            const ConfigDescription& config,
-                                            const StringPiece& product,
-                                            std::unique_ptr<Value> value,
-                                            IDiagnostics* diag) {
-  return AddResourceImpl(name, id, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+                                             const ConfigDescription& config,
+                                             const StringPiece& product,
+                                             std::unique_ptr<Value> value, IDiagnostics* diag) {
+  return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator,
                          ResolveValueCollision, diag);
 }
 
+bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name,
+                                 const Source& source, IDiagnostics* diag) {
+  const StringPiece bad_char = name_validator(name.entry);
+  if (!bad_char.empty()) {
+    diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '"
+                                    << name.entry << "'. Invalid character '" << bad_char << "'");
+    return false;
+  }
+  return true;
+}
+
 bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
                                     const ConfigDescription& config, const StringPiece& product,
                                     std::unique_ptr<Value> value, NameValidator name_validator,
-                                    const CollisionResolverFunc& conflictResolver,
+                                    const CollisionResolverFunc& conflict_resolver,
                                     IDiagnostics* diag) {
   CHECK(value != nullptr);
   CHECK(diag != nullptr);
 
-  const StringPiece bad_char = name_validator(name.entry);
-  if (!bad_char.empty()) {
-    diag->Error(DiagMessage(value->GetSource()) << "resource '" << name
-                                                << "' has invalid entry name '" << name.entry
-                                                << "'. Invalid character '" << bad_char << "'");
-
+  const Source& source = value->GetSource();
+  if (!ValidateName(name_validator, name, source, diag)) {
     return false;
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
   if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
-    diag->Error(DiagMessage(value->GetSource())
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but package '" << package->name << "' already has ID "
-                << std::hex << (int)package->id.value() << std::dec);
+    diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+                                    << " but package '" << package->name << "' already has ID "
+                                    << StringPrintf("%02x", package->id.value()));
     return false;
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
   if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
-    diag->Error(DiagMessage(value->GetSource())
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but type '" << type->type << "' already has ID "
-                << std::hex << (int)type->id.value() << std::dec);
+    diag->Error(DiagMessage(source)
+                << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+                << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
     return false;
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
   if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
-    diag->Error(DiagMessage(value->GetSource())
+    diag->Error(DiagMessage(source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
-                << ResourceId(package->id.value(), type->id.value(),
-                              entry->id.value()));
+                << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
     return false;
   }
 
   ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
-  if (!config_value->value) {
+  if (config_value->value == nullptr) {
     // Resource does not exist, add it now.
     config_value->value = std::move(value);
-
   } else {
-    switch (conflictResolver(config_value->value.get(), value.get())) {
+    switch (conflict_resolver(config_value->value.get(), value.get())) {
       case CollisionResult::kTakeNew:
         // Take the incoming value.
         config_value->value = std::move(value);
         break;
 
       case CollisionResult::kConflict:
-        diag->Error(DiagMessage(value->GetSource())
-                    << "duplicate value for resource '" << name << "' "
-                    << "with config '" << config << "'");
-        diag->Error(DiagMessage(config_value->value->GetSource())
-                    << "resource previously defined here");
+        diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' "
+                                        << "with config '" << config << "'");
+        diag->Error(DiagMessage(source) << "resource previously defined here");
         return false;
 
       case CollisionResult::kKeepOriginal:
@@ -441,52 +430,56 @@
   return true;
 }
 
-bool ResourceTable::SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
-                                   const Symbol& symbol, IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, ValidateName, diag);
+bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility,
+                                  IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, ResourceId{}, ResourceNameValidator, diag);
 }
 
-bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
-                                               const ResourceId& res_id,
-                                               const Symbol& symbol,
-                                               IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, SkipValidateName, diag);
+bool ResourceTable::SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+                                         IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, ResourceId{}, SkipNameValidator, diag);
 }
 
-bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
-                                       const Symbol& symbol, NameValidator name_validator,
-                                       IDiagnostics* diag) {
+bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+                                        const ResourceId& res_id, IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name,
+                                               const Visibility& visibility,
+                                               const ResourceId& res_id, IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+                                      const ResourceId& res_id, NameValidator name_validator,
+                                      IDiagnostics* diag) {
   CHECK(diag != nullptr);
 
-  const StringPiece bad_char = name_validator(name.entry);
-  if (!bad_char.empty()) {
-    diag->Error(DiagMessage(symbol.source) << "resource '" << name << "' has invalid entry name '"
-                                           << name.entry << "'. Invalid character '" << bad_char
-                                           << "'");
+  const Source& source = visibility.source;
+  if (!ValidateName(name_validator, name, source, diag)) {
     return false;
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
   if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
-    diag->Error(DiagMessage(symbol.source)
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but package '" << package->name << "' already has ID "
-                << std::hex << (int)package->id.value() << std::dec);
+    diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+                                    << " but package '" << package->name << "' already has ID "
+                                    << StringPrintf("%02x", package->id.value()));
     return false;
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
   if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
-    diag->Error(DiagMessage(symbol.source)
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but type '" << type->type << "' already has ID "
-                << std::hex << (int)type->id.value() << std::dec);
+    diag->Error(DiagMessage(source)
+                << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+                << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
     return false;
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
   if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
-    diag->Error(DiagMessage(symbol.source)
+    diag->Error(DiagMessage(source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
                 << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
@@ -499,48 +492,96 @@
     entry->id = res_id.entry_id();
   }
 
-  // Only mark the type state as public, it doesn't care about being private.
-  if (symbol.state == SymbolState::kPublic) {
-    type->symbol_status.state = SymbolState::kPublic;
+  // Only mark the type visibility level as public, it doesn't care about being private.
+  if (visibility.level == Visibility::Level::kPublic) {
+    type->visibility_level = Visibility::Level::kPublic;
   }
 
-  if (symbol.allow_new) {
-    // This symbol can be added as a new resource when merging (if it belongs to an overlay).
-    entry->symbol_status.allow_new = true;
-  }
-
-  if (symbol.state == SymbolState::kUndefined &&
-      entry->symbol_status.state != SymbolState::kUndefined) {
+  if (visibility.level == Visibility::Level::kUndefined &&
+      entry->visibility.level != Visibility::Level::kUndefined) {
     // We can't undefine a symbol (remove its visibility). Ignore.
     return true;
   }
 
-  if (symbol.state == SymbolState::kPrivate &&
-      entry->symbol_status.state == SymbolState::kPublic) {
+  if (visibility.level < entry->visibility.level) {
     // We can't downgrade public to private. Ignore.
     return true;
   }
 
   // This symbol definition takes precedence, replace.
-  entry->symbol_status.state = symbol.state;
-  entry->symbol_status.source = symbol.source;
-  entry->symbol_status.comment = symbol.comment;
+  entry->visibility = visibility;
   return true;
 }
 
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) {
+bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new,
+                                IDiagnostics* diag) {
+  return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+                                       IDiagnostics* diag) {
+  return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+                                    NameValidator name_validator, IDiagnostics* diag) {
+  CHECK(diag != nullptr);
+
+  if (!ValidateName(name_validator, name, allow_new.source, diag)) {
+    return false;
+  }
+
+  ResourceTablePackage* package = FindOrCreatePackage(name.package);
+  ResourceTableType* type = package->FindOrCreateType(name.type);
+  ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+  entry->allow_new = allow_new;
+  return true;
+}
+
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+                                   IDiagnostics* diag) {
+  return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
+                                          const Overlayable& overlayable, IDiagnostics* diag) {
+  return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+                                       NameValidator name_validator, IDiagnostics* diag) {
+  CHECK(diag != nullptr);
+
+  if (!ValidateName(name_validator, name, overlayable.source, diag)) {
+    return false;
+  }
+
+  ResourceTablePackage* package = FindOrCreatePackage(name.package);
+  ResourceTableType* type = package->FindOrCreateType(name.type);
+  ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+  if (entry->overlayable) {
+    diag->Error(DiagMessage(overlayable.source)
+                << "duplicate overlayable declaration for resource '" << name << "'");
+    diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+    return false;
+  }
+  entry->overlayable = overlayable;
+  return true;
+}
+
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
   ResourceTablePackage* package = FindPackage(name.package);
-  if (!package) {
+  if (package == nullptr) {
     return {};
   }
 
   ResourceTableType* type = package->FindType(name.type);
-  if (!type) {
+  if (type == nullptr) {
     return {};
   }
 
   ResourceEntry* entry = type->FindEntry(name.entry);
-  if (!entry) {
+  if (entry == nullptr) {
     return {};
   }
   return SearchResult{package, type, entry};
@@ -552,23 +593,20 @@
     ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
     for (const auto& type : pkg->types) {
       ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
-      if (!new_type->id) {
-        new_type->id = type->id;
-        new_type->symbol_status = type->symbol_status;
-      }
+      new_type->id = type->id;
+      new_type->visibility_level = type->visibility_level;
 
       for (const auto& entry : type->entries) {
         ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
-        if (!new_entry->id) {
-          new_entry->id = entry->id;
-          new_entry->symbol_status = entry->symbol_status;
-        }
+        new_entry->id = entry->id;
+        new_entry->visibility = entry->visibility;
+        new_entry->allow_new = entry->allow_new;
+        new_entry->overlayable = entry->overlayable;
 
         for (const auto& config_value : entry->values) {
           ResourceConfigValue* new_value =
               new_entry->FindOrCreateValue(config_value->config, config_value->product);
-          Value* value = config_value->value->Clone(&new_table->string_pool);
-          new_value->value = std::unique_ptr<Value>(value);
+          new_value->value.reset(config_value->value->Clone(&new_table->string_pool));
         }
       }
     }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index d5db67e..eaa2d0b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -38,40 +38,40 @@
 
 namespace aapt {
 
-enum class SymbolState {
-  kUndefined,
-  kPrivate,
-  kPublic,
+// The Public status of a resource.
+struct Visibility {
+  enum class Level {
+    kUndefined,
+    kPrivate,
+    kPublic,
+  };
+
+  Level level = Level::kUndefined;
+  Source source;
+  std::string comment;
 };
 
-/**
- * The Public status of a resource.
- */
-struct Symbol {
-  SymbolState state = SymbolState::kUndefined;
+// Represents <add-resource> in an overlay.
+struct AllowNew {
   Source source;
+  std::string comment;
+};
 
-  // Whether this entry (originating from an overlay) can be added as a new resource.
-  bool allow_new = false;
-
+// The policy dictating whether an entry is overlayable at runtime by RROs.
+struct Overlayable {
+  Source source;
   std::string comment;
 };
 
 class ResourceConfigValue {
  public:
-  /**
-   * The configuration for which this value is defined.
-   */
+  // The configuration for which this value is defined.
   const ConfigDescription config;
 
-  /**
-   * The product for which this value is defined.
-   */
+  // The product for which this value is defined.
   const std::string product;
 
-  /**
-   * The actual Value.
-   */
+  // The actual Value.
   std::unique_ptr<Value> value;
 
   ResourceConfigValue(const ConfigDescription& config, const android::StringPiece& product)
@@ -87,27 +87,21 @@
  */
 class ResourceEntry {
  public:
-  /**
-   * The name of the resource. Immutable, as
-   * this determines the order of this resource
-   * when doing lookups.
-   */
+  // The name of the resource. Immutable, as this determines the order of this resource
+  // when doing lookups.
   const std::string name;
 
-  /**
-   * The entry ID for this resource.
-   */
+  // The entry ID for this resource (the EEEE in 0xPPTTEEEE).
   Maybe<uint16_t> id;
 
-  /**
-   * Whether this resource is public (and must maintain the same entry ID across
-   * builds).
-   */
-  Symbol symbol_status;
+  // Whether this resource is public (and must maintain the same entry ID across builds).
+  Visibility visibility;
 
-  /**
-   * The resource's values for each configuration.
-   */
+  Maybe<AllowNew> allow_new;
+
+  Maybe<Overlayable> overlayable;
+
+  // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
   explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
@@ -125,31 +119,19 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
 };
 
-/**
- * Represents a resource type, which holds entries defined
- * for this type.
- */
+// Represents a resource type (eg. string, drawable, layout, etc.) containing resource entries.
 class ResourceTableType {
  public:
-  /**
-   * The logical type of resource (string, drawable, layout, etc.).
-   */
+  // The logical type of resource (string, drawable, layout, etc.).
   const ResourceType type;
 
-  /**
-   * The type ID for this resource.
-   */
+  // The type ID for this resource (the TT in 0xPPTTEEEE).
   Maybe<uint8_t> id;
 
-  /**
-   * Whether this type is public (and must maintain the same
-   * type ID across builds).
-   */
-  Symbol symbol_status;
+  // Whether this type is public (and must maintain the same type ID across builds).
+  Visibility::Level visibility_level = Visibility::Level::kUndefined;
 
-  /**
-   * List of resources for this type.
-   */
+  // List of resources for this type.
   std::vector<std::unique_ptr<ResourceEntry>> entries;
 
   explicit ResourceTableType(const ResourceType type) : type(type) {}
@@ -163,9 +145,11 @@
 
 class ResourceTablePackage {
  public:
-  Maybe<uint8_t> id;
   std::string name;
 
+  // The package ID (the PP in 0xPPTTEEEE).
+  Maybe<uint8_t> id;
+
   std::vector<std::unique_ptr<ResourceTableType>> types;
 
   ResourceTablePackage() = default;
@@ -176,10 +160,7 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
 };
 
-/**
- * The container and index for all resources defined for an app. This gets
- * flattened into a binary resource table (resources.arsc).
- */
+// The container and index for all resources defined for an app.
 class ResourceTable {
  public:
   ResourceTable() = default;
@@ -188,47 +169,51 @@
 
   using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
 
-  /**
-   * When a collision of resources occurs, this method decides which value to
-   * keep.
-   */
+  // When a collision of resources occurs, this method decides which value to keep.
   static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
 
   bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
                    const android::StringPiece& product, std::unique_ptr<Value> value,
                    IDiagnostics* diag);
 
-  bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
-                   const ConfigDescription& config, const android::StringPiece& product,
-                   std::unique_ptr<Value> value, IDiagnostics* diag);
+  bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+                         const ConfigDescription& config, const android::StringPiece& product,
+                         std::unique_ptr<Value> value, IDiagnostics* diag);
 
   bool AddFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                         const Source& source, const android::StringPiece& path, IDiagnostics* diag);
 
-  bool AddFileReferenceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                    const Source& source, const android::StringPiece& path,
-                                    io::IFile* file, IDiagnostics* diag);
+  bool AddFileReferenceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                               const Source& source, const android::StringPiece& path,
+                               io::IFile* file, IDiagnostics* diag);
 
-  /**
-   * Same as AddResource, but doesn't verify the validity of the name. This is
-   * used
-   * when loading resources from an existing binary resource table that may have
-   * mangled
-   * names.
-   */
-  bool AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                               const android::StringPiece& product, std::unique_ptr<Value> value,
-                               IDiagnostics* diag);
+  // Same as AddResource, but doesn't verify the validity of the name. This is used
+  // when loading resources from an existing binary resource table that may have mangled names.
+  bool AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                          const android::StringPiece& product, std::unique_ptr<Value> value,
+                          IDiagnostics* diag);
 
-  bool AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id,
-                               const ConfigDescription& config, const android::StringPiece& product,
-                               std::unique_ptr<Value> value, IDiagnostics* diag);
+  bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+                                const ConfigDescription& config,
+                                const android::StringPiece& product, std::unique_ptr<Value> value,
+                                IDiagnostics* diag);
 
-  bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
-                      const Symbol& symbol, IDiagnostics* diag);
+  bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag);
+  bool SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+                            IDiagnostics* diag);
+  bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+                           const ResourceId& res_id, IDiagnostics* diag);
+  bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
+                                  const ResourceId& res_id, IDiagnostics* diag);
 
-  bool SetSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& res_id,
-                                  const Symbol& symbol, IDiagnostics* diag);
+  bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+                      IDiagnostics* diag);
+  bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+                             IDiagnostics* diag);
+
+  bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
+  bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+                          IDiagnostics* diag);
 
   struct SearchResult {
     ResourceTablePackage* package;
@@ -236,40 +221,28 @@
     ResourceEntry* entry;
   };
 
-  Maybe<SearchResult> FindResource(const ResourceNameRef& name);
+  Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
 
-  /**
-   * Returns the package struct with the given name, or nullptr if such a
-   * package does not
-   * exist. The empty string is a valid package and typically is used to
-   * represent the
-   * 'current' package before it is known to the ResourceTable.
-   */
-  ResourceTablePackage* FindPackage(const android::StringPiece& name);
+  // Returns the package struct with the given name, or nullptr if such a package does not
+  // exist. The empty string is a valid package and typically is used to represent the
+  // 'current' package before it is known to the ResourceTable.
+  ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
 
-  ResourceTablePackage* FindPackageById(uint8_t id);
+  ResourceTablePackage* FindPackageById(uint8_t id) const;
 
   ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
 
   std::unique_ptr<ResourceTable> Clone() const;
 
-  /**
-   * The string pool used by this resource table. Values that reference strings
-   * must use
-   * this pool to create their strings.
-   *
-   * NOTE: `string_pool` must come before `packages` so that it is destroyed
-   * after.
-   * When `string_pool` references are destroyed (as they will be when
-   * `packages`
-   * is destroyed), they decrement a refCount, which would cause invalid
-   * memory access if the pool was already destroyed.
-   */
+  // The string pool used by this resource table. Values that reference strings must use
+  // this pool to create their strings.
+  // NOTE: `string_pool` must come before `packages` so that it is destroyed after.
+  // When `string_pool` references are destroyed (as they will be when `packages` is destroyed),
+  // they decrement a refCount, which would cause invalid memory access if the pool was already
+  // destroyed.
   StringPool string_pool;
 
-  /**
-   * The list of packages in this table, sorted alphabetically by package name.
-   */
+  // The list of packages in this table, sorted alphabetically by package name.
   std::vector<std::unique_ptr<ResourceTablePackage>> packages;
 
   // Set of dynamic packages that this table may reference. Their package names get encoded
@@ -284,6 +257,9 @@
 
   ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
 
+  bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source,
+                    IDiagnostics* diag);
+
   bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
                        const ConfigDescription& config, const android::StringPiece& product,
                        std::unique_ptr<Value> value, NameValidator name_validator,
@@ -293,8 +269,19 @@
                             const Source& source, const android::StringPiece& path, io::IFile* file,
                             NameValidator name_validator, IDiagnostics* diag);
 
+  bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+                         const ResourceId& res_id, NameValidator name_validator,
+                         IDiagnostics* diag);
+
+  bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+                       NameValidator name_validator, IDiagnostics* diag);
+
+  bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+                          NameValidator name_validator, IDiagnostics* diag);
+
   bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
-                          const Symbol& symbol, NameValidator name_validator, IDiagnostics* diag);
+                          const Visibility& symbol, NameValidator name_validator,
+                          IDiagnostics* diag);
 
   DISALLOW_COPY_AND_ASSIGN(ResourceTable);
 };
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2a3c131..eb75f94 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -24,7 +24,10 @@
 #include <ostream>
 #include <string>
 
+using ::android::StringPiece;
+using ::testing::Eq;
 using ::testing::NotNull;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -45,7 +48,7 @@
 TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
   ResourceTable table;
 
-  EXPECT_TRUE(table.AddResourceAllowMangled(
+  EXPECT_TRUE(table.AddResourceMangled(
       test::ParseNameOrDie("android:id/heythere       "), ConfigDescription{}, "",
       test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
 }
@@ -141,4 +144,104 @@
   EXPECT_EQ(std::string("tablet"), values[1]->product);
 }
 
+static StringPiece LevelToString(Visibility::Level level) {
+  switch (level) {
+    case Visibility::Level::kPrivate:
+      return "private";
+    case Visibility::Level::kPublic:
+      return "private";
+    default:
+      return "undefined";
+  }
+}
+
+static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
+                                                       const ResourceNameRef& name,
+                                                       Visibility::Level level,
+                                                       const StringPiece& comment) {
+  Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+  if (!result) {
+    return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
+  }
+
+  const Visibility& visibility = result.value().entry->visibility;
+  if (visibility.level != level) {
+    return ::testing::AssertionFailure() << "expected visibility " << LevelToString(level)
+                                         << " but got " << LevelToString(visibility.level);
+  }
+
+  if (visibility.comment != comment) {
+    return ::testing::AssertionFailure() << "expected visibility comment '" << comment
+                                         << "' but got '" << visibility.comment << "'";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+TEST(ResourceTableTest, SetVisibility) {
+  using Level = Visibility::Level;
+
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  Visibility visibility;
+  visibility.level = Visibility::Level::kPrivate;
+  visibility.comment = "private";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+  visibility.level = Visibility::Level::kUndefined;
+  visibility.comment = "undefined";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+  visibility.level = Visibility::Level::kPublic;
+  visibility.comment = "public";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+
+  visibility.level = Visibility::Level::kPrivate;
+  visibility.comment = "private";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+}
+
+TEST(ResourceTableTest, SetAllowNew) {
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  AllowNew allow_new;
+  Maybe<ResourceTable::SearchResult> result;
+
+  allow_new.comment = "first";
+  ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+  result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->allow_new);
+  ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
+
+  allow_new.comment = "second";
+  ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+  result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->allow_new);
+  ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
+}
+
+TEST(ResourceTableTest, SetOverlayable) {
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  Overlayable overlayable;
+
+  overlayable.comment = "first";
+  ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+  Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->overlayable);
+  ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
+
+  overlayable.comment = "second";
+  ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7e7c86d..8552195 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -93,11 +93,10 @@
   repeated Entry entry = 3;
 }
 
-// The status of a symbol/entry. This contains information like visibility (public/private),
-// comments, and whether the entry can be overridden.
-message SymbolStatus {
+// The Visibility of a symbol/entry (public, private, undefined).
+message Visibility {
   // The visibility of the resource outside of its package.
-  enum Visibility {
+  enum Level {
     // No visibility was explicitly specified. This is typically treated as private.
     // The distinction is important when two separate R.java files are generated: a public and
     // private one. An unknown visibility, in this case, would cause the resource to be omitted
@@ -115,17 +114,32 @@
     PUBLIC = 2;
   }
 
-  Visibility visibility = 1;
+  Level level = 1;
 
   // The path at which this entry's visibility was defined (eg. public.xml).
   Source source = 2;
 
   // The comment associated with the <public> tag.
   string comment = 3;
+}
 
-  // Whether the symbol can be merged into another resource table without there being an existing
-  // definition to override. Used for overlays and set to true when <add-resource> is specified.
-  bool allow_new = 4;
+// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an
+// existing resource.
+message AllowNew {
+  // Where this was defined in source.
+  Source source = 1;
+
+  // Any comment associated with the declaration.
+  string comment = 2;
+}
+
+// Whether a resource is overlayable by runtime resource overlays (RRO).
+message Overlayable {
+  // Where this declaration was defined in source.
+  Source source = 1;
+
+  // Any comment associated with the declaration.
+  string comment = 2;
 }
 
 // An entry ID in the range [0x0000, 0xffff].
@@ -147,12 +161,19 @@
   // form package:type/entry.
   string name = 2;
 
-  // The symbol status of this entry, which includes visibility information.
-  SymbolStatus symbol_status = 3;
+  // The visibility of this entry (public, private, undefined).
+  Visibility visibility = 3;
+
+  // Whether this resource, when originating from a compile-time overlay, is allowed to NOT overlay
+  // any existing resources.
+  AllowNew allow_new = 4;
+
+  // Whether this resource can be overlaid by a runtime resource overlay (RRO).
+  Overlayable overlayable = 5;
 
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
-  repeated ConfigValue config_value = 4;
+  repeated ConfigValue config_value = 6;
 }
 
 // A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 83512b9..7c1e96e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -49,6 +49,7 @@
 #include "xml/XmlPullParser.h"
 
 using ::aapt::io::FileInputStream;
+using ::aapt::text::Printer;
 using ::android::StringPiece;
 using ::android::base::SystemErrorCodeToString;
 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -112,6 +113,7 @@
 struct CompileOptions {
   std::string output_path;
   Maybe<std::string> res_dir;
+  Maybe<std::string> generate_text_symbols_path;
   bool pseudolocalize = false;
   bool no_png_crunch = false;
   bool legacy_mode = false;
@@ -261,6 +263,58 @@
     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
     return false;
   }
+
+  if (options.generate_text_symbols_path) {
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+    if (fout_text.HadError()) {
+      context->GetDiagnostics()->Error(DiagMessage()
+                                       << "failed writing to'"
+                                       << options.generate_text_symbols_path.value()
+                                       << "': " << fout_text.GetError());
+      return false;
+    }
+
+    Printer r_txt_printer(&fout_text);
+    for (const auto& package : table.packages) {
+      for (const auto& type : package->types) {
+        for (const auto& entry : type->entries) {
+          // Check access modifiers.
+          switch(entry->visibility.level) {
+            case Visibility::Level::kUndefined :
+              r_txt_printer.Print("default ");
+              break;
+            case Visibility::Level::kPublic :
+              r_txt_printer.Print("public ");
+              break;
+            case Visibility::Level::kPrivate :
+              r_txt_printer.Print("private ");
+          }
+
+          if (type->type != ResourceType::kStyleable) {
+            r_txt_printer.Print("int ");
+            r_txt_printer.Print(to_string(type->type));
+            r_txt_printer.Print(" ");
+            r_txt_printer.Println(entry->name);
+          } else {
+            r_txt_printer.Print("int[] styleable ");
+            r_txt_printer.Println(entry->name);
+
+            if (!entry->values.empty()) {
+              auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
+              for (const auto& attr : styleable->entries) {
+                r_txt_printer.Print("default int styleable ");
+                r_txt_printer.Print(entry->name);
+                r_txt_printer.Print("_");
+                r_txt_printer.Println(attr.name.value().entry);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
   return true;
 }
 
@@ -402,6 +456,31 @@
     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
     return false;
   }
+
+  if (options.generate_text_symbols_path) {
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+    if (fout_text.HadError()) {
+      context->GetDiagnostics()->Error(DiagMessage()
+                                       << "failed writing to'"
+                                       << options.generate_text_symbols_path.value()
+                                       << "': " << fout_text.GetError());
+      return false;
+    }
+
+    Printer r_txt_printer(&fout_text);
+    for (const auto res : xmlres->file.exported_symbols) {
+      r_txt_printer.Print("default int id ");
+      r_txt_printer.Println(res.name.entry);
+    }
+
+    // And print ourselves.
+    r_txt_printer.Print("default int ");
+    r_txt_printer.Print(path_data.resource_dir);
+    r_txt_printer.Print(" ");
+    r_txt_printer.Println(path_data.name);
+  }
+
   return true;
 }
 
@@ -609,6 +688,10 @@
       Flags()
           .RequiredFlag("-o", "Output path", &options.output_path)
           .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
+          .OptionalFlag("--output-text-symbols",
+                        "Generates a text file containing the resource symbols in the\n"
+                        "specified file",
+                        &options.generate_text_symbols_path)
           .OptionalSwitch("--pseudo-localize",
                           "Generate resources for pseudo-locales "
                           "(en-XA and ar-XB)",
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index fc1f1d6..12113ed 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -75,14 +75,14 @@
   std::cerr << source << ": " << message << "\n";
 }
 
-static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
-  return symbol_a.state != symbol_b.state;
+static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
+  return vis_a.level != vis_b.level;
 }
 
 template <typename Id>
-static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
-                     const Maybe<Id>& id_b) {
-  if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
+static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
+                     const Visibility::Level& level_b, const Maybe<Id>& id_b) {
+  if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) {
     return id_a != id_b;
   }
   return false;
@@ -157,17 +157,17 @@
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
-      if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
+      if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
                    << " has different visibility (";
-        if (entry_b->symbol_status.state == SymbolState::kPublic) {
+        if (entry_b->visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (entry_a->symbol_status.state == SymbolState::kPublic) {
+        if (entry_a->visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -175,7 +175,7 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
+      } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
                           entry_b->id)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
@@ -225,16 +225,16 @@
       EmitDiffLine(apk_a->GetSource(), str_stream.str());
       diff = true;
     } else {
-      if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
+      if (type_a->visibility_level != type_b->visibility_level) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
-        if (type_b->symbol_status.state == SymbolState::kPublic) {
+        if (type_b->visibility_level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (type_a->symbol_status.state == SymbolState::kPublic) {
+        if (type_a->visibility_level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -242,7 +242,8 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
+      } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level,
+                          type_b->id)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
         if (type_b->id) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index bc7f5a8..3d2fb55 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -69,15 +69,13 @@
   printer->Println(StringPrintf("Data:     offset=%" PRIi64 " length=%zd", offset, len));
 }
 
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
+                        const DebugPrintTableOptions& print_options) {
   // Use a smaller buffer so that there is less latency for dumping to stdout.
   constexpr size_t kStdOutBufferSize = 1024u;
   io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
   Printer printer(&fout);
 
-  DebugPrintTableOptions print_options;
-  print_options.show_sources = true;
-
   std::string err;
   std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
   if (zip) {
@@ -244,7 +242,12 @@
 // Entry point for dump command.
 int Dump(const std::vector<StringPiece>& args) {
   bool verbose = false;
-  Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
+  bool no_values = false;
+  Flags flags = Flags()
+                    .OptionalSwitch("--no-values",
+                                    "Suppresses output of values when displaying resource tables.",
+                                    &no_values)
+                    .OptionalSwitch("-v", "increase verbosity of output", &verbose);
   if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
     return 1;
   }
@@ -252,8 +255,11 @@
   DumpContext context;
   context.SetVerbose(verbose);
 
+  DebugPrintTableOptions dump_table_options;
+  dump_table_options.show_sources = true;
+  dump_table_options.show_values = !no_values;
   for (const std::string& arg : flags.GetArgs()) {
-    if (!TryDumpFile(&context, arg)) {
+    if (!TryDumpFile(&context, arg, dump_table_options)) {
       return 1;
     }
   }
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index d782de5..72e07dc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -631,9 +631,9 @@
 
               dst_path =
                   ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler());
-              bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config,
-                                                                doc->file.source, dst_path, nullptr,
-                                                                context_->GetDiagnostics());
+              bool result =
+                  table->AddFileReferenceMangled(doc->file.name, doc->file.config, doc->file.source,
+                                                 dst_path, nullptr, context_->GetDiagnostics());
               if (!result) {
                 return false;
               }
@@ -1343,9 +1343,9 @@
 
       std::unique_ptr<Id> id = util::make_unique<Id>();
       id->SetSource(source.WithLine(exported_symbol.line));
-      bool result = final_table_.AddResourceAllowMangled(
-          res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
-          context_->GetDiagnostics());
+      bool result =
+          final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(),
+                                          std::string(), std::move(id), context_->GetDiagnostics());
       if (!result) {
         return false;
       }
@@ -2121,6 +2121,9 @@
                         &options.manifest_fixer_options.rename_instrumentation_target_package)
           .OptionalFlagList("-0", "File extensions not to compress.",
                             &options.extensions_to_not_compress)
+          .OptionalSwitch("--warn-manifest-validation",
+                          "Treat manifest validation errors as warnings.",
+                          &options.manifest_fixer_options.warn_validation)
           .OptionalFlagList("--split",
                             "Split resources matching a set of configs out to a Split APK.\n"
                             "Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d8bb999..9c76119 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -377,44 +377,10 @@
   }
 
   const std::string& apk_path = flags.GetArgs()[0];
-  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
-  if (!apk) {
-    return 1;
-  }
 
   context.SetVerbose(verbose);
   IDiagnostics* diag = context.GetDiagnostics();
 
-  if (target_densities) {
-    // Parse the target screen densities.
-    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
-      Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
-      if (!target_density) {
-        return 1;
-      }
-      options.table_splitter_options.preferred_densities.push_back(target_density.value());
-    }
-  }
-
-  std::unique_ptr<IConfigFilter> filter;
-  if (!configs.empty()) {
-    filter = ParseConfigFilterParameters(configs, diag);
-    if (filter == nullptr) {
-      return 1;
-    }
-    options.table_splitter_options.config_filter = filter.get();
-  }
-
-  // Parse the split parameters.
-  for (const std::string& split_arg : split_args) {
-    options.split_paths.emplace_back();
-    options.split_constraints.emplace_back();
-    if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
-                             &options.split_constraints.back())) {
-      return 1;
-    }
-  }
-
   if (config_path) {
     std::string& path = config_path.value();
     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
@@ -456,6 +422,41 @@
     return 1;
   }
 
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
+  if (!apk) {
+    return 1;
+  }
+
+  if (target_densities) {
+    // Parse the target screen densities.
+    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+      Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
+      if (!target_density) {
+        return 1;
+      }
+      options.table_splitter_options.preferred_densities.push_back(target_density.value());
+    }
+  }
+
+  std::unique_ptr<IConfigFilter> filter;
+  if (!configs.empty()) {
+    filter = ParseConfigFilterParameters(configs, diag);
+    if (filter == nullptr) {
+      return 1;
+    }
+    options.table_splitter_options.config_filter = filter.get();
+  }
+
+  // Parse the split parameters.
+  for (const std::string& split_arg : split_args) {
+    options.split_paths.emplace_back();
+    options.split_constraints.emplace_back();
+    if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
+                             &options.split_constraints.back())) {
+      return 1;
+    }
+  }
+
   if (options.table_flattener_options.collapse_key_stringpool) {
     if (whitelist_path) {
       std::string& path = whitelist_path.value();
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index ebc523f..eabeb47 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -49,13 +49,15 @@
 using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::configuration::DeviceFeature;
 using ::aapt::configuration::Entry;
+using ::aapt::configuration::ExtractConfiguration;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Group;
 using ::aapt::configuration::Locale;
+using ::aapt::configuration::OrderedEntry;
 using ::aapt::configuration::OutputArtifact;
 using ::aapt::configuration::PostProcessingConfiguration;
 using ::aapt::configuration::handler::AbiGroupTagHandler;
-using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkTagHandler;
 using ::aapt::configuration::handler::ArtifactFormatTagHandler;
 using ::aapt::configuration::handler::ArtifactTagHandler;
 using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
@@ -130,7 +132,7 @@
     return false;
   }
 
-  for (const T& item : group->second) {
+  for (const T& item : group->second.entry) {
     target->push_back(item);
   }
   return true;
@@ -188,61 +190,6 @@
   };
 }
 
-/** Returns the binary reprasentation of the XML configuration. */
-Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
-                                                        IDiagnostics* diag) {
-  StringInputStream in(contents);
-  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
-  if (!doc) {
-    return {};
-  }
-
-  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
-  Element* root = doc->root.get();
-  if (root == nullptr) {
-    diag->Error(DiagMessage() << "Could not find the root element in the XML document");
-    return {};
-  }
-
-  std::string& xml_ns = root->namespace_uri;
-  if (!xml_ns.empty()) {
-    if (xml_ns != kAaptXmlNs) {
-      diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
-      return {};
-    }
-
-    xml_ns.clear();
-    NamespaceVisitor visitor;
-    root->Accept(&visitor);
-  }
-
-  XmlActionExecutor executor;
-  XmlNodeAction& root_action = executor["post-process"];
-  XmlNodeAction& artifacts_action = root_action["artifacts"];
-  XmlNodeAction& groups_action = root_action["groups"];
-
-  PostProcessingConfiguration config;
-
-  // Parse the artifact elements.
-  artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
-  artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
-
-  // Parse the different configuration groups.
-  groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
-  groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
-  groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
-  groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
-  groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
-  groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
-
-  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
-    diag->Error(DiagMessage() << "Could not process XML document");
-    return {};
-  }
-
-  return {config};
-}
-
 /** Converts a ConfiguredArtifact into an OutputArtifact. */
 Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
                                        const std::string& apk_name,
@@ -302,11 +249,11 @@
     has_errors = true;
   }
 
-  if (artifact.android_sdk_group) {
-    auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
-    if (entry == config.android_sdk_groups.end()) {
+  if (artifact.android_sdk) {
+    auto entry = config.android_sdks.find(artifact.android_sdk.value());
+    if (entry == config.android_sdks.end()) {
       src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
-                                   << artifact.android_sdk_group.value());
+                                   << artifact.android_sdk.value());
       has_errors = true;
     } else {
       output_artifact.android_sdk = {entry->second};
@@ -323,6 +270,64 @@
 
 namespace configuration {
 
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+                                                        const std::string& config_path,
+                                                        IDiagnostics* diag) {
+  StringInputStream in(contents);
+  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
+  if (!doc) {
+    return {};
+  }
+
+  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+  Element* root = doc->root.get();
+  if (root == nullptr) {
+    diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+    return {};
+  }
+
+  std::string& xml_ns = root->namespace_uri;
+  if (!xml_ns.empty()) {
+    if (xml_ns != kAaptXmlNs) {
+      diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+      return {};
+    }
+
+    xml_ns.clear();
+    NamespaceVisitor visitor;
+    root->Accept(&visitor);
+  }
+
+  XmlActionExecutor executor;
+  XmlNodeAction& root_action = executor["post-process"];
+  XmlNodeAction& artifacts_action = root_action["artifacts"];
+
+  PostProcessingConfiguration config;
+
+  // Parse the artifact elements.
+  artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+  artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+  // Parse the different configuration groups.
+  root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+  root_action["screen-density-groups"]["screen-density-group"].Action(
+      Bind(&config, ScreenDensityGroupTagHandler));
+  root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+  root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
+  root_action["gl-texture-groups"]["gl-texture-group"].Action(
+      Bind(&config, GlTextureGroupTagHandler));
+  root_action["device-feature-groups"]["device-feature-group"].Action(
+      Bind(&config, DeviceFeatureGroupTagHandler));
+
+  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+    diag->Error(DiagMessage() << "Could not process XML document");
+    return {};
+  }
+
+  return {config};
+}
+
 const StringPiece& AbiToString(Abi abi) {
   return kAbiToStringMap.at(static_cast<size_t>(abi));
 }
@@ -383,7 +388,7 @@
     return {};
   }
 
-  if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
+  if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
     return {};
   }
 
@@ -414,47 +419,37 @@
   if (!ReadFileToString(path, &contents, true)) {
     return {};
   }
-  return ConfigurationParser(contents);
+  return ConfigurationParser(contents, path);
 }
 
-ConfigurationParser::ConfigurationParser(std::string contents)
-    : contents_(std::move(contents)),
-      diag_(&noop_) {
+ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
+    : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
 }
 
 Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
     const android::StringPiece& apk_path) {
-  Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+  Maybe<PostProcessingConfiguration> maybe_config =
+      ExtractConfiguration(contents_, config_path_, diag_);
   if (!maybe_config) {
     return {};
   }
-  const PostProcessingConfiguration& config = maybe_config.value();
-
-  // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
-  // see: https://developer.android.com/google/play/publishing/multiple-apks.html
-  //
-  // For now, make sure the version codes are unique.
-  std::vector<ConfiguredArtifact> artifacts = config.artifacts;
-  std::sort(artifacts.begin(), artifacts.end());
-  if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
-    diag_->Error(DiagMessage() << "Configuration has duplicate versions");
-    return {};
-  }
-
-  const std::string& apk_name = file::GetFilename(apk_path).to_string();
-  const StringPiece ext = file::GetExtension(apk_name);
-  const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
 
   // Convert from a parsed configuration to a list of artifacts for processing.
+  const std::string& apk_name = file::GetFilename(apk_path).to_string();
   std::vector<OutputArtifact> output_artifacts;
   bool has_errors = false;
 
-  for (const ConfiguredArtifact& artifact : artifacts) {
+  PostProcessingConfiguration& config = maybe_config.value();
+  config.SortArtifacts();
+
+  int version = 1;
+  for (const ConfiguredArtifact& artifact : config.artifacts) {
     Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
     if (!output_artifact) {
       // Defer return an error condition so that all errors are reported.
       has_errors = true;
     } else {
+      output_artifact.value().version = version++;
       output_artifacts.push_back(std::move(output_artifact.value()));
     }
   }
@@ -470,24 +465,18 @@
 
 bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
                         IDiagnostics* diag) {
-  // This will be incremented later so the first version will always be different to the base APK.
-  int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
-
   ConfiguredArtifact artifact{};
-  Maybe<int> version;
   for (const auto& attr : root_element->attributes) {
     if (attr.name == "name") {
       artifact.name = attr.value;
-    } else if (attr.name == "version") {
-      version = std::stoi(attr.value);
     } else if (attr.name == "abi-group") {
       artifact.abi_group = {attr.value};
     } else if (attr.name == "screen-density-group") {
       artifact.screen_density_group = {attr.value};
     } else if (attr.name == "locale-group") {
       artifact.locale_group = {attr.value};
-    } else if (attr.name == "android-sdk-group") {
-      artifact.android_sdk_group = {attr.value};
+    } else if (attr.name == "android-sdk") {
+      artifact.android_sdk = {attr.value};
     } else if (attr.name == "gl-texture-group") {
       artifact.gl_texture_group = {attr.value};
     } else if (attr.name == "device-feature-group") {
@@ -497,9 +486,6 @@
                                << attr.value);
     }
   }
-
-  artifact.version = (version) ? version.value() : current_version + 1;
-
   config->artifacts.push_back(artifact);
   return true;
 };
@@ -523,9 +509,19 @@
     return false;
   }
 
-  auto& group = config->abi_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->abi_groups);
   bool valid = true;
 
+  // Special case for empty abi-group tag. Label will be used as the ABI.
+  if (root_element->GetChildElements().empty()) {
+    auto abi = kStringToAbiMap.find(label);
+    if (abi == kStringToAbiMap.end()) {
+      return false;
+    }
+    group.push_back(abi->second);
+    return true;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "abi") {
       diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
@@ -534,7 +530,13 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
+          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+          if (abi != kStringToAbiMap.end()) {
+            group.push_back(abi->second);
+          } else {
+            diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
+            valid = false;
+          }
           break;
         }
       }
@@ -551,9 +553,28 @@
     return false;
   }
 
-  auto& group = config->screen_density_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
   bool valid = true;
 
+  // Special case for empty screen-density-group tag. Label will be used as the screen density.
+  if (root_element->GetChildElements().empty()) {
+    ConfigDescription config_descriptor;
+    bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+    if (parsed &&
+        (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+            android::ResTable_config::CONFIG_DENSITY)) {
+      // Copy the density with the minimum SDK version stripped out.
+      group.push_back(config_descriptor.CopyWithoutSdkVersion());
+    } else {
+      diag->Error(DiagMessage()
+                      << "Could not parse config descriptor for empty screen-density-group: "
+                      << label);
+      valid = false;
+    }
+
+    return valid;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "screen-density") {
       diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -592,9 +613,28 @@
     return false;
   }
 
-  auto& group = config->locale_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->locale_groups);
   bool valid = true;
 
+  // Special case to auto insert a locale for an empty group. Label will be used for locale.
+  if (root_element->GetChildElements().empty()) {
+    ConfigDescription config_descriptor;
+    bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+    if (parsed &&
+        (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+            android::ResTable_config::CONFIG_LOCALE)) {
+      // Copy the locale with the minimum SDK version stripped out.
+      group.push_back(config_descriptor.CopyWithoutSdkVersion());
+    } else {
+      diag->Error(DiagMessage()
+                      << "Could not parse config descriptor for empty screen-density-group: "
+                      << label);
+      valid = false;
+    }
+
+    return valid;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "locale") {
       diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -626,61 +666,58 @@
   return valid;
 };
 
-bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
-                               IDiagnostics* diag) {
-  std::string label = GetLabel(root_element, diag);
-  if (label.empty()) {
-    return false;
-  }
-
+bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                          IDiagnostics* diag) {
+  AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
   bool valid = true;
-  bool found = false;
+  for (const auto& attr : root_element->attributes) {
+    bool valid_attr = false;
+    if (attr.name == "label") {
+      entry.label = attr.value;
+      valid_attr = true;
+    } else if (attr.name == "minSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.min_sdk_version = version.value();
+      }
+    } else if (attr.name == "targetSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.target_sdk_version = version;
+      }
+    } else if (attr.name == "maxSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.max_sdk_version = version;
+      }
+    }
 
-  for (auto* child : root_element->GetChildElements()) {
-    if (child->name != "android-sdk") {
-      diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+    if (!valid_attr) {
+      diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
       valid = false;
-    } else {
-      AndroidSdk entry;
-      for (const auto& attr : child->attributes) {
-        Maybe<int>* target = nullptr;
-        if (attr.name == "minSdkVersion") {
-          target = &entry.min_sdk_version;
-        } else if (attr.name == "targetSdkVersion") {
-          target = &entry.target_sdk_version;
-        } else if (attr.name == "maxSdkVersion") {
-          target = &entry.max_sdk_version;
-        } else {
-          diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
-          continue;
-        }
-
-        *target = ResourceUtils::ParseSdkVersion(attr.value);
-        if (!*target) {
-          diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
-          valid = false;
-        }
-      }
-
-      // TODO: Fill in the manifest details when they are finalised.
-      for (auto node : child->GetChildElements()) {
-        if (node->name == "manifest") {
-          if (entry.manifest) {
-            diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
-            continue;
-          }
-          entry.manifest = {AndroidManifest()};
-        }
-      }
-
-      config->android_sdk_groups[label] = entry;
-      if (found) {
-        valid = false;
-      }
-      found = true;
     }
   }
 
+  if (entry.min_sdk_version == -1) {
+    diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
+    valid = false;
+  }
+
+  // TODO: Fill in the manifest details when they are finalised.
+  for (auto node : root_element->GetChildElements()) {
+    if (node->name == "manifest") {
+      if (entry.manifest) {
+        diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+        continue;
+      }
+      entry.manifest = {AndroidManifest()};
+    }
+  }
+
+  config->android_sdks[entry.label] = entry;
   return valid;
 };
 
@@ -691,7 +728,7 @@
     return false;
   }
 
-  auto& group = config->gl_texture_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
   bool valid = true;
 
   GlTexture result;
@@ -734,7 +771,7 @@
     return false;
   }
 
-  auto& group = config->device_feature_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
   bool valid = true;
 
   for (auto* child : root_element->GetChildElements()) {
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index ca58910..7f1d445 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -71,7 +71,8 @@
 };
 
 struct AndroidSdk {
-  Maybe<int> min_sdk_version;
+  std::string label;
+  int min_sdk_version;  // min_sdk_version is mandatory if splitting by SDK.
   Maybe<int> target_sdk_version;
   Maybe<int> max_sdk_version;
   Maybe<AndroidManifest> manifest;
@@ -113,15 +114,19 @@
   Maybe<AndroidSdk> android_sdk;
   std::vector<DeviceFeature> features;
   std::vector<GlTexture> textures;
+
+  inline int GetMinSdk(int default_value = -1) const {
+    if (!android_sdk) {
+      return default_value;
+    }
+    return android_sdk.value().min_sdk_version;
+  }
 };
 
 }  // namespace configuration
 
 // Forward declaration of classes used in the API.
 struct IDiagnostics;
-namespace xml {
-class Element;
-}
 
 /**
  * XML configuration file parser for the split and optimize commands.
@@ -133,8 +138,8 @@
   static Maybe<ConfigurationParser> ForPath(const std::string& path);
 
   /** Returns a ConfigurationParser for the configuration in the provided file contents. */
-  static ConfigurationParser ForContents(const std::string& contents) {
-    ConfigurationParser parser{contents};
+  static ConfigurationParser ForContents(const std::string& contents, const std::string& path) {
+    ConfigurationParser parser{contents, path};
     return parser;
   }
 
@@ -156,7 +161,7 @@
    * diagnostics context. The default diagnostics context can be overridden with a call to
    * WithDiagnostics(IDiagnostics *).
    */
-  explicit ConfigurationParser(std::string contents);
+  ConfigurationParser(std::string contents, const std::string& config_path);
 
   /** Returns the current diagnostics context to any subclasses. */
   IDiagnostics* diagnostics() {
@@ -166,6 +171,8 @@
  private:
   /** The contents of the configuration file to parse. */
   const std::string contents_;
+  /** Path to the input configuration. */
+  const std::string config_path_;
   /** The diagnostics context to send messages to. */
   IDiagnostics* diag_;
 };
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 7657ebd..a583057 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -17,35 +17,105 @@
 #ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
 #define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
 
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <limits>
+
 namespace aapt {
+
+// Forward declaration of classes used in the API.
+namespace xml {
+class Element;
+}
+
 namespace configuration {
 
+template <typename T>
+struct OrderedEntry {
+  size_t order;
+  std::vector<T> entry;
+};
+
 /** A mapping of group labels to group of configuration items. */
 template <class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
+using Group = std::unordered_map<std::string, OrderedEntry<T>>;
 
 /** A mapping of group label to a single configuration item. */
 template <class T>
 using Entry = std::unordered_map<std::string, T>;
 
+/** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */
+template <typename T>
+std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) {
+  OrderedEntry<T>& entry = (*group)[label];
+  // If this is a new entry, set the order.
+  if (entry.order == 0) {
+    entry.order = group->size();
+  }
+  return entry.entry;
+}
+
+/**
+ * A ComparisonChain is a grouping of comparisons to perform when sorting groups that have a well
+ * defined order of precedence. Comparisons are only made if none of the previous comparisons had a
+ * definite result. A comparison has a result if at least one of the items has an entry for that
+ * value and that they are not equal.
+ */
+class ComparisonChain {
+ public:
+  /**
+   * Adds a new comparison of items in a group to the chain. The new comparison is only used if we
+   * have not been able to determine the sort order with the previous comparisons.
+   */
+  template <typename T>
+  ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs,
+                       const Maybe<std::string>& rhs) {
+    return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs));
+  }
+
+  /**
+   * Adds a new comparison to the chain. The new comparison is only used if we have not been able to
+   * determine the sort order with the previous comparisons.
+   */
+  ComparisonChain& Add(int lhs, int rhs) {
+    if (!has_result_) {
+      has_result_ = (lhs != rhs);
+      result_ = (lhs < rhs);
+    }
+    return *this;
+  }
+
+  /** Returns true if the left hand side should come before the right hand side. */
+  bool Compare() {
+    return result_;
+  }
+
+ private:
+  template <typename T>
+  inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) {
+    if (!label) {
+      return std::numeric_limits<size_t>::max();
+    }
+    return groups.at(label.value()).order;
+  }
+
+  bool has_result_ = false;
+  bool result_ = false;
+};
+
 /** Output artifact configuration options. */
 struct ConfiguredArtifact {
   /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
   Maybe<std::string> name;
-  /**
-   * Value to add to the base Android manifest versionCode. If it is not present in the
-   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
-   * a value, artifacts are a 1 based index.
-   */
-  int version;
   /** If present, uses the ABI group with this name. */
   Maybe<std::string> abi_group;
   /** If present, uses the screen density group with this name. */
   Maybe<std::string> screen_density_group;
   /** If present, uses the locale group with this name. */
   Maybe<std::string> locale_group;
-  /** If present, uses the Android SDK group with this name. */
-  Maybe<std::string> android_sdk_group;
+  /** If present, uses the Android SDK with this name. */
+  Maybe<std::string> android_sdk;
   /** If present, uses the device feature group with this name. */
   Maybe<std::string> device_feature_group;
   /** If present, uses the OpenGL texture group with this name. */
@@ -57,31 +127,71 @@
 
   /** Convert an artifact name template into a name string based on configuration contents. */
   Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
-  bool operator<(const ConfiguredArtifact& rhs) const {
-    // TODO(safarmer): Order by play store multi-APK requirements.
-    return version < rhs.version;
-  }
-
-  bool operator==(const ConfiguredArtifact& rhs) const {
-    return version == rhs.version;
-  }
 };
 
 /** AAPT2 XML configuration file binary representation. */
 struct PostProcessingConfiguration {
-  // TODO: Support named artifacts?
   std::vector<ConfiguredArtifact> artifacts;
   Maybe<std::string> artifact_format;
 
   Group<Abi> abi_groups;
   Group<ConfigDescription> screen_density_groups;
   Group<ConfigDescription> locale_groups;
-  Entry<AndroidSdk> android_sdk_groups;
   Group<DeviceFeature> device_feature_groups;
   Group<GlTexture> gl_texture_groups;
+  Entry<AndroidSdk> android_sdks;
+
+  /**
+   * Sorts the configured artifacts based on the ordering of the groups in the configuration file.
+   * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger
+   * versionCode to ensure users get the correct APK when they upgrade their OS.
+   */
+  void SortArtifacts() {
+    std::sort(artifacts.begin(), artifacts.end(), *this);
+  }
+
+  /** Comparator that ensures artifacts are in the preferred order for versionCode rewriting. */
+  bool operator()(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+    // Split dimensions are added in the order of precedence. Items higher in the list result in
+    // higher version codes.
+    return ComparisonChain()
+        // All splits with a minSdkVersion specified must be last to ensure the application will be
+        // updated if a user upgrades the version of Android on their device.
+        .Add(GetMinSdk(lhs), GetMinSdk(rhs))
+        // ABI version is important, especially on x86 phones where they may begin to run in ARM
+        // emulation mode on newer Android versions. This allows us to ensure that the x86 version
+        // is installed on these devices rather than ARM.
+        .Add(abi_groups, lhs.abi_group, rhs.abi_group)
+        // The rest are in arbitrary order based on estimated usage.
+        .Add(screen_density_groups, lhs.screen_density_group, rhs.screen_density_group)
+        .Add(locale_groups, lhs.locale_group, rhs.locale_group)
+        .Add(gl_texture_groups, lhs.gl_texture_group, rhs.gl_texture_group)
+        .Add(device_feature_groups, lhs.device_feature_group, rhs.device_feature_group)
+        .Compare();
+  }
+
+ private:
+  /**
+   * Returns the min_sdk_version from the provided artifact or 0 if none is present. This allows
+   * artifacts that have an Android SDK version to have a higher versionCode than those that do not.
+   */
+  inline int GetMinSdk(const ConfiguredArtifact& artifact) {
+    if (!artifact.android_sdk) {
+      return 0;
+    }
+    const auto& entry = android_sdks.find(artifact.android_sdk.value());
+    if (entry == android_sdks.end()) {
+      return 0;
+    }
+    return entry->second.min_sdk_version;
+  }
 };
 
+/** Parses the provided XML document returning the post processing configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+                                                        const std::string& config_path,
+                                                        IDiagnostics* diag);
+
 namespace handler {
 
 /** Handler for <artifact> tags. */
@@ -104,9 +214,9 @@
 bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
                            xml::Element* element, IDiagnostics* diag);
 
-/** Handler for <android-sdk-group> tags. */
-bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
-                               xml::Element* element, IDiagnostics* diag);
+/** Handler for <android-sdk> tags. */
+bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+                          IDiagnostics* diag);
 
 /** Handler for <gl-texture-group> tags. */
 bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 3f356d7..0329846 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -30,11 +30,33 @@
 
 namespace configuration {
 void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
-  *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+  *os << "SDK: min=" << sdk.min_sdk_version
       << ", target=" << sdk.target_sdk_version.value_or_default(-1)
       << ", max=" << sdk.max_sdk_version.value_or_default(-1);
 }
 
+bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+  return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group &&
+         lhs.screen_density_group == rhs.screen_density_group &&
+         lhs.locale_group == rhs.locale_group && lhs.android_sdk == rhs.android_sdk &&
+         lhs.device_feature_group == rhs.device_feature_group &&
+         lhs.gl_texture_group == rhs.gl_texture_group;
+}
+
+std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) {
+  PrintTo(value, &out);
+  return out;
+}
+
+void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) {
+  *os << "\n{"
+      << "\n  name: " << artifact.name << "\n  sdk: " << artifact.android_sdk
+      << "\n  abi: " << artifact.abi_group << "\n  density: " << artifact.screen_density_group
+      << "\n  locale: " << artifact.locale_group
+      << "\n  features: " << artifact.device_feature_group
+      << "\n  textures: " << artifact.gl_texture_group << "\n}\n";
+}
+
 namespace handler {
 
 namespace {
@@ -44,6 +66,7 @@
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::ExtractConfiguration;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Locale;
 using ::aapt::configuration::PostProcessingConfiguration;
@@ -52,11 +75,13 @@
 using ::android::ResTable_config;
 using ::android::base::StringPrintf;
 using ::testing::ElementsAre;
+using ::testing::Eq;
 using ::testing::SizeIs;
+using ::testing::StrEq;
 
 constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
 <post-process xmlns="http://schemas.android.com/tools/aapt">
-  <groups>
+  <abi-groups>
     <abi-group label="arm">
       <abi>armeabi-v7a</abi>
       <abi>arm64-v8a</abi>
@@ -65,6 +90,8 @@
       <abi>x86</abi>
       <abi>mips</abi>
     </abi-group>
+  </abi-groups>
+  <screen-density-groups>
     <screen-density-group label="large">
       <screen-density>xhdpi</screen-density>
       <screen-density>xxhdpi</screen-density>
@@ -78,6 +105,8 @@
       <screen-density>xxhdpi</screen-density>
       <screen-density>xxxhdpi</screen-density>
     </screen-density-group>
+  </screen-density-groups>
+  <locale-groups>
     <locale-group label="europe">
       <locale>en</locale>
       <locale>es</locale>
@@ -89,25 +118,30 @@
       <locale>es-rMX</locale>
       <locale>fr-rCA</locale>
     </locale-group>
-    <android-sdk-group label="v19">
-      <android-sdk
-          minSdkVersion="19"
-          targetSdkVersion="24"
-          maxSdkVersion="25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>
+  </locale-groups>
+  <android-sdks>
+    <android-sdk
+    	  label="v19"
+        minSdkVersion="19"
+        targetSdkVersion="24"
+        maxSdkVersion="25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>
+  </android-sdks>
+  <gl-texture-groups>
     <gl-texture-group label="dxt1">
       <gl-texture name="GL_EXT_texture_compression_dxt1">
         <texture-path>assets/dxt1/*</texture-path>
       </gl-texture>
     </gl-texture-group>
+  </gl-texture-groups>
+  <device-feature-groups>
     <device-feature-group label="low-latency">
       <supports-feature>android.hardware.audio.low_latency</supports-feature>
     </device-feature-group>
-  </groups>
+  </device-feature-groups>
   <artifacts>
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -117,7 +151,7 @@
         abi-group="arm"
         screen-density-group="large"
         locale-group="europe"
-        android-sdk-group="v19"
+        android-sdk="v19"
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>
     <artifact
@@ -125,7 +159,7 @@
         abi-group="other"
         screen-density-group="alldpi"
         locale-group="north-america"
-        android-sdk-group="v19"
+        android-sdk="v19"
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>
   </artifacts>
@@ -134,7 +168,8 @@
 
 class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
  public:
-  ConfigurationParserTest() : ConfigurationParser("") {}
+  ConfigurationParserTest() : ConfigurationParser("", "config.xml") {
+  }
 
  protected:
   StdErrDiagnostics diag_;
@@ -145,8 +180,31 @@
   EXPECT_FALSE(result);
 }
 
+TEST_F(ConfigurationParserTest, ExtractConfiguration) {
+  Maybe<PostProcessingConfiguration> maybe_config =
+      ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+
+  PostProcessingConfiguration config = maybe_config.value();
+
+  auto& arm = config.abi_groups["arm"];
+  auto& other = config.abi_groups["other"];
+  EXPECT_EQ(arm.order, 1ul);
+  EXPECT_EQ(other.order, 2ul);
+
+  auto& large = config.screen_density_groups["large"];
+  auto& alldpi = config.screen_density_groups["alldpi"];
+  EXPECT_EQ(large.order, 1ul);
+  EXPECT_EQ(alldpi.order, 2ul);
+
+  auto& north_america = config.locale_groups["north-america"];
+  auto& europe = config.locale_groups["europe"];
+  // Checked in reverse to make sure access order does not matter.
+  EXPECT_EQ(north_america.order, 2ul);
+  EXPECT_EQ(europe.order, 1ul);
+}
+
 TEST_F(ConfigurationParserTest, ValidateFile) {
-  auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+  auto parser = ConfigurationParser::ForContents(kValidConfig, "conf.xml").WithDiagnostics(&diag_);
   auto result = parser.Parse("test.apk");
   ASSERT_TRUE(result);
   const std::vector<OutputArtifact>& value = result.value();
@@ -154,6 +212,7 @@
 
   const OutputArtifact& a1 = value[0];
   EXPECT_EQ(a1.name, "art1.apk");
+  EXPECT_EQ(a1.version, 1);
   EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
   EXPECT_THAT(a1.screen_densities,
               ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
@@ -161,12 +220,15 @@
                           test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
   EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
                                       test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
-  EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+  ASSERT_TRUE(a1.android_sdk);
+  ASSERT_TRUE(a1.android_sdk.value().min_sdk_version);
+  EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l);
   EXPECT_THAT(a1.textures, SizeIs(1ul));
   EXPECT_THAT(a1.features, SizeIs(1ul));
 
   const OutputArtifact& a2 = value[1];
   EXPECT_EQ(a2.name, "art2.apk");
+  EXPECT_EQ(a2.version, 2);
   EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
   EXPECT_THAT(a2.screen_densities,
               ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
@@ -178,124 +240,138 @@
   EXPECT_THAT(a2.locales,
               ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
                           test::ParseConfigOrDie("fr-rCA")));
-  EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+  ASSERT_TRUE(a2.android_sdk);
+  ASSERT_TRUE(a2.android_sdk.value().min_sdk_version);
+  EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l);
   EXPECT_THAT(a2.textures, SizeIs(1ul));
   EXPECT_THAT(a2.features, SizeIs(1ul));
 }
 
+TEST_F(ConfigurationParserTest, ConfiguredArtifactOrdering) {
+  // Create a base builder with the configuration groups but no artifacts to allow it to be copied.
+  test::PostProcessingConfigurationBuilder base_builder = test::PostProcessingConfigurationBuilder()
+                                                              .AddAbiGroup("arm")
+                                                              .AddAbiGroup("arm64")
+                                                              .AddAndroidSdk("v23", 23)
+                                                              .AddAndroidSdk("v19", 19);
+
+  {
+    // Test version ordering.
+    ConfiguredArtifact v23;
+    v23.android_sdk = {"v23"};
+    ConfiguredArtifact v19;
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v23).AddArtifact(v19).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(v19));
+    EXPECT_THAT(config.artifacts[1], Eq(v23));
+  }
+
+  {
+    // Test ABI ordering.
+    ConfiguredArtifact arm;
+    arm.android_sdk = {"v19"};
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact arm64;
+    arm64.android_sdk = {"v19"};
+    arm64.abi_group = {"arm64"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(arm64));
+  }
+
+  {
+    // Test Android SDK has precedence over ABI.
+    ConfiguredArtifact arm;
+    arm.android_sdk = {"v23"};
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact arm64;
+    arm64.android_sdk = {"v19"};
+    arm64.abi_group = {"arm64"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm64));
+    EXPECT_THAT(config.artifacts[1], Eq(arm));
+  }
+
+  {
+    // Test version is better than ABI.
+    ConfiguredArtifact arm;
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact v19;
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(v19));
+  }
+
+  {
+    // Test version is sorted higher than no version.
+    ConfiguredArtifact arm;
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact v19;
+    v19.abi_group = {"arm"};
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(v19));
+  }
+}
+
 TEST_F(ConfigurationParserTest, InvalidNamespace) {
   constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
-  <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+    <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
 
-  auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
+  auto result = ConfigurationParser::ForContents(invalid_ns, "config.xml").Parse("test.apk");
   ASSERT_FALSE(result);
 }
 
 TEST_F(ConfigurationParserTest, ArtifactAction) {
   PostProcessingConfiguration config;
-  {
-    const auto doc = test::BuildXmlDom(R"xml(
+  const auto doc = test::BuildXmlDom(R"xml(
       <artifact
           abi-group="arm"
           screen-density-group="large"
           locale-group="europe"
-          android-sdk-group="v19"
+          android-sdk="v19"
           gl-texture-group="dxt1"
           device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
+  ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
 
-    EXPECT_THAT(config.artifacts, SizeIs(1ul));
+  EXPECT_THAT(config.artifacts, SizeIs(1ul));
 
-    auto& artifact = config.artifacts.back();
-    EXPECT_FALSE(artifact.name);  // TODO: make this fail.
-    EXPECT_EQ(1, artifact.version);
-    EXPECT_EQ("arm", artifact.abi_group.value());
-    EXPECT_EQ("large", artifact.screen_density_group.value());
-    EXPECT_EQ("europe", artifact.locale_group.value());
-    EXPECT_EQ("v19", artifact.android_sdk_group.value());
-    EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
-    EXPECT_EQ("low-latency", artifact.device_feature_group.value());
-  }
-
-  {
-    // Perform a second action to ensure we get 2 artifacts.
-    const auto doc = test::BuildXmlDom(R"xml(
-      <artifact
-          abi-group="other"
-          screen-density-group="large"
-          locale-group="europe"
-          android-sdk-group="v19"
-          gl-texture-group="dxt1"
-          device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(2ul));
-    EXPECT_EQ(2, config.artifacts.back().version);
-  }
-
-  {
-    // Perform a third action with a set version code.
-    const auto doc = test::BuildXmlDom(R"xml(
-    <artifact
-        version="5"
-        abi-group="other"
-        screen-density-group="large"
-        locale-group="europe"
-        android-sdk-group="v19"
-        gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(3ul));
-    EXPECT_EQ(5, config.artifacts.back().version);
-  }
-
-  {
-    // Perform a fourth action to ensure the version code still increments.
-    const auto doc = test::BuildXmlDom(R"xml(
-    <artifact
-        abi-group="other"
-        screen-density-group="large"
-        locale-group="europe"
-        android-sdk-group="v19"
-        gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(4ul));
-    EXPECT_EQ(6, config.artifacts.back().version);
-  }
-}
-
-TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
-  static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
-      <pst-process xmlns="http://schemas.android.com/tools/aapt">>
-        <artifacts>
-          <artifact-format>
-            ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
-          </artifact-format>
-          <artifact
-              name="art1"
-              abi-group="arm"
-              screen-density-group="large"
-              locale-group="europe"
-              android-sdk-group="v19"
-              gl-texture-group="dxt1"
-              device-feature-group="low-latency"/>
-          <artifact
-              name="art2"
-              version = "1"
-              abi-group="other"
-              screen-density-group="alldpi"
-              locale-group="north-america"
-              android-sdk-group="v19"
-              gl-texture-group="dxt1"
-              device-feature-group="low-latency"/>
-        </artifacts>
-      </post-process>)xml";
-  auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
-  ASSERT_FALSE(result);
+  auto& artifact = config.artifacts.back();
+  EXPECT_FALSE(artifact.name);  // TODO: make this fail.
+  EXPECT_EQ("arm", artifact.abi_group.value());
+  EXPECT_EQ("large", artifact.screen_density_group.value());
+  EXPECT_EQ("europe", artifact.locale_group.value());
+  EXPECT_EQ("v19", artifact.android_sdk.value());
+  EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+  EXPECT_EQ("low-latency", artifact.device_feature_group.value());
 }
 
 TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
@@ -334,10 +410,36 @@
   EXPECT_THAT(config.abi_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.abi_groups.count("arm"));
 
-  auto& out = config.abi_groups["arm"];
+  auto& out = config.abi_groups["arm"].entry;
   ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
 }
 
+TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) {
+  static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  EXPECT_THAT(config.abi_groups, SizeIs(1ul));
+  ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a"));
+
+  auto& out = config.abi_groups["arm64-v8a"].entry;
+  ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) {
+  static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
   static constexpr const char* xml = R"xml(
     <screen-density-group label="large">
@@ -364,10 +466,39 @@
   ConfigDescription xxxhdpi;
   xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
 
-  auto& out = config.screen_density_groups["large"];
+  auto& out = config.screen_density_groups["large"].entry;
   ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
 }
 
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) {
+  static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
+  ASSERT_EQ(1u, config.screen_density_groups.count("xhdpi"));
+
+  ConfigDescription xhdpi;
+  xhdpi.density = ResTable_config::DENSITY_XHIGH;
+
+  auto& out = config.screen_density_groups["xhdpi"].entry;
+  ASSERT_THAT(out, ElementsAre(xhdpi));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) {
+  static constexpr const char* xml = R"xml(<screen-density-group label="really-big-screen"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, LocaleGroupAction) {
   static constexpr const char* xml = R"xml(
     <locale-group label="europe">
@@ -386,7 +517,7 @@
   ASSERT_EQ(1ul, config.locale_groups.size());
   ASSERT_EQ(1u, config.locale_groups.count("europe"));
 
-  const auto& out = config.locale_groups["europe"];
+  const auto& out = config.locale_groups["europe"].entry;
 
   ConfigDescription en = test::ParseConfigOrDie("en");
   ConfigDescription es = test::ParseConfigOrDie("es");
@@ -396,29 +527,56 @@
   ASSERT_THAT(out, ElementsAre(en, es, fr, de));
 }
 
+TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) {
+  static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  ASSERT_EQ(1ul, config.locale_groups.size());
+  ASSERT_EQ(1u, config.locale_groups.count("en"));
+
+  const auto& out = config.locale_groups["en"].entry;
+
+  ConfigDescription en = test::ParseConfigOrDie("en");
+
+  ASSERT_THAT(out, ElementsAre(en));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) {
+  static constexpr const char* xml = R"xml(<locale-group label="arm64"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="v19">
-      <android-sdk
+      <android-sdk label="v19"
           minSdkVersion="19"
           targetSdkVersion="24"
           maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
-      </android-sdk>
-    </android-sdk-group>)xml";
+      </android-sdk>)xml";
 
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  ASSERT_EQ(1ul, config.android_sdk_groups.size());
-  ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+  ASSERT_EQ(1ul, config.android_sdks.size());
+  ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-  auto& out = config.android_sdk_groups["v19"];
+  auto& out = config.android_sdks["v19"];
 
   AndroidSdk sdk;
   sdk.min_sdk_version = 19;
@@ -431,98 +589,86 @@
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) {
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk minSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml = "<android-sdk label='v19' minSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
-    EXPECT_EQ(19, out.min_sdk_version.value());
+    auto& out = config.android_sdks["v19"];
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.max_sdk_version);
     EXPECT_FALSE(out.target_sdk_version);
   }
 
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk maxSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml =
+        "<android-sdk label='v19' minSdkVersion='19' maxSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
+    auto& out = config.android_sdks["v19"];
     EXPECT_EQ(19, out.max_sdk_version.value());
-    EXPECT_FALSE(out.min_sdk_version);
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.target_sdk_version);
   }
 
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk targetSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml =
+        "<android-sdk label='v19' minSdkVersion='19' targetSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
+    auto& out = config.android_sdks["v19"];
     EXPECT_EQ(19, out.target_sdk_version.value());
-    EXPECT_FALSE(out.min_sdk_version);
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.max_sdk_version);
   }
 }
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="v19">
-      <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>)xml";
+    <android-sdk
+        label="v19"
+        minSdkVersion="v19"
+        targetSdkVersion="v24"
+        maxSdkVersion="v25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>)xml";
 
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_FALSE(ok);
 }
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="P">
       <android-sdk
+          label="P"
           minSdkVersion="25"
           targetSdkVersion="%s"
           maxSdkVersion="%s">
-      </android-sdk>
-    </android-sdk-group>)xml";
+      </android-sdk>)xml";
 
   const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion();
   const char* codename = dev_sdk.first.data();
@@ -531,13 +677,13 @@
   auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  ASSERT_EQ(1ul, config.android_sdk_groups.size());
-  ASSERT_EQ(1u, config.android_sdk_groups.count("P"));
+  ASSERT_EQ(1ul, config.android_sdks.size());
+  ASSERT_EQ(1u, config.android_sdks.count("P"));
 
-  auto& out = config.android_sdk_groups["P"];
+  auto& out = config.android_sdks["P"];
 
   AndroidSdk sdk;
   sdk.min_sdk_version = 25;
@@ -567,7 +713,7 @@
   EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
 
-  auto& out = config.gl_texture_groups["dxt1"];
+  auto& out = config.gl_texture_groups["dxt1"].entry;
 
   GlTexture texture{
       std::string("GL_EXT_texture_compression_dxt1"),
@@ -596,7 +742,7 @@
   EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
 
-  auto& out = config.device_feature_groups["low-latency"];
+  auto& out = config.device_feature_groups["low-latency"].entry;
 
   DeviceFeature low_latency = "android.hardware.audio.low_latency";
   DeviceFeature pro = "android.hardware.audio.pro";
@@ -650,7 +796,7 @@
   artifact.device_feature_group = {"df1"};
   artifact.gl_texture_group = {"glx1"};
   artifact.locale_group = {"en-AU"};
-  artifact.android_sdk_group = {"v26"};
+  artifact.android_sdk = {"v26"};
 
   {
     auto result = artifact.ToArtifactName(
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 134153a..fb2f49b 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -8,22 +8,52 @@
   <xsd:element name="post-process">
     <xsd:complexType>
       <xsd:sequence>
-        <xsd:element name="groups" type="groups"/>
         <xsd:element name="artifacts" type="artifacts"/>
+        <xsd:element name="android-sdks" type="android-sdks"/>
+        <xsd:element name="abi-groups" type="abi-groups"/>
+        <xsd:element name="screen-density-groups" type="screen-density-groups"/>
+        <xsd:element name="locale-groups" type="locale-groups"/>
+        <xsd:element name="gl-texture-groups" type="gl-texture-groups"/>
+        <xsd:element name="device-feature-groups" type="device-feature-groups"/>
       </xsd:sequence>
     </xsd:complexType>
   </xsd:element>
 
-  <xsd:complexType name="groups">
+  <xsd:complexType name="android-sdks">
+    <xsd:sequence>
+      <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="abi-groups">
     <xsd:sequence>
       <xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="screen-density-groups">
+    <xsd:sequence>
       <xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="locale-groups">
+    <xsd:sequence>
       <xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
-      <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="gl-texture-groups">
+    <xsd:sequence>
       <xsd:element
           name="gl-texture-group"
           type="gl-texture-group"
           maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="device-feature-groups">
+    <xsd:sequence>
       <xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
     </xsd:sequence>
   </xsd:complexType>
@@ -38,8 +68,6 @@
 
   <!-- Groups output artifacts together by dimension labels. -->
   <xsd:complexType name="artifact">
-    <xsd:attribute name="name" type="xsd:string"/>
-    <xsd:attribute name="version" type="xsd:integer"/>
     <xsd:attribute name="abi-group" type="xsd:string"/>
     <xsd:attribute name="android-sdk-group" type="xsd:string"/>
     <xsd:attribute name="device-feature-group" type="xsd:string"/>
@@ -52,7 +80,7 @@
     <xsd:sequence>
       <xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="gl-texture">
@@ -66,14 +94,14 @@
     <xsd:sequence>
       <xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="abi-group">
     <xsd:sequence>
       <xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:simpleType name="abi-name">
@@ -93,7 +121,7 @@
     <xsd:sequence>
       <xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:simpleType name="screen-density">
@@ -108,20 +136,14 @@
     </xsd:restriction>
   </xsd:simpleType>
 
-  <xsd:complexType name="android-sdk-group">
-    <xsd:sequence>
-      <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
-  </xsd:complexType>
-
   <xsd:complexType name="android-sdk">
     <!-- TODO(safarmer): Add permissions to add/remove. -->
     <!-- TODO(safarmer): Add option for uncompressed native libs. -->
     <xsd:sequence>
       <xsd:element name="manifest" type="manifest"/>
     </xsd:sequence>
-    <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+    <xsd:attribute name="label" type="xsd:string" use="required"/>
+    <xsd:attribute name="minSdkVersion" type="xsd:integer" use="required"/>
     <xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
     <xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
   </xsd:complexType>
@@ -135,7 +157,7 @@
     <xsd:sequence>
       <xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="locale">
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
index ce31e61..d8aba09 100644
--- a/tools/aapt2/configuration/example/config.xml
+++ b/tools/aapt2/configuration/example/config.xml
@@ -1,70 +1,5 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <post-process xmlns="http://schemas.android.com/tools/aapt">
-  <groups>
-    <abi-group label="arm">
-      <abi>armeabi-v7a</abi>
-      <abi>arm64-v8a</abi>
-    </abi-group>
-
-    <abi-group label="other">
-      <abi>x86</abi>
-      <abi>mips</abi>
-    </abi-group>
-
-    <screen-density-group label="large">
-      <screen-density>xhdpi</screen-density>
-      <screen-density>xxhdpi</screen-density>
-      <screen-density>xxxhdpi</screen-density>
-    </screen-density-group>
-
-    <screen-density-group label="alldpi">
-      <screen-density>ldpi</screen-density>
-      <screen-density>mdpi</screen-density>
-      <screen-density>hdpi</screen-density>
-      <screen-density>xhdpi</screen-density>
-      <screen-density>xxhdpi</screen-density>
-      <screen-density>xxxhdpi</screen-density>
-    </screen-density-group>
-
-    <locale-group label="europe">
-      <locale lang="en"/>
-      <locale lang="es"/>
-      <locale lang="fr"/>
-      <locale lang="de" compressed="true"/>
-    </locale-group>
-
-    <locale-group label="north-america">
-      <locale lang="en"/>
-      <locale lang="es" region="MX"/>
-      <locale lang="fr" region="CA" compressed="true"/>
-    </locale-group>
-
-    <locale-group label="all">
-      <locale compressed="true"/>
-    </locale-group>
-
-    <android-sdk-group label="19">
-      <android-sdk
-          minSdkVersion="19"
-          targetSdkVersion="24"
-          maxSdkVersion="25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>
-
-    <gl-texture-group label="dxt1">
-      <gl-texture name="GL_EXT_texture_compression_dxt1">
-        <texture-path>assets/dxt1/*</texture-path>
-      </gl-texture>
-    </gl-texture-group>
-
-    <device-feature-group label="low-latency">
-      <supports-feature>android.hardware.audio.low_latency</supports-feature>
-    </device-feature-group>
-  </groups>
-
   <artifacts>
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -87,4 +22,79 @@
         device-feature-group="low-latency"/>
 
   </artifacts>
+
+  <android-sdks>
+    <android-sdk
+        label="19"
+        minSdkVersion="19"
+        targetSdkVersion="24"
+        maxSdkVersion="25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>
+  </android-sdks>
+
+  <abi-groups>
+    <abi-group label="arm">
+      <abi>armeabi-v7a</abi>
+      <abi>arm64-v8a</abi>
+    </abi-group>
+
+    <abi-group label="other">
+      <abi>x86</abi>
+      <abi>mips</abi>
+    </abi-group>
+  </abi-groups>
+
+  <screen-density-groups>
+    <screen-density-group label="large">
+      <screen-density>xhdpi</screen-density>
+      <screen-density>xxhdpi</screen-density>
+      <screen-density>xxxhdpi</screen-density>
+    </screen-density-group>
+
+    <screen-density-group label="alldpi">
+      <screen-density>ldpi</screen-density>
+      <screen-density>mdpi</screen-density>
+      <screen-density>hdpi</screen-density>
+      <screen-density>xhdpi</screen-density>
+      <screen-density>xxhdpi</screen-density>
+      <screen-density>xxxhdpi</screen-density>
+    </screen-density-group>
+  </screen-density-groups>
+
+  <locale-groups>
+    <locale-group label="europe">
+      <locale lang="en"/>
+      <locale lang="es"/>
+      <locale lang="fr"/>
+      <locale lang="de" compressed="true"/>
+    </locale-group>
+
+    <locale-group label="north-america">
+      <locale lang="en"/>
+      <locale lang="es" region="MX"/>
+      <locale lang="fr" region="CA" compressed="true"/>
+    </locale-group>
+
+    <locale-group label="all">
+      <locale compressed="true"/>
+    </locale-group>
+  </locale-groups>
+
+  <gl-texture-groups>
+    <gl-texture-group label="dxt1">
+      <gl-texture name="GL_EXT_texture_compression_dxt1">
+        <texture-path>assets/dxt1/*</texture-path>
+      </gl-texture>
+    </gl-texture-group>
+  </gl-texture-groups>
+
+  <device-feature-groups>
+    <device-feature-group label="low-latency">
+      <supports-feature>android.hardware.audio.low_latency</supports-feature>
+    </device-feature-group>
+  </device-feature-groups>
+
 </post-process>
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 5078678..8d079ff 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -223,7 +223,7 @@
         break;
 
       case android::RES_TABLE_TYPE_SPEC_TYPE:
-        if (!ParseTypeSpec(parser.chunk())) {
+        if (!ParseTypeSpec(package, parser.chunk())) {
           return false;
         }
         break;
@@ -260,7 +260,8 @@
   return true;
 }
 
-bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
+                                         const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
     diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
@@ -276,6 +277,34 @@
     diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
     return false;
   }
+
+  // The data portion of this chunk contains entry_count 32bit entries,
+  // each one representing a set of flags.
+  const size_t entry_count = dtohl(type_spec->entryCount);
+
+  // There can only be 2^16 entries in a type, because that is the ID
+  // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
+  if (entry_count > std::numeric_limits<uint16_t>::max()) {
+    diag_->Error(DiagMessage(source_)
+                 << "ResTable_typeSpec has too many entries (" << entry_count << ")");
+    return false;
+  }
+
+  const size_t data_size = util::DeviceToHost32(type_spec->header.size) -
+                           util::DeviceToHost16(type_spec->header.headerSize);
+  if (entry_count * sizeof(uint32_t) > data_size) {
+    diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries.");
+    return false;
+  }
+
+  // Record the type_spec_flags for later. We don't know resource names yet, and we need those
+  // to mark resources as overlayable.
+  const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>(
+      reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize));
+  for (size_t i = 0; i < entry_count; i++) {
+    ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i));
+    entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]);
+  }
   return true;
 }
 
@@ -346,18 +375,34 @@
       return false;
     }
 
-    if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
-                                         diag_)) {
+    if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value),
+                                          diag_)) {
       return false;
     }
 
-    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-      Symbol symbol;
-      symbol.state = SymbolState::kPublic;
-      symbol.source = source_.WithLine(0);
-      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
-        return false;
+    const uint32_t type_spec_flags = entry_type_spec_flags_[res_id];
+    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 ||
+        (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) {
+      if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+        Visibility visibility;
+        visibility.level = Visibility::Level::kPublic;
+        visibility.source = source_.WithLine(0);
+        if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
+          return false;
+        }
       }
+
+      if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
+        Overlayable overlayable;
+        overlayable.source = source_.WithLine(0);
+        if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
+          return false;
+        }
+      }
+
+      // Erase the ID from the map once processed, so that we don't mark the same symbol more than
+      // once.
+      entry_type_spec_flags_.erase(res_id);
     }
 
     // Add this resource name->id mapping to the index so
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 052f806..a1f9f83 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -50,7 +50,7 @@
 
   bool ParseTable(const android::ResChunk_header* chunk);
   bool ParsePackage(const android::ResChunk_header* chunk);
-  bool ParseTypeSpec(const android::ResChunk_header* chunk);
+  bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseLibrary(const android::ResChunk_header* chunk);
 
@@ -105,6 +105,9 @@
   // A mapping of resource ID to resource name. When we finish parsing
   // we use this to convert all resource IDs to symbolic references.
   std::map<ResourceId, ResourceName> id_index_;
+
+  // A mapping of resource ID to type spec flags.
+  std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index a3034df..24a4112 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -283,7 +283,7 @@
 
     T* result = buffer->NextBlock<T>();
     ResTable_entry* out_entry = (ResTable_entry*)result;
-    if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+    if (entry->entry->visibility.level == Visibility::Level::kPublic) {
       out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
     }
 
@@ -443,10 +443,15 @@
 
       // Populate the config masks for this entry.
 
-      if (entry->symbol_status.state == SymbolState::kPublic) {
+      if (entry->visibility.level == Visibility::Level::kPublic) {
         config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
       }
 
+      if (entry->overlayable) {
+        config_masks[entry->id.value()] |=
+            util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
+      }
+
       const size_t config_count = entry->values.size();
       for (size_t i = 0; i < config_count; i++) {
         const ConfigDescription& config = entry->values[i]->config;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f0b80d2..51ccdc7 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -26,6 +26,7 @@
 
 using namespace android;
 
+using ::testing::Gt;
 using ::testing::IsNull;
 using ::testing::NotNull;
 
@@ -250,15 +251,15 @@
     const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
     const auto value =
         util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
-    CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
-                             std::unique_ptr<Value>(value->Clone(nullptr)),
-                             context->GetDiagnostics()));
+    CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
+                                   std::unique_ptr<Value>(value->Clone(nullptr)),
+                                   context->GetDiagnostics()));
 
     // Every few entries, write out a sparse_config value. This will give us the desired load.
     if (i % stride == 0) {
-      CHECK(table->AddResource(name, resid, sparse_config, "",
-                               std::unique_ptr<Value>(value->Clone(nullptr)),
-                               context->GetDiagnostics()));
+      CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
+                                     std::unique_ptr<Value>(value->Clone(nullptr)),
+                                     context->GetDiagnostics()));
     }
   }
   return table;
@@ -568,4 +569,25 @@
                      ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
 }
 
+TEST_F(TableFlattenerTest, FlattenOverlayable) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
+          .Build();
+
+  ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
+                                    Overlayable{}, test::GetDiagnostics()));
+
+  ResTable res_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+  const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable");
+  uint32_t spec_flags = 0u;
+  ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr,
+                                          0u, nullptr, 0u, &spec_flags),
+              Gt(0u));
+  EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE);
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 0f0bce8..3d6975d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -358,16 +358,16 @@
   out_source->line = static_cast<size_t>(pb_source.position().line_number());
 }
 
-static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) {
-  switch (pb_visibility) {
-    case pb::SymbolStatus_Visibility_PRIVATE:
-      return SymbolState::kPrivate;
-    case pb::SymbolStatus_Visibility_PUBLIC:
-      return SymbolState::kPublic;
+static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level& pb_level) {
+  switch (pb_level) {
+    case pb::Visibility::PRIVATE:
+      return Visibility::Level::kPrivate;
+    case pb::Visibility::PUBLIC:
+      return Visibility::Level::kPublic;
     default:
       break;
   }
-  return SymbolState::kUndefined;
+  return Visibility::Level::kUndefined;
 }
 
 static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
@@ -402,28 +402,48 @@
       }
 
       // Deserialize the symbol status (public/private with source and comments).
-      if (pb_entry.has_symbol_status()) {
-        const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
-        if (pb_status.has_source()) {
-          DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source);
+      if (pb_entry.has_visibility()) {
+        const pb::Visibility& pb_visibility = pb_entry.visibility();
+        if (pb_visibility.has_source()) {
+          DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source);
         }
+        entry->visibility.comment = pb_visibility.comment();
 
-        entry->symbol_status.comment = pb_status.comment();
-        entry->symbol_status.allow_new = pb_status.allow_new();
-
-        const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
-        entry->symbol_status.state = visibility;
-        if (visibility == SymbolState::kPublic) {
+        const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level());
+        entry->visibility.level = level;
+        if (level == Visibility::Level::kPublic) {
           // Propagate the public visibility up to the Type.
-          type->symbol_status.state = SymbolState::kPublic;
-        } else if (visibility == SymbolState::kPrivate) {
+          type->visibility_level = Visibility::Level::kPublic;
+        } else if (level == Visibility::Level::kPrivate) {
           // Only propagate if no previous state was assigned.
-          if (type->symbol_status.state == SymbolState::kUndefined) {
-            type->symbol_status.state = SymbolState::kPrivate;
+          if (type->visibility_level == Visibility::Level::kUndefined) {
+            type->visibility_level = Visibility::Level::kPrivate;
           }
         }
       }
 
+      if (pb_entry.has_allow_new()) {
+        const pb::AllowNew& pb_allow_new = pb_entry.allow_new();
+
+        AllowNew allow_new;
+        if (pb_allow_new.has_source()) {
+          DeserializeSourceFromPb(pb_allow_new.source(), src_pool, &allow_new.source);
+        }
+        allow_new.comment = pb_allow_new.comment();
+        entry->allow_new = std::move(allow_new);
+      }
+
+      if (pb_entry.has_overlayable()) {
+        const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+
+        Overlayable overlayable;
+        if (pb_overlayable.has_source()) {
+          DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
+        }
+        overlayable.comment = pb_overlayable.comment();
+        entry->overlayable = std::move(overlayable);
+      }
+
       ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
                        pb_entry.entry_id().id());
       if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 97ce01a..78f1281 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -43,16 +43,16 @@
   }
 }
 
-static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+static pb::Visibility::Level SerializeVisibilityToPb(Visibility::Level state) {
   switch (state) {
-    case SymbolState::kPrivate:
-      return pb::SymbolStatus_Visibility_PRIVATE;
-    case SymbolState::kPublic:
-      return pb::SymbolStatus_Visibility_PUBLIC;
+    case Visibility::Level::kPrivate:
+      return pb::Visibility::PRIVATE;
+    case Visibility::Level::kPublic:
+      return pb::Visibility::PUBLIC;
     default:
       break;
   }
-  return pb::SymbolStatus_Visibility_UNKNOWN;
+  return pb::Visibility::UNKNOWN;
 }
 
 void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) {
@@ -293,12 +293,26 @@
         }
         pb_entry->set_name(entry->name);
 
-        // Write the SymbolStatus struct.
-        pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
-        pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
-        SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
-        pb_status->set_comment(entry->symbol_status.comment);
-        pb_status->set_allow_new(entry->symbol_status.allow_new);
+        // Write the Visibility struct.
+        pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
+        pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+        SerializeSourceToPb(entry->visibility.source, &source_pool,
+                            pb_visibility->mutable_source());
+        pb_visibility->set_comment(entry->visibility.comment);
+
+        if (entry->allow_new) {
+          pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
+          SerializeSourceToPb(entry->allow_new.value().source, &source_pool,
+                              pb_allow_new->mutable_source());
+          pb_allow_new->set_comment(entry->allow_new.value().comment);
+        }
+
+        if (entry->overlayable) {
+          pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+          SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
+                              pb_overlayable->mutable_source());
+          pb_overlayable->set_comment(entry->overlayable.value().comment);
+        }
 
         for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
           pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 9649a4d..d7f83fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -44,14 +44,15 @@
           .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
           .AddString("com.app.a:string/text", {}, "hi")
           .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
-          .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
+          .SetSymbolState("com.app.a:bool/foo", {}, Visibility::Level::kUndefined,
+                          true /*allow_new*/)
           .Build();
 
-  Symbol public_symbol;
-  public_symbol.state = SymbolState::kPublic;
-  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
-                                    ResourceId(0x7f020000), public_symbol,
-                                    context->GetDiagnostics()));
+  Visibility public_symbol;
+  public_symbol.level = Visibility::Level::kPublic;
+  ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"),
+                                         public_symbol, ResourceId(0x7f020000),
+                                         context->GetDiagnostics()));
 
   Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
   ASSERT_THAT(id, NotNull());
@@ -89,6 +90,10 @@
       test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
       util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
 
+  // Make an overlayable resource.
+  ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+                                    Overlayable{}, test::GetDiagnostics()));
+
   pb::ResourceTable pb_table;
   SerializeTableToPb(*table, &pb_table);
 
@@ -110,13 +115,13 @@
       new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
   ASSERT_TRUE(result);
 
-  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().type->visibility_level, Eq(Visibility::Level::kPublic));
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
 
   result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
   ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
-  EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(result.value().entry->allow_new);
 
   // Find the product-dependent values
   BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
@@ -148,6 +153,12 @@
   EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
   EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
   EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_TRUE(search_result.value().entry->overlayable);
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8c8c254..6b07b1e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -191,14 +191,14 @@
                                        const JavaClassGeneratorOptions& options)
     : context_(context), table_(table), options_(options) {}
 
-bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
   switch (options_.types) {
     case JavaClassGeneratorOptions::SymbolTypes::kAll:
       return false;
     case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
-      return state == SymbolState::kUndefined;
+      return level == Visibility::Level::kUndefined;
     case JavaClassGeneratorOptions::SymbolTypes::kPublic:
-      return state != SymbolState::kPublic;
+      return level != Visibility::Level::kPublic;
   }
   return true;
 }
@@ -444,8 +444,8 @@
     AnnotationProcessor* processor = resource_member->GetCommentBuilder();
 
     // Add the comments from any <public> tags.
-    if (entry.symbol_status.state != SymbolState::kUndefined) {
-      processor->AppendComment(entry.symbol_status.comment);
+    if (entry.visibility.level != Visibility::Level::kUndefined) {
+      processor->AppendComment(entry.visibility.comment);
     }
 
     // Add the comments from all configurations of this entry.
@@ -484,7 +484,7 @@
 Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
                                                         const StringPiece& package_name_to_generate,
                                                         const ResourceEntry& entry) {
-  if (SkipSymbol(entry.symbol_status.state)) {
+  if (SkipSymbol(entry.visibility.level)) {
     return {};
   }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 4992f07..853120b 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -82,7 +82,7 @@
   static std::string TransformToFieldName(const android::StringPiece& symbol);
 
  private:
-  bool SkipSymbol(SymbolState state);
+  bool SkipSymbol(Visibility::Level state);
   bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
 
   // Returns the unmangled resource entry name if the unmangled package is the same as
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 02f4cb1..5beb594 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -139,8 +139,8 @@
           .AddSimple("android:id/one", ResourceId(0x01020000))
           .AddSimple("android:id/two", ResourceId(0x01020001))
           .AddSimple("android:id/three", ResourceId(0x01020002))
-          .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
-          .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+          .SetSymbolState("android:id/one", ResourceId(0x01020000), Visibility::Level::kPublic)
+          .SetSymbolState("android:id/two", ResourceId(0x01020001), Visibility::Level::kPrivate)
           .Build();
 
   std::unique_ptr<IAaptContext> context =
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index a68df1d..da05dc3 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -430,7 +430,10 @@
     return false;
   }
 
-  if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
+  xml::XmlActionExecutorPolicy policy = options_.warn_validation
+                                            ? xml::XmlActionExecutorPolicy::kWhitelistWarning
+                                            : xml::XmlActionExecutorPolicy::kWhitelist;
+  if (!executor.Execute(policy, context->GetDiagnostics(), doc)) {
     return false;
   }
 
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index f5715f6..0caa52e 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -57,6 +57,11 @@
   // The version codename of the framework being compiled against to set for
   // 'android:compileSdkVersionCodename' in the <manifest> tag.
   Maybe<std::string> compile_sdk_version_codename;
+
+  // Wether validation errors should be treated only as warnings. If this is 'true', then an
+  // incorrect node will not result in an error, but only as a warning, and the parsing will
+  // continue.
+  bool warn_validation = false;
 };
 
 // Verifies that the manifest is correctly formed and inserts defaults where specified with
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 1320dcd..c6f895b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -112,7 +112,9 @@
 }
 
 TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
-  ManifestFixerOptions options = {std::string("8"), std::string("22")};
+  ManifestFixerOptions options;
+  options.min_sdk_version_default = std::string("8");
+  options.target_sdk_version_default = std::string("22");
 
   std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -193,7 +195,9 @@
 }
 
 TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
-  ManifestFixerOptions options = {std::string("8"), std::string("22")};
+  ManifestFixerOptions options;
+  options.min_sdk_version_default = std::string("8");
+  options.target_sdk_version_default = std::string("22");
   std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
           <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                     package="android">
@@ -467,4 +471,27 @@
   EXPECT_THAT(attr->value, StrEq("P"));
 }
 
+TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <beep/>
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.warn_validation = true;
+
+  // Unexpected element should result in a warning if the flag is set to 'true'.
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+
+  // Unexpected element should result in an error if the flag is set to 'false'.
+  options.warn_validation = false;
+  manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, IsNull());
+
+  // By default the flag should be set to 'false'.
+  manifest = Verify(input);
+  ASSERT_THAT(manifest, IsNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index eee4b60..675b02a 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -62,7 +62,7 @@
       continue;
     }
 
-    if (type->symbol_status.state != SymbolState::kPublic) {
+    if (type->visibility_level != Visibility::Level::kPublic) {
       // No public attributes, so we can safely leave these private attributes
       // where they are.
       continue;
@@ -72,7 +72,7 @@
 
     move_if(type->entries, std::back_inserter(private_attr_entries),
             [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
-              return entry->symbol_status.state != SymbolState::kPublic;
+              return entry->visibility.level != Visibility::Level::kPublic;
             });
 
     if (private_attr_entries.empty()) {
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 7fcf6e7..168234b 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,9 +30,9 @@
           .AddSimple("android:attr/publicB")
           .AddSimple("android:attr/privateB")
           .SetSymbolState("android:attr/publicA", ResourceId(0x01010000),
-                          SymbolState::kPublic)
+                          Visibility::Level::kPublic)
           .SetSymbolState("android:attr/publicB", ResourceId(0x01010000),
-                          SymbolState::kPublic)
+                          Visibility::Level::kPublic)
           .Build();
 
   PrivateAttributeMover mover;
@@ -81,7 +81,7 @@
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .AddSimple("android:attr/pub")
-          .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic)
+          .SetSymbolState("android:attr/pub", ResourceId(0x01010000), Visibility::Level::kPublic)
           .Build();
 
   ResourceTablePackage* package = table->FindPackage("android");
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index ad7d8b6..b8f8804 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -363,8 +363,8 @@
         NameMangler::Unmangle(&name.entry, &name.package);
 
         // Symbol state information may be lost if there is no value for the resource.
-        if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) {
-          context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source)
+        if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) {
+          context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source)
                                            << "no definition for declared symbol '" << name << "'");
           error = true;
         }
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 58d0607..e819f51 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -83,44 +83,58 @@
 
 static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
                       ResourceTableType* src_type) {
-  if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+  if (src_type->visibility_level > dst_type->visibility_level) {
     // The incoming type's visibility is stronger, so we should override the visibility.
-    if (src_type->symbol_status.state == SymbolState::kPublic) {
+    if (src_type->visibility_level == Visibility::Level::kPublic) {
       // Only copy the ID if the source is public, or else the ID is meaningless.
       dst_type->id = src_type->id;
     }
-    dst_type->symbol_status = std::move(src_type->symbol_status);
-  } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
-             src_type->symbol_status.state == SymbolState::kPublic &&
-             dst_type->id && src_type->id &&
-             dst_type->id.value() != src_type->id.value()) {
+    dst_type->visibility_level = src_type->visibility_level;
+  } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
+             src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
+             src_type->id && dst_type->id.value() != src_type->id.value()) {
     // Both types are public and have different IDs.
-    context->GetDiagnostics()->Error(DiagMessage(src)
-                                     << "cannot merge type '" << src_type->type
-                                     << "': conflicting public IDs");
+    context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
+                                                      << "': conflicting public IDs");
     return false;
   }
   return true;
 }
 
-static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry,
-                       ResourceEntry* src_entry) {
-  if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
-    // The incoming type's visibility is stronger, so we should override the visibility.
-    if (src_entry->symbol_status.state == SymbolState::kPublic) {
-      // Only copy the ID if the source is public, or else the ID is meaningless.
+static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
+                       ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+  // Copy over the strongest visibility.
+  if (src_entry->visibility.level > dst_entry->visibility.level) {
+    // Only copy the ID if the source is public, or else the ID is meaningless.
+    if (src_entry->visibility.level == Visibility::Level::kPublic) {
       dst_entry->id = src_entry->id;
     }
-    dst_entry->symbol_status = std::move(src_entry->symbol_status);
-  } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
-             dst_entry->symbol_status.state == SymbolState::kPublic &&
-             dst_entry->id && src_entry->id &&
-             dst_entry->id.value() != src_entry->id.value()) {
+    dst_entry->visibility = std::move(src_entry->visibility);
+  } else if (src_entry->visibility.level == Visibility::Level::kPublic &&
+             dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id &&
+             src_entry->id && src_entry->id != dst_entry->id) {
     // Both entries are public and have different IDs.
     context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name
                                                       << "': conflicting public IDs");
     return false;
   }
+
+  // Copy over the rest of the properties, if needed.
+  if (src_entry->allow_new) {
+    dst_entry->allow_new = std::move(src_entry->allow_new);
+  }
+
+  if (src_entry->overlayable) {
+    if (dst_entry->overlayable && !overlay) {
+      context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+                                       << "duplicate overlayable declaration for resource '"
+                                       << src_entry->name << "'");
+      context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+                                       << "previous declaration here");
+      return false;
+    }
+    dst_entry->overlayable = std::move(src_entry->overlayable);
+  }
   return true;
 }
 
@@ -202,7 +216,7 @@
       }
 
       ResourceEntry* dst_entry;
-      if (allow_new_resources || src_entry->symbol_status.allow_new) {
+      if (allow_new_resources || src_entry->allow_new) {
         dst_entry = dst_type->FindOrCreateEntry(entry_name);
       } else {
         dst_entry = dst_type->FindEntry(entry_name);
@@ -220,7 +234,7 @@
         continue;
       }
 
-      if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+      if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
         error = true;
         continue;
       }
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 6aab8de..34461c6 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -182,14 +182,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -205,14 +203,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -228,14 +224,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -253,7 +247,7 @@
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", {}, SymbolState::kUndefined, true /*allow new overlay*/)
+          .SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/)
           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
           .Build();
 
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 16898d6..991faad 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -120,8 +120,6 @@
 }
 
 bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
-  // TODO(safarmer): Handle APK version codes for the generated APKs.
-
   std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
   std::unordered_set<std::string> filtered_artifacts;
   std::unordered_set<std::string> kept_artifacts;
@@ -237,8 +235,8 @@
     splits.config_filter = &axis_filter;
   }
 
-  if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
-    wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
+  if (artifact.android_sdk) {
+    wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
   }
 
   std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -301,7 +299,7 @@
       if (xml::Attribute* min_sdk_attr =
               uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
         // Populate with a pre-compiles attribute to we don't need to relink etc.
-        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
         min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
       } else {
         // There was no minSdkVersion. This is strange since at this point we should have been
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 0cfc0bd..3cae0e8 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -190,7 +190,7 @@
   ResourceTable::SearchResult sr = result.value();
 
   std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
-  symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic);
+  symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic);
 
   if (sr.package->id && sr.type->id && sr.entry->id) {
     symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 9d49ca6..e991743 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -233,13 +233,13 @@
             ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
             if (!split_type->id) {
               split_type->id = type->id;
-              split_type->symbol_status = type->symbol_status;
+              split_type->visibility_level = type->visibility_level;
             }
 
             ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
             if (!split_entry->id) {
               split_entry->id = entry->id;
-              split_entry->symbol_status = entry->symbol_status;
+              split_entry->visibility = entry->visibility;
             }
 
             // Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 88897a8..495a48a 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -26,6 +26,7 @@
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::GetOrCreateGroup;
 using ::aapt::io::StringInputStream;
 using ::android::StringPiece;
 
@@ -116,19 +117,20 @@
                                                      const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
   ResourceName res_name = ParseNameOrDie(name);
-  CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
-                                        GetDiagnostics()));
+  CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value),
+                                         GetDiagnostics()));
   return *this;
 }
 
 ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
-                                                           const ResourceId& id, SymbolState state,
+                                                           const ResourceId& id,
+                                                           Visibility::Level level,
                                                            bool allow_new) {
   ResourceName res_name = ParseNameOrDie(name);
-  Symbol symbol;
-  symbol.state = state;
-  symbol.allow_new = allow_new;
-  CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
+  Visibility visibility;
+  visibility.level = level;
+  CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics()));
+  CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics()));
   return *this;
 }
 
@@ -226,6 +228,11 @@
   return *this;
 }
 
+ArtifactBuilder& ArtifactBuilder::SetVersion(int version) {
+  artifact_.version = version;
+  return *this;
+}
+
 ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
   artifact_.abis.push_back(abi);
   return *this;
@@ -250,5 +257,54 @@
   return artifact_;
 }
 
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAbiGroup(
+    const std::string& label, std::vector<configuration::Abi> abis) {
+  return AddGroup(label, &config_.abi_groups, std::move(abis));
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDensityGroup(
+    const std::string& label, std::vector<std::string> densities) {
+  std::vector<ConfigDescription> configs;
+  for (const auto& density : densities) {
+    configs.push_back(test::ParseConfigOrDie(density));
+  }
+  return AddGroup(label, &config_.screen_density_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddLocaleGroup(
+    const std::string& label, std::vector<std::string> locales) {
+  std::vector<ConfigDescription> configs;
+  for (const auto& locale : locales) {
+    configs.push_back(test::ParseConfigOrDie(locale));
+  }
+  return AddGroup(label, &config_.locale_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDeviceFeatureGroup(
+    const std::string& label) {
+  return AddGroup(label, &config_.device_feature_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddGlTextureGroup(
+    const std::string& label) {
+  return AddGroup(label, &config_.gl_texture_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAndroidSdk(
+    std::string label, int min_sdk) {
+  config_.android_sdks[label] = AndroidSdk::ForMinSdk(min_sdk);
+  return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+    configuration::ConfiguredArtifact artifact) {
+  config_.artifacts.push_back(std::move(artifact));
+  return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+  return config_;
+}
+
 }  // namespace test
 }  // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 2f83b78..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -68,7 +68,7 @@
   ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
                                  const ResourceId& id, std::unique_ptr<Value> value);
   ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
-                                       SymbolState state, bool allow_new = false);
+                                       Visibility::Level level, bool allow_new = false);
 
   StringPool* string_pool();
   std::unique_ptr<ResourceTable> Build();
@@ -160,6 +160,7 @@
   ArtifactBuilder() = default;
 
   ArtifactBuilder& SetName(const std::string& name);
+  ArtifactBuilder& SetVersion(int version);
   ArtifactBuilder& AddAbi(configuration::Abi abi);
   ArtifactBuilder& AddDensity(const ConfigDescription& density);
   ArtifactBuilder& AddLocale(const ConfigDescription& locale);
@@ -167,9 +168,41 @@
   configuration::OutputArtifact Build();
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(ArtifactBuilder);
+
   configuration::OutputArtifact artifact_;
 };
 
+class PostProcessingConfigurationBuilder {
+ public:
+  PostProcessingConfigurationBuilder() = default;
+
+  PostProcessingConfigurationBuilder& AddAbiGroup(const std::string& label,
+                                                  std::vector<configuration::Abi> abis = {});
+  PostProcessingConfigurationBuilder& AddDensityGroup(const std::string& label,
+                                                      std::vector<std::string> densities = {});
+  PostProcessingConfigurationBuilder& AddLocaleGroup(const std::string& label,
+                                                     std::vector<std::string> locales = {});
+  PostProcessingConfigurationBuilder& AddDeviceFeatureGroup(const std::string& label);
+  PostProcessingConfigurationBuilder& AddGlTextureGroup(const std::string& label);
+  PostProcessingConfigurationBuilder& AddAndroidSdk(std::string label, int min_sdk);
+  PostProcessingConfigurationBuilder& AddArtifact(configuration::ConfiguredArtifact artrifact);
+
+  configuration::PostProcessingConfiguration Build();
+
+ private:
+  template <typename T>
+  inline PostProcessingConfigurationBuilder& AddGroup(const std::string& label,
+                                                      configuration::Group<T>* group,
+                                                      std::vector<T> to_add = {}) {
+    auto& values = GetOrCreateGroup(label, group);
+    values.insert(std::begin(values), std::begin(to_add), std::end(to_add));
+    return *this;
+  }
+
+  configuration::PostProcessingConfiguration config_;
+};
+
 }  // namespace test
 }  // namespace aapt
 
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 602a902..cb844f0 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -66,7 +66,7 @@
         continue;
       }
 
-      if (policy == XmlActionExecutorPolicy::kWhitelist) {
+      if (policy != XmlActionExecutorPolicy::kNone) {
         DiagMessage error_msg(child_el->line_number);
         error_msg << "unexpected element ";
         PrintElementToDiagMessage(child_el, &error_msg);
@@ -74,8 +74,14 @@
         for (const StringPiece& element : *bread_crumb) {
           error_msg << "<" << element << ">";
         }
-        diag->Error(error_msg);
-        error = true;
+        if (policy == XmlActionExecutorPolicy::kWhitelistWarning) {
+          // Treat the error only as a warning.
+          diag->Warn(error_msg);
+        } else {
+          // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail.
+          diag->Error(error_msg);
+          error = true;
+        }
       }
     }
   }
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index df70100..f689b2a 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -34,10 +34,15 @@
   // Actions are run if elements are matched, errors occur only when actions return false.
   kNone,
 
-  // The actions defined must match and run. If an element is found that does
-  // not match an action, an error occurs.
+  // The actions defined must match and run. If an element is found that does not match an action,
+  // an error occurs.
   // Note: namespaced elements are always ignored.
   kWhitelist,
+
+  // The actions defined should match and run. if an element is found that does not match an
+  // action, a warning is printed.
+  // Note: namespaced elements are always ignored.
+  kWhitelistWarning,
 };
 
 // Contains the actions to perform at this XML node. This is a recursive data structure that
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 468864b..e301eb1 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -97,6 +97,7 @@
     cflags: [
         "-Wall",
         "-Werror",
+        "-fexceptions",
     ],
     export_generated_headers: ["statslog.h"],
     shared_libs: [
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index f76196d..a25784d 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -111,8 +111,9 @@
             return JAVA_TYPE_UNKNOWN;
         case FieldDescriptor::TYPE_MESSAGE:
             // TODO: not the final package name
-            if (field->message_type()->full_name() == "android.os.statsd.WorkSource") {
-                return JAVA_TYPE_WORK_SOURCE;
+            if (field->message_type()->full_name() ==
+                "android.os.statsd.AttributionChain") {
+              return JAVA_TYPE_ATTRIBUTION_CHAIN;
             } else {
                 return JAVA_TYPE_OBJECT;
             }
@@ -136,138 +137,153 @@
 }
 
 /**
+ * Gather the info about an atom proto.
+ */
+int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
+                 vector<java_type_t> *signature) {
+
+  int errorCount = 0;
+  // Build a sorted list of the fields. Descriptor has them in source file
+  // order.
+  map<int, const FieldDescriptor *> fields;
+  for (int j = 0; j < atom->field_count(); j++) {
+    const FieldDescriptor *field = atom->field(j);
+    fields[field->number()] = field;
+  }
+
+  // Check that the parameters start at 1 and go up sequentially.
+  int expectedNumber = 1;
+  for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+       it != fields.end(); it++) {
+    const int number = it->first;
+    const FieldDescriptor *field = it->second;
+    if (number != expectedNumber) {
+      print_error(field,
+                  "Fields must be numbered consecutively starting at 1:"
+                  " '%s' is %d but should be %d\n",
+                  field->name().c_str(), number, expectedNumber);
+      errorCount++;
+      expectedNumber = number;
+      continue;
+    }
+    expectedNumber++;
+  }
+
+  // Check that only allowed types are present. Remove any invalid ones.
+  for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+       it != fields.end(); it++) {
+    const FieldDescriptor *field = it->second;
+
+    java_type_t javaType = java_type(field);
+
+    if (javaType == JAVA_TYPE_UNKNOWN) {
+      print_error(field, "Unkown type for field: %s\n", field->name().c_str());
+      errorCount++;
+      continue;
+    } else if (javaType == JAVA_TYPE_OBJECT) {
+      // Allow attribution chain, but only at position 1.
+      print_error(field, "Message type not allowed for field: %s\n",
+                  field->name().c_str());
+      errorCount++;
+      continue;
+    } else if (javaType == JAVA_TYPE_BYTE_ARRAY) {
+      print_error(field, "Raw bytes type not allowed for field: %s\n",
+                  field->name().c_str());
+      errorCount++;
+      continue;
+    }
+  }
+
+  // Check that if there's an attribution chain, it's at position 1.
+  for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+       it != fields.end(); it++) {
+    int number = it->first;
+    if (number != 1) {
+      const FieldDescriptor *field = it->second;
+      java_type_t javaType = java_type(field);
+      if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+        print_error(
+            field,
+            "AttributionChain fields must have field id 1, in message: '%s'\n",
+            atom->name().c_str());
+        errorCount++;
+      }
+    }
+  }
+
+  // Build the type signature and the atom data.
+  for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+       it != fields.end(); it++) {
+    const FieldDescriptor *field = it->second;
+    java_type_t javaType = java_type(field);
+
+    AtomField atField(field->name(), javaType);
+    if (javaType == JAVA_TYPE_ENUM) {
+      // All enums are treated as ints when it comes to function signatures.
+      signature->push_back(JAVA_TYPE_INT);
+      const EnumDescriptor *enumDescriptor = field->enum_type();
+      for (int i = 0; i < enumDescriptor->value_count(); i++) {
+        atField.enumValues[enumDescriptor->value(i)->number()] =
+            enumDescriptor->value(i)->name().c_str();
+      }
+    } else {
+      signature->push_back(javaType);
+    }
+    atomDecl->fields.push_back(atField);
+  }
+
+  return errorCount;
+}
+
+/**
  * Gather the info about the atoms.
  */
-int
-collate_atoms(const Descriptor* descriptor, Atoms* atoms)
-{
-    int errorCount = 0;
-    const bool dbg = false;
+int collate_atoms(const Descriptor *descriptor, Atoms *atoms) {
+  int errorCount = 0;
+  const bool dbg = false;
 
-    for (int i=0; i<descriptor->field_count(); i++) {
-        const FieldDescriptor* atomField = descriptor->field(i);
-
-        if (dbg) {
-            printf("   %s (%d)\n", atomField->name().c_str(), atomField->number());
-        }
-
-        // StatsEvent only has one oneof, which contains only messages. Don't allow other types.
-        if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) {
-            print_error(atomField,
-                    "Bad type for atom. StatsEvent can only have message type fields: %s\n",
-                    atomField->name().c_str());
-            errorCount++;
-            continue;
-        }
-
-        const Descriptor* atom = atomField->message_type();
-
-        // Build a sorted list of the fields. Descriptor has them in source file order.
-        map<int,const FieldDescriptor*> fields;
-        for (int j=0; j<atom->field_count(); j++) {
-            const FieldDescriptor* field = atom->field(j);
-            fields[field->number()] = field;
-        }
-
-        // Check that the parameters start at 1 and go up sequentially.
-        int expectedNumber = 1;
-        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
-                it != fields.end(); it++) {
-            const int number = it->first;
-            const FieldDescriptor* field = it->second;
-            if (number != expectedNumber) {
-                print_error(field, "Fields must be numbered consecutively starting at 1:"
-                        " '%s' is %d but should be %d\n",
-                        field->name().c_str(), number, expectedNumber);
-                errorCount++;
-                expectedNumber = number;
-                continue;
-            }
-            expectedNumber++;
-        }
-
-        // Check that only allowed types are present. Remove any invalid ones.
-        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
-                it != fields.end(); it++) {
-            const FieldDescriptor* field = it->second;
-
-            java_type_t javaType = java_type(field);
-
-            if (javaType == JAVA_TYPE_UNKNOWN) {
-                print_error(field, "Unkown type for field: %s\n", field->name().c_str());
-                errorCount++;
-                continue;
-            } else if (javaType == JAVA_TYPE_OBJECT) {
-                // Allow WorkSources, but only at position 1.
-                print_error(field, "Message type not allowed for field: %s\n",
-                        field->name().c_str());
-                errorCount++;
-                continue;
-            } else if (javaType == JAVA_TYPE_BYTE_ARRAY) {
-                print_error(field, "Raw bytes type not allowed for field: %s\n",
-                        field->name().c_str());
-                errorCount++;
-                continue;
-            }
-        }
-
-        // Check that if there's a WorkSource, it's at position 1.
-        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
-                it != fields.end(); it++) {
-            int number = it->first;
-            if (number != 1) {
-                const FieldDescriptor* field = it->second;
-                java_type_t javaType = java_type(field);
-                if (javaType == JAVA_TYPE_WORK_SOURCE) {
-                    print_error(field, "WorkSource fields must have field id 1, in message: '%s'\n",
-                           atom->name().c_str());
-                    errorCount++;
-                }
-            }
-        }
-
-        AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
-
-        // Build the type signature and the atom data.
-        vector<java_type_t> signature;
-        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
-                it != fields.end(); it++) {
-            const FieldDescriptor* field = it->second;
-            java_type_t javaType = java_type(field);
-
-            AtomField atField(field->name(), javaType);
-            if (javaType == JAVA_TYPE_ENUM) {
-                // All enums are treated as ints when it comes to function signatures.
-                signature.push_back(JAVA_TYPE_INT);
-                const EnumDescriptor* enumDescriptor = field->enum_type();
-                for (int i = 0; i < enumDescriptor->value_count(); i++) {
-                    atField.enumValues[enumDescriptor->value(i)->number()] =
-                        enumDescriptor->value(i)->name().c_str();
-                }
-            } else {
-                signature.push_back(javaType);
-            }
-            atomDecl.fields.push_back(atField);
-        }
-
-        atoms->signatures.insert(signature);
-        atoms->decls.insert(atomDecl);
-    }
+  for (int i = 0; i < descriptor->field_count(); i++) {
+    const FieldDescriptor *atomField = descriptor->field(i);
 
     if (dbg) {
-        printf("signatures = [\n");
-        for (set<vector<java_type_t>>::const_iterator it = atoms->signatures.begin();
-                it != atoms->signatures.end(); it++) {
-            printf("   ");
-            for (vector<java_type_t>::const_iterator jt = it->begin(); jt != it->end(); jt++) {
-                printf(" %d", (int)*jt);
-            }
-            printf("\n");
-        }
-        printf("]\n");
+      printf("   %s (%d)\n", atomField->name().c_str(), atomField->number());
     }
 
-    return errorCount;
+    // StatsEvent only has one oneof, which contains only messages. Don't allow
+    // other types.
+    if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) {
+      print_error(atomField,
+                  "Bad type for atom. StatsEvent can only have message type "
+                  "fields: %s\n",
+                  atomField->name().c_str());
+      errorCount++;
+      continue;
+    }
+
+    const Descriptor *atom = atomField->message_type();
+    AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
+    vector<java_type_t> signature;
+    errorCount += collate_atom(atom, &atomDecl, &signature);
+    atoms->signatures.insert(signature);
+    atoms->decls.insert(atomDecl);
+  }
+
+  if (dbg) {
+    printf("signatures = [\n");
+    for (set<vector<java_type_t>>::const_iterator it =
+             atoms->signatures.begin();
+         it != atoms->signatures.end(); it++) {
+      printf("   ");
+      for (vector<java_type_t>::const_iterator jt = it->begin();
+           jt != it->end(); jt++) {
+        printf(" %d", (int)*jt);
+      }
+      printf("\n");
+    }
+    printf("]\n");
+  }
+
+  return errorCount;
 }
 
 }  // namespace stats_log_api_gen
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 2f840d7..cd0625c 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -37,22 +37,21 @@
  * The types for atom parameters.
  */
 typedef enum {
-    JAVA_TYPE_UNKNOWN = 0,
+  JAVA_TYPE_UNKNOWN = 0,
 
-    JAVA_TYPE_WORK_SOURCE = 1,
-    JAVA_TYPE_BOOLEAN = 2,
-    JAVA_TYPE_INT = 3,
-    JAVA_TYPE_LONG = 4,
-    JAVA_TYPE_FLOAT = 5,
-    JAVA_TYPE_DOUBLE = 6,
-    JAVA_TYPE_STRING = 7,
-    JAVA_TYPE_ENUM = 8,
+  JAVA_TYPE_ATTRIBUTION_CHAIN = 1,
+  JAVA_TYPE_BOOLEAN = 2,
+  JAVA_TYPE_INT = 3,
+  JAVA_TYPE_LONG = 4,
+  JAVA_TYPE_FLOAT = 5,
+  JAVA_TYPE_DOUBLE = 6,
+  JAVA_TYPE_STRING = 7,
+  JAVA_TYPE_ENUM = 8,
 
-    JAVA_TYPE_OBJECT = -1,
-    JAVA_TYPE_BYTE_ARRAY = -2,
+  JAVA_TYPE_OBJECT = -1,
+  JAVA_TYPE_BYTE_ARRAY = -2,
 } java_type_t;
 
-
 /**
  * The name and type for an atom field.
  */
@@ -100,9 +99,11 @@
  * Gather the information about the atoms.  Returns the number of errors.
  */
 int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
+int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
+                 vector<java_type_t> *signature);
 
 }  // namespace stats_log_api_gen
 }  // namespace android
 
 
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
\ No newline at end of file
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 423d028..5e93c08 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -24,8 +24,6 @@
 
 using android::os::statsd::Atom;
 
-// TODO: Support WorkSources
-
 /**
  * Turn lower and camel case into upper case with underscores.
  */
@@ -97,13 +95,13 @@
     }
 }
 
-static int
-write_stats_log_cpp(FILE* out, const Atoms& atoms)
-{
+static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
+                               const AtomDecl &attributionDecl) {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
     fprintf(out, "\n");
 
+    fprintf(out, "#include <exception>\n");
     fprintf(out, "#include <log/log_event_list.h>\n");
     fprintf(out, "#include <log/log.h>\n");
     fprintf(out, "#include <statslog.h>\n");
@@ -117,15 +115,29 @@
     // Print write methods
     fprintf(out, "\n");
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
+        signature != atoms.signatures.end(); signature++) {
         int argIndex;
 
         fprintf(out, "void\n");
         fprintf(out, "stats_write(int32_t code");
         argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
-                arg != signature->end(); arg++) {
-            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                            fprintf(out, ", const std::vector<%s>& %s",
+                                 cpp_type_name(chainField.javaType),
+                                 chainField.name.c_str());
+                    } else {
+                            fprintf(out, ", const %s* %s, size_t %s_length",
+                                 cpp_type_name(chainField.javaType),
+                                 chainField.name.c_str(), chainField.name.c_str());
+                    }
+                }
+            } else {
+                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            }
             argIndex++;
         }
         fprintf(out, ")\n");
@@ -133,15 +145,40 @@
         fprintf(out, "{\n");
         argIndex = 1;
         fprintf(out, "    android_log_event_list event(kStatsEventTag);\n");
-        fprintf(out, "    event << code;\n");
+        fprintf(out, "    event << code;\n\n");
         for (vector<java_type_t>::const_iterator arg = signature->begin();
-                arg != signature->end(); arg++) {
-            if (*arg == JAVA_TYPE_STRING) {
-                fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
-                fprintf(out, "        arg%d = \"\";\n", argIndex);
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (const auto &chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, "    if (%s_length != %s.size()) {\n",
+                            attributionDecl.fields.front().name.c_str(), chainField.name.c_str());
+                        fprintf(out, "        throw std::invalid_argument(\"attribution fields with"
+                            " diff length: %s vs %s\");\n",
+                            attributionDecl.fields.front().name.c_str(),
+                            chainField.name.c_str());
+                        fprintf(out, "        return;\n");
+                        fprintf(out, "    }\n");
+                    }
+                }
+                fprintf(out, "\n    event.begin();\n");
+                fprintf(out, "    for (size_t i = 0; i < %s_length; ++i) {\n",
+                    attributionDecl.fields.front().name.c_str());
+                fprintf(out, "        event.begin();\n");
+                for (const auto &chainField : attributionDecl.fields) {
+                    fprintf(out, "        event << %s[i];\n", chainField.name.c_str());
+                }
+                fprintf(out, "        event.end();\n");
                 fprintf(out, "    }\n");
+                fprintf(out, "    event.end();\n\n");
+            } else {
+                if (*arg == JAVA_TYPE_STRING) {
+                    fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
+                    fprintf(out, "        arg%d = \"\";\n", argIndex);
+                    fprintf(out, "    }\n");
+                }
+                fprintf(out, "    event << arg%d;\n", argIndex);
             }
-            fprintf(out, "    event << arg%d;\n", argIndex);
             argIndex++;
         }
 
@@ -160,7 +197,7 @@
 
 
 static int
-write_stats_log_header(FILE* out, const Atoms& atoms)
+write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
 {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
@@ -168,6 +205,7 @@
     fprintf(out, "#pragma once\n");
     fprintf(out, "\n");
     fprintf(out, "#include <stdint.h>\n");
+    fprintf(out, "#include <vector>\n");
     fprintf(out, "\n");
 
     fprintf(out, "namespace android {\n");
@@ -185,7 +223,7 @@
     size_t i = 0;
     // Print constants
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-            atom != atoms.decls.end(); atom++) {
+        atom != atoms.decls.end(); atom++) {
         string constant = make_constant_name(atom->name);
         fprintf(out, "\n");
         fprintf(out, "    /**\n");
@@ -193,7 +231,21 @@
         fprintf(out, "     * Usage: stats_write(StatsLog.%s", constant.c_str());
         for (vector<AtomField>::const_iterator field = atom->fields.begin();
                 field != atom->fields.end(); field++) {
-            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, ", const std::vector<%s>& %s",
+                             cpp_type_name(chainField.javaType),
+                             chainField.name.c_str());
+                    } else {
+                        fprintf(out, ", const %s* %s, size_t %s_length",
+                             cpp_type_name(chainField.javaType),
+                             chainField.name.c_str(), chainField.name.c_str());
+                    }
+                }
+            } else {
+                fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+            }
         }
         fprintf(out, ");\n");
         fprintf(out, "     */\n");
@@ -208,8 +260,7 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
-    fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
-            maxPushedAtomId);
+    fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", maxPushedAtomId);
 
     // Print write methods
     fprintf(out, "//\n");
@@ -220,8 +271,21 @@
         fprintf(out, "void stats_write(int32_t code ");
         int argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
-                arg != signature->end(); arg++) {
-            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, ", const std::vector<%s>& %s",
+                            cpp_type_name(chainField.javaType), chainField.name.c_str());
+                    } else {
+                        fprintf(out, ", const %s* %s, size_t %s_length",
+                            cpp_type_name(chainField.javaType),
+                            chainField.name.c_str(), chainField.name.c_str());
+                    }
+                }
+            } else {
+                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            }
             argIndex++;
         }
         fprintf(out, ");\n");
@@ -235,7 +299,7 @@
 }
 
 static int
-write_stats_log_java(FILE* out, const Atoms& atoms)
+write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
 {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
@@ -247,7 +311,7 @@
     fprintf(out, " * API For logging statistics events.\n");
     fprintf(out, " * @hide\n");
     fprintf(out, " */\n");
-    fprintf(out, "public final class StatsLog {\n");
+    fprintf(out, "public class StatsLogInternal {\n");
     fprintf(out, "    // Constants for atom codes.\n");
 
     // Print constants for the atom codes.
@@ -259,8 +323,15 @@
         fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
         fprintf(out, "     * Usage: StatsLog.write(StatsLog.%s", constant.c_str());
         for (vector<AtomField>::const_iterator field = atom->fields.begin();
-                field != atom->fields.end(); field++) {
-            fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+            field != atom->fields.end(); field++) {
+            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, ", %s[] %s",
+                        java_type_name(chainField.javaType), chainField.name.c_str());
+                }
+            } else {
+                fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+            }
         }
         fprintf(out, ");\n");
         fprintf(out, "     */\n");
@@ -271,33 +342,41 @@
     // Print constants for the enum values.
     fprintf(out, "    // Constants for enum values.\n\n");
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-            atom != atoms.decls.end(); atom++) {
+        atom != atoms.decls.end(); atom++) {
         for (vector<AtomField>::const_iterator field = atom->fields.begin();
-                field != atom->fields.end(); field++) {
-          if (field->javaType == JAVA_TYPE_ENUM) {
-            fprintf(out, "    // Values for %s.%s\n", atom->message.c_str(), field->name.c_str());
-            for (map<int, string>::const_iterator value = field->enumValues.begin();
-                 value != field->enumValues.end(); value++) {
-              fprintf(out, "    public static final int %s__%s__%s = %d;\n",
-                      make_constant_name(atom->message).c_str(),
-                      make_constant_name(field->name).c_str(),
-                      make_constant_name(value->second).c_str(),
-                      value->first);
+            field != atom->fields.end(); field++) {
+            if (field->javaType == JAVA_TYPE_ENUM) {
+                fprintf(out, "    // Values for %s.%s\n", atom->message.c_str(),
+                    field->name.c_str());
+                for (map<int, string>::const_iterator value = field->enumValues.begin();
+                    value != field->enumValues.end(); value++) {
+                    fprintf(out, "    public static final int %s__%s__%s = %d;\n",
+                        make_constant_name(atom->message).c_str(),
+                        make_constant_name(field->name).c_str(),
+                        make_constant_name(value->second).c_str(),
+                        value->first);
+                }
+                fprintf(out, "\n");
             }
-            fprintf(out, "\n");
-          }
         }
     }
 
     // Print write methods
     fprintf(out, "    // Write methods\n");
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
+        signature != atoms.signatures.end(); signature++) {
         fprintf(out, "    public static native void write(int code");
         int argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
-                arg != signature->end(); arg++) {
-            fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, ", %s[] %s",
+                        java_type_name(chainField.javaType), chainField.name.c_str());
+                }
+            } else {
+                fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+            }
             argIndex++;
         }
         fprintf(out, ");\n");
@@ -330,12 +409,25 @@
     }
 }
 
+static const char*
+jni_array_type_name(java_type_t type)
+{
+    switch (type) {
+        case JAVA_TYPE_INT:
+            return "jintArray";
+        case JAVA_TYPE_STRING:
+            return "jobjectArray";
+        default:
+            return "UNKNOWN";
+    }
+}
+
 static string
 jni_function_name(const vector<java_type_t>& signature)
 {
     string result("StatsLog_write");
     for (vector<java_type_t>::const_iterator arg = signature.begin();
-            arg != signature.end(); arg++) {
+        arg != signature.end(); arg++) {
         switch (*arg) {
             case JAVA_TYPE_BOOLEAN:
                 result += "_boolean";
@@ -356,6 +448,9 @@
             case JAVA_TYPE_STRING:
                 result += "_String";
                 break;
+            case JAVA_TYPE_ATTRIBUTION_CHAIN:
+              result += "_AttributionChain";
+              break;
             default:
                 result += "_UNKNOWN";
                 break;
@@ -387,19 +482,26 @@
 }
 
 static string
-jni_function_signature(const vector<java_type_t>& signature)
+jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &attributionDecl)
 {
     string result("(I");
     for (vector<java_type_t>::const_iterator arg = signature.begin();
-            arg != signature.end(); arg++) {
-        result += java_type_signature(*arg);
+        arg != signature.end(); arg++) {
+        if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                result += "[";
+                result += java_type_signature(chainField.javaType);
+            }
+        } else {
+            result += java_type_signature(*arg);
+        }
     }
     result += ")V";
     return result;
 }
 
 static int
-write_stats_log_jni(FILE* out, const Atoms& atoms)
+write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
 {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
@@ -408,6 +510,8 @@
     fprintf(out, "#include <statslog.h>\n");
     fprintf(out, "\n");
     fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
+    fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
+    fprintf(out, "#include <utils/Vector.h>\n");
     fprintf(out, "#include \"core_jni_helpers.h\"\n");
     fprintf(out, "#include \"jni.h\"\n");
     fprintf(out, "\n");
@@ -419,7 +523,7 @@
 
     // Print write methods
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
+        signature != atoms.signatures.end(); signature++) {
         int argIndex;
 
         fprintf(out, "static void\n");
@@ -428,7 +532,14 @@
         argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
-            fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex);
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, ", %s %s", jni_array_type_name(chainField.javaType),
+                        chainField.name.c_str());
+                }
+            } else {
+                fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex);
+            }
             argIndex++;
         }
         fprintf(out, ")\n");
@@ -437,10 +548,11 @@
 
         // Prepare strings
         argIndex = 1;
-        bool hadString = false;
+        bool hadStringOrChain = false;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_STRING) {
+                hadStringOrChain = true;
                 fprintf(out, "    const char* str%d;\n", argIndex);
                 fprintf(out, "    if (arg%d != NULL) {\n", argIndex);
                 fprintf(out, "        str%d = env->GetStringUTFChars(arg%d, NULL);\n",
@@ -448,13 +560,52 @@
                 fprintf(out, "    } else {\n");
                 fprintf(out, "        str%d = NULL;\n", argIndex);
                 fprintf(out, "    }\n");
-                hadString = true;
+            } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                hadStringOrChain = true;
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, "    size_t %s_length = env->GetArrayLength(%s);\n",
+                        chainField.name.c_str(), chainField.name.c_str());
+                    if (chainField.name != attributionDecl.fields.front().name) {
+                        fprintf(out, "    if (%s_length != %s_length) {\n",
+                            chainField.name.c_str(),
+                            attributionDecl.fields.front().name.c_str());
+                        fprintf(out, "        jniThrowException(env, "
+                            "\"java/lang/IllegalArgumentException\", "
+                            "\"invalid attribution field(%s) length.\");\n",
+                            chainField.name.c_str());
+                        fprintf(out, "        return;\n");
+                        fprintf(out, "    }\n");
+                    }
+                    if (chainField.javaType == JAVA_TYPE_INT) {
+                        fprintf(out, "    jint* %s_array = env->GetIntArrayElements(%s, NULL);\n",
+                            chainField.name.c_str(), chainField.name.c_str());
+                    } else if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, "    std::vector<%s> %s_vec;\n",
+                            cpp_type_name(chainField.javaType), chainField.name.c_str());
+                        fprintf(out, "    std::vector<ScopedUtfChars*> scoped_%s_vec;\n",
+                            chainField.name.c_str());
+                        fprintf(out, "    for (size_t i = 0; i < %s_length; ++i) {\n",
+                            chainField.name.c_str());
+                        fprintf(out, "        jstring jstr = "
+                            "(jstring)env->GetObjectArrayElement(%s, i);\n",
+                             chainField.name.c_str());
+                        fprintf(out, "        ScopedUtfChars* scoped_%s = "
+                            "new ScopedUtfChars(env, jstr);\n",
+                             chainField.name.c_str());
+                        fprintf(out, "        %s_vec.push_back(scoped_%s->c_str());\n",
+                                chainField.name.c_str(), chainField.name.c_str());
+                        fprintf(out, "        scoped_%s_vec.push_back(scoped_%s);\n",
+                                chainField.name.c_str(), chainField.name.c_str());
+                        fprintf(out, "    }\n");
+                    }
+                    fprintf(out, "\n");
+                }
             }
             argIndex++;
         }
-
-        // Emit this to quiet the unused parameter warning if there were no strings.
-        if (!hadString) {
+        // Emit this to quiet the unused parameter warning if there were no strings or attribution
+        // chains.
+        if (!hadStringOrChain) {
             fprintf(out, "    (void)env;\n");
         }
 
@@ -463,11 +614,24 @@
         fprintf(out, "    android::util::stats_write(code");
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
-            const char* argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
-            fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_INT) {
+                        fprintf(out, ", (const %s*)%s_array, %s_length",
+                            cpp_type_name(chainField.javaType),
+                            chainField.name.c_str(), chainField.name.c_str());
+                    } else if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, ", %s_vec", chainField.name.c_str());
+                    }
+                }
+            } else {
+                const char *argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
+                fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
+            }
             argIndex++;
         }
         fprintf(out, ");\n");
+        fprintf(out, "\n");
 
         // Clean up strings
         argIndex = 1;
@@ -478,6 +642,18 @@
                 fprintf(out, "        env->ReleaseStringUTFChars(arg%d, str%d);\n",
                         argIndex, argIndex);
                 fprintf(out, "    }\n");
+            } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_INT) {
+                        fprintf(out, "    env->ReleaseIntArrayElements(%s, %s_array, 0);\n",
+                            chainField.name.c_str(), chainField.name.c_str());
+                    } else if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, "    for (size_t i = 0; i < %s_length; ++i) {\n",
+                            chainField.name.c_str());
+                        fprintf(out, "        delete scoped_%s_vec[i];\n", chainField.name.c_str());
+                        fprintf(out, "    }\n");
+                    }
+                }
             }
             argIndex++;
         }
@@ -494,8 +670,8 @@
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
             signature != atoms.signatures.end(); signature++) {
         fprintf(out, "    { \"write\", \"%s\", (void*)%s },\n",
-                jni_function_signature(*signature).c_str(),
-                jni_function_name(*signature).c_str());
+            jni_function_signature(*signature, attributionDecl).c_str(),
+            jni_function_name(*signature).c_str());
     }
     fprintf(out, "};\n");
     fprintf(out, "\n");
@@ -591,6 +767,11 @@
         return 1;
     }
 
+    AtomDecl attributionDecl;
+    vector<java_type_t> attributionSignature;
+    collate_atom(android::os::statsd::Attribution::descriptor(),
+                 &attributionDecl, &attributionSignature);
+
     // Write the .cpp file
     if (cppFilename.size() != 0) {
         FILE* out = fopen(cppFilename.c_str(), "w");
@@ -598,7 +779,8 @@
             fprintf(stderr, "Unable to open file for write: %s\n", cppFilename.c_str());
             return 1;
         }
-        errorCount = android::stats_log_api_gen::write_stats_log_cpp(out, atoms);
+        errorCount = android::stats_log_api_gen::write_stats_log_cpp(
+            out, atoms, attributionDecl);
         fclose(out);
     }
 
@@ -609,7 +791,8 @@
             fprintf(stderr, "Unable to open file for write: %s\n", headerFilename.c_str());
             return 1;
         }
-        errorCount = android::stats_log_api_gen::write_stats_log_header(out, atoms);
+        errorCount = android::stats_log_api_gen::write_stats_log_header(
+            out, atoms, attributionDecl);
         fclose(out);
     }
 
@@ -620,7 +803,8 @@
             fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str());
             return 1;
         }
-        errorCount = android::stats_log_api_gen::write_stats_log_java(out, atoms);
+        errorCount = android::stats_log_api_gen::write_stats_log_java(
+            out, atoms, attributionDecl);
         fclose(out);
     }
 
@@ -631,7 +815,8 @@
             fprintf(stderr, "Unable to open file for write: %s\n", jniFilename.c_str());
             return 1;
         }
-        errorCount = android::stats_log_api_gen::write_stats_log_jni(out, atoms);
+        errorCount = android::stats_log_api_gen::write_stats_log_jni(
+            out, atoms, attributionDecl);
         fclose(out);
     }
 
@@ -650,4 +835,4 @@
     GOOGLE_PROTOBUF_VERIFY_VERSION;
 
     return android::stats_log_api_gen::run(argc, argv);
-}
+}
\ No newline at end of file
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index 6686158..84b22cda 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -39,22 +39,22 @@
 }
 
 message AllTypesAtom {
-    optional android.os.statsd.WorkSource attribution = 1;
-    optional double double_field = 2;
-    optional float float_field = 3;
-    optional int64 int64_field = 4;
-    optional uint64 uint64_field = 5;
-    optional int32 int32_field = 6;
-    optional fixed64 fixed64_field = 7;
-    optional fixed32 fixed32_field = 8;
-    optional bool bool_field = 9;
-    optional string string_field = 10;
-    optional uint32 uint32_field = 11;
-    optional AnEnum enum_field = 12;
-    optional sfixed32 sfixed32_field = 13;
-    optional sfixed64 sfixed64_field = 14;
-    optional sint32 sint32_field = 15;
-    optional sint64 sint64_field = 16;
+  optional android.os.statsd.AttributionChain attribution_chain = 1;
+  optional double double_field = 2;
+  optional float float_field = 3;
+  optional int64 int64_field = 4;
+  optional uint64 uint64_field = 5;
+  optional int32 int32_field = 6;
+  optional fixed64 fixed64_field = 7;
+  optional fixed32 fixed32_field = 8;
+  optional bool bool_field = 9;
+  optional string string_field = 10;
+  optional uint32 uint32_field = 11;
+  optional AnEnum enum_field = 12;
+  optional sfixed32 sfixed32_field = 13;
+  optional sfixed64 sfixed64_field = 14;
+  optional sint32 sint32_field = 15;
+  optional sint64 sint64_field = 16;
 }
 
 message Event {
@@ -99,14 +99,12 @@
     }
 }
 
-message BadWorkSourcePositionAtom {
-    optional int32 field1 = 1;
-    optional android.os.statsd.WorkSource attribution = 2;
+message BadAttributionChainPositionAtom {
+  optional int32 field1 = 1;
+  optional android.os.statsd.AttributionChain attribution = 2;
 }
 
-message BadWorkSourcePosition {
-    oneof event {
-        BadWorkSourcePositionAtom bad = 1;
-    }
+message BadAttributionChainPosition {
+  oneof event { BadAttributionChainPositionAtom bad = 1; }
 }
 
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 073f2cf..b2b7298 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -64,7 +64,7 @@
         } \
     } while(0)
 
-/** Expects that exactly one specific field has expected enum values. */ 
+/** Expects that exactly one specific field has expected enum values. */
 #define EXPECT_HAS_ENUM_FIELD(atom, field_name, values)        \
     do { \
         for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
@@ -95,24 +95,25 @@
     EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT, JAVA_TYPE_INT);
 
     // AllTypesAtom
-    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures,
-                JAVA_TYPE_WORK_SOURCE, // WorkSource
-                JAVA_TYPE_DOUBLE, // double
-                JAVA_TYPE_FLOAT, // float
-                JAVA_TYPE_LONG, // int64
-                JAVA_TYPE_LONG, // uint64
-                JAVA_TYPE_INT, // int32
-                JAVA_TYPE_LONG, // fixed64
-                JAVA_TYPE_INT, // fixed32
-                JAVA_TYPE_BOOLEAN, // bool
-                JAVA_TYPE_STRING, // string
-                JAVA_TYPE_INT, // uint32
-                JAVA_TYPE_INT, // AnEnum
-                JAVA_TYPE_INT, // sfixed32
-                JAVA_TYPE_LONG, // sfixed64
-                JAVA_TYPE_INT, // sint32
-                JAVA_TYPE_LONG // sint64
-            );
+    EXPECT_SET_CONTAINS_SIGNATURE(
+        atoms.signatures,
+        JAVA_TYPE_ATTRIBUTION_CHAIN, // AttributionChain
+        JAVA_TYPE_DOUBLE,            // double
+        JAVA_TYPE_FLOAT,             // float
+        JAVA_TYPE_LONG,              // int64
+        JAVA_TYPE_LONG,              // uint64
+        JAVA_TYPE_INT,               // int32
+        JAVA_TYPE_LONG,              // fixed64
+        JAVA_TYPE_INT,               // fixed32
+        JAVA_TYPE_BOOLEAN,           // bool
+        JAVA_TYPE_STRING,            // string
+        JAVA_TYPE_INT,               // uint32
+        JAVA_TYPE_INT,               // AnEnum
+        JAVA_TYPE_INT,               // sfixed32
+        JAVA_TYPE_LONG,              // sfixed64
+        JAVA_TYPE_INT,               // sint32
+        JAVA_TYPE_LONG               // sint64
+    );
 
     set<AtomDecl>::const_iterator atom = atoms.decls.begin();
     EXPECT_EQ(1, atom->code);
@@ -187,14 +188,16 @@
 }
 
 /**
- * Test that atoms that have a WorkSource not in the first position are rejected.
+ * Test that atoms that have an attribution chain not in the first position are
+ * rejected.
  */
-TEST(CollationTest, FailBadWorkSourcePosition) {
-    Atoms atoms;
-    int errorCount = collate_atoms(BadWorkSourcePosition::descriptor(), &atoms);
+TEST(CollationTest, FailBadAttributionChainPosition) {
+  Atoms atoms;
+  int errorCount =
+      collate_atoms(BadAttributionChainPosition::descriptor(), &atoms);
 
-    EXPECT_EQ(1, errorCount);
+  EXPECT_EQ(1, errorCount);
 }
 
 }  // namespace stats_log_api_gen
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 4e9391d..1121ead 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -32,26 +32,6 @@
     shared_libs: ["libprotoc"],
 }
 
-cc_library {
-    name: "streamingflags",
-    host_supported: true,
-    proto: {
-        export_proto_headers: true,
-        include_dirs: ["external/protobuf/src"],
-    },
-
-    target: {
-        host: {
-            proto: {
-                type: "full",
-            },
-            srcs: [
-                "stream.proto",
-            ],
-        },
-    },
-}
-
 cc_binary_host {
     name: "protoc-gen-javastream",
     srcs: [
@@ -68,5 +48,4 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
-    static_libs: ["streamingflags"],
 }
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index 745b3dc..d6b9d81 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -2,8 +2,6 @@
 #include "stream_proto_utils.h"
 #include "string_utils.h"
 
-#include <frameworks/base/tools/streaming_proto/stream.pb.h>
-
 #include <iomanip>
 #include <iostream>
 #include <sstream>
@@ -12,18 +10,14 @@
 using namespace google::protobuf::io;
 using namespace std;
 
+const bool GENERATE_MAPPING = true;
+
 static string
 make_filename(const FileDescriptorProto& file_descriptor)
 {
     return file_descriptor.name() + ".h";
 }
 
-static inline bool
-should_generate_enums_mapping(const EnumDescriptorProto& enu)
-{
-    return enu.options().GetExtension(stream_enum).enable_enums_mapping();
-}
-
 static void
 write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
 {
@@ -36,7 +30,7 @@
                 << " = " << value.number() << ";" << endl;
     }
 
-    if (should_generate_enums_mapping(enu)) {
+    if (GENERATE_MAPPING) {
         string name = make_constant_name(enu.name());
         string prefix = name + "_";
         text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
@@ -79,23 +73,11 @@
     text << endl;
 }
 
-static inline bool
-should_generate_fields_mapping(const DescriptorProto& message)
-{
-    return message.options().GetExtension(stream_msg).enable_fields_mapping();
-}
-
-static inline bool
-should_generate_fields_mapping_recursively(const DescriptorProto& message) {
-    return message.options().GetExtension(stream_msg).enable_fields_mapping_recursively();
-}
-
 static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent, bool genMapping)
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
 {
     int N;
     const string indented = indent + INDENT;
-    genMapping |= should_generate_fields_mapping_recursively(message);
 
     text << indent << "// message " << message.name() << endl;
     text << indent << "namespace " << message.name() << " {" << endl;
@@ -109,7 +91,7 @@
     // Nested classes
     N = message.nested_type_size();
     for (int i=0; i<N; i++) {
-        write_message(text, message.nested_type(i), indented, genMapping);
+        write_message(text, message.nested_type(i), indented);
     }
 
     // Fields
@@ -118,7 +100,7 @@
         write_field(text, message.field(i), indented);
     }
 
-    if (genMapping | should_generate_fields_mapping(message)) {
+    if (GENERATE_MAPPING) {
         N = message.field_size();
         text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
         text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
@@ -167,7 +149,7 @@
 
     N = file_descriptor.message_type_size();
     for (size_t i=0; i<N; i++) {
-        write_message(text, file_descriptor.message_type(i), "", false);
+        write_message(text, file_descriptor.message_type(i), "");
     }
 
     for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
deleted file mode 100644
index e9b24a8..0000000
--- a/tools/streaming_proto/stream.proto
+++ /dev/null
@@ -1,45 +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.
- */
-
-syntax = "proto2";
-
-import "google/protobuf/descriptor.proto";
-
-package android.stream_proto;
-
-// This option tells streaming proto plugin to compile .proto files with extra features.
-message MessageOptions {
-  // creates a mapping of field names of the message to its field ids
-  optional bool enable_fields_mapping = 1;
-
-  // creates mapping between field names to its field ids and recursively for its submessages.
-  optional bool enable_fields_mapping_recursively = 2;
-}
-
-extend google.protobuf.MessageOptions {
-    // Flags used by streaming proto plugins
-    optional MessageOptions stream_msg = 126856794;
-}
-
-message EnumOptions {
-  // creates a mapping of enum names to its values, strip its prefix enum type for each value
-  optional bool enable_enums_mapping = 1;
-}
-
-extend google.protobuf.EnumOptions {
-    // Flags used by streaming proto plugins
-    optional EnumOptions stream_enum = 126856794;
-}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a158d94..ea9be29 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2846,8 +2846,7 @@
      * gets added to the list of configured networks for the foreground user.
      *
      * For a new network, this function is used instead of a
-     * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
-     * reconnect()
+     * sequence of addNetwork(), enableNetwork(), and reconnect()
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
@@ -2869,8 +2868,7 @@
     /**
      * Connect to a network with the given networkId.
      *
-     * This function is used instead of a enableNetwork(), saveConfiguration() and
-     * reconnect()
+     * This function is used instead of a enableNetwork() and reconnect()
      *
      * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
      *        getConfiguredNetworks}.
@@ -2890,10 +2888,12 @@
      * is updated. Any new network is enabled by default.
      *
      * For a new network, this function is used instead of a
-     * sequence of addNetwork(), enableNetwork() and saveConfiguration().
+     * sequence of addNetwork() and enableNetwork().
      *
      * For an existing network, it accomplishes the task of updateNetwork()
-     * and saveConfiguration()
+     *
+     * This API will cause reconnect if the crecdentials of the current active
+     * connection has been changed.
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
@@ -2912,7 +2912,6 @@
      * foreground user.
      *
      * This function is used instead of a sequence of removeNetwork()
-     * and saveConfiguration().
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
index bad5ce2..c4b24cf 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -42,9 +42,9 @@
             in ConfigRequest configRequest, boolean notifyOnIdentityChanged);
     void disconnect(int clientId, in IBinder binder);
 
-    void publish(int clientId, in PublishConfig publishConfig,
+    void publish(in String callingPackage, int clientId, in PublishConfig publishConfig,
             in IWifiAwareDiscoverySessionCallback callback);
-    void subscribe(int clientId, in SubscribeConfig subscribeConfig,
+    void subscribe(in String callingPackage, int clientId, in SubscribeConfig subscribeConfig,
             in IWifiAwareDiscoverySessionCallback callback);
 
     // session API
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 166da48..d57d152 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -301,7 +301,7 @@
         if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
 
         try {
-            mService.publish(clientId, publishConfig,
+            mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
                     new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
                             clientId));
         } catch (RemoteException e) {
@@ -334,7 +334,7 @@
         }
 
         try {
-            mService.subscribe(clientId, subscribeConfig,
+            mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
                     new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
                             clientId));
         } catch (RemoteException e) {
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 653fcff..9cab66a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -145,7 +145,7 @@
         // (2) publish - should succeed
         PublishConfig publishConfig = new PublishConfig.Builder().build();
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig), any());
+        inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig), any());
 
         // (3) disconnect
         session.close();
@@ -197,7 +197,7 @@
         // (4) subscribe: should succeed
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
         session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig), any());
+        inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig), any());
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
     }
@@ -280,7 +280,7 @@
 
         // (1) publish
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+        inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
                 sessionProxyCallback.capture());
 
         // (2) publish session created
@@ -372,7 +372,7 @@
 
         // (2) publish: successfully - then terminated
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+        inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
                 sessionProxyCallback.capture());
         sessionProxyCallback.getValue().onSessionStarted(sessionId);
         sessionProxyCallback.getValue().onSessionTerminated(0);
@@ -429,7 +429,7 @@
 
         // (1) subscribe
         session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+        inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
                 sessionProxyCallback.capture());
 
         // (2) subscribe session created
@@ -514,7 +514,7 @@
 
         // (2) subscribe: successfully - then terminated
         session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+        inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
                 sessionProxyCallback.capture());
         sessionProxyCallback.getValue().onSessionStarted(sessionId);
         sessionProxyCallback.getValue().onSessionTerminated(0);
@@ -912,7 +912,7 @@
 
         // (2) publish successfully
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+        inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
                 sessionProxyCallback.capture());
         sessionProxyCallback.getValue().onSessionStarted(sessionId);
         mMockLooper.dispatchAll();
@@ -1089,7 +1089,7 @@
 
         // (2) publish successfully
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+        inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
                 sessionProxyCallback.capture());
         sessionProxyCallback.getValue().onSessionStarted(sessionId);
         mMockLooper.dispatchAll();