Merge "Updating Batterystats version to 29."
diff --git a/Android.bp b/Android.bp
index e23d9c2..2dcbc92 100644
--- a/Android.bp
+++ b/Android.bp
@@ -473,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",
@@ -493,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",
diff --git a/Android.mk b/Android.mk
index 995630b..1f37326 100644
--- a/Android.mk
+++ b/Android.mk
@@ -277,9 +277,6 @@
 framework_base_android_test_base_src_files := \
   $(call all-java-files-under, test-base/src/junit)
 
-framework_base_android_test_mock_src_files := \
-  $(call all-java-files-under, test-mock/src/android/test/mock)
-
 framework_base_android_test_runner_src_files := \
   $(call all-java-files-under, test-runner/src/junit)
 
@@ -311,6 +308,7 @@
   $(files_to_check_apis) \
   $(call find-other-java-files,\
     test-base/src \
+    test-mock/src \
     test-runner/src)
 
 # These are relative to frameworks/base
@@ -331,7 +329,6 @@
 # These are relative to frameworks/base
 framework_docs_LOCAL_API_CHECK_SRC_FILES := \
   $(framework_base_android_test_base_src_files) \
-  $(framework_base_android_test_mock_src_files) \
   $(framework_base_android_test_runner_src_files) \
   $(files_to_check_apis) \
   $(common_src_files) \
@@ -359,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.
diff --git a/api/current.txt b/api/current.txt
index 60d314f..a508b00 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6368,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);
@@ -6468,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);
@@ -6648,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);
@@ -11091,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";
@@ -11413,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);
   }
 
 }
@@ -15484,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;
@@ -15582,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;
@@ -16125,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();
@@ -21356,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);
@@ -21391,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";
@@ -21643,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();
@@ -26226,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;
@@ -32269,6 +32294,7 @@
     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";
@@ -32278,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";
@@ -33786,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";
@@ -40680,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";
@@ -40740,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);
@@ -40832,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);
@@ -41116,12 +41287,15 @@
     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 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);
@@ -41219,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);
@@ -41520,299 +41705,6 @@
 
 }
 
-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.text {
 
   public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -48636,6 +48528,7 @@
     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();
diff --git a/api/removed.txt b/api/removed.txt
index 1a4e796..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 {
diff --git a/api/system-current.txt b/api/system-current.txt
index ec509ad..3e78167 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4144,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();
@@ -4160,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 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);
@@ -4177,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 {
@@ -4348,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 {
diff --git a/api/test-current.txt b/api/test-current.txt
index e64c320..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 {
@@ -439,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";
@@ -685,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 {
@@ -984,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/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 3e517bb..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
 
@@ -174,7 +174,8 @@
     tests/metrics/EventMetricProducer_test.cpp \
     tests/metrics/ValueMetricProducer_test.cpp \
     tests/metrics/GaugeMetricProducer_test.cpp \
-    tests/guardrail/StatsdStats_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/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index f6caca8..0c078d5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -114,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, mTimeBaseSec);
+
+    sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
 
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
@@ -125,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 7ec4e4b..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;
 
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/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 a6d2719..cb3f3d6 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -248,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");
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 01487f0..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);
 }
 
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 6ff6b87..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.
      *
@@ -133,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 bc12a78..9031ed0 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -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 220861d..1c8f422 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -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.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 6402633..47cca0e 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -128,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());
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 4a037ff..19d51e8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -45,8 +45,6 @@
 // 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 int atomTagId, const int64_t startTimeNs);
@@ -56,12 +54,6 @@
     // 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,
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 a5900f4..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, const long timeBaseSec) : mConfigKey(key) {
+MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
+                               const long timeBaseSec, sp<UidMap> uidMap)
+    : mConfigKey(key), mUidMap(uidMap) {
     mConfigValid =
             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 738adc9..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, const long timeBaseSec);
+    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 7efa6cd..40aed7b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -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 95c8a59..6050f43 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -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 36e25edf..5c43096 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -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/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 3018be1..21a9cf3 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -87,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) {
@@ -115,37 +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);
 
-    // Notify any interested producers that this app has updated
-    for (auto it : mSubscribers) {
-        it->notifyAppUpgrade(appName, uid, versionCode);
+        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();
+
+        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;
+            }
+        }
+        if (!found) {
+            // Otherwise, we need to add an app at this uid.
+            mMap.insert(make_pair(uid, AppData(appName, versionCode)));
+        }
+        getListenerListCopyLocked(&broadcastList);
     }
 
-    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();
-
-    auto range = mMap.equal_range(int(uid));
-    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;
-            return;
+    for (auto weakPtr : broadcastList) {
+        auto strongPtr = weakPtr.promote();
+        if (strongPtr != NULL) {
+            strongPtr->notifyAppUpgrade(appName, uid, versionCode);
         }
     }
-
-    // Otherwise, we need to add an app at this uid.
-    mMap.insert(make_pair(uid, AppData(appName, versionCode)));
 }
 
 void UidMap::ensureBytesUsedBelowLimit() {
@@ -174,42 +197,59 @@
     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);
 }
@@ -270,7 +310,7 @@
     return m;
 }
 
-size_t UidMap::getBytesUsed() {
+size_t UidMap::getBytesUsed() const {
     return mBytesUsed;
 }
 
@@ -316,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) {
@@ -350,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 487fdf9..a9aec94 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -72,14 +72,14 @@
 
     // 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);
@@ -102,7 +102,9 @@
     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;
@@ -117,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;
@@ -133,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.
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
deleted file mode 100644
index 7527a64..0000000
--- a/cmds/statsd/src/stats_util.cpp
+++ /dev/null
@@ -1,57 +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::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;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 8fd1ea8c..1cdf031 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include <sstream>
+#include "HashableDimensionKey.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "logd/LogReader.h"
 
 #include <unordered_map>
@@ -26,13 +27,11 @@
 namespace os {
 namespace statsd {
 
-#define DEFAULT_DIMENSION_KEY ""
+const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
 
 // Minimum bucket size in seconds
 const long kMinBucketSizeSec = 5 * 60;
 
-typedef std::string HashableDimensionKey;
-
 typedef std::map<std::string, HashableDimensionKey> ConditionKey;
 
 typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
@@ -67,13 +66,6 @@
 } EventKV;
 
 typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
-
-EventMetricData parse(log_msg msg);
-
-int getTagId(log_msg msg);
-
-std::string getHashableKey(std::vector<KeyValuePair> key);
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index a30b5f8..4729f6a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -222,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;
 
@@ -240,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 b1924ea..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(), 1000);
+    auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"),
+                                                           build_fake_config(), 1000, new UidMap());
     EXPECT_TRUE(metricsManager->isConfigValid());
 }
 
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index a5c8875..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(), 1000) {
+    MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
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/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 68b7dcb..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>
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/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/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 0a30ff8..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;
         }
 
@@ -548,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/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 de346f3..aaa6bf0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1768,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;
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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9816297..7e80ac7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2690,9 +2690,6 @@
      * @see #getPasswordBlacklistName
      * @see #isActivePasswordSufficient
      * @see #resetPasswordWithToken
-     *
-     * TODO(63578054): unhide for P
-     * @hide
      */
     public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
             @Nullable List<String> blacklist) {
@@ -2712,9 +2709,6 @@
      * @return the name of the blacklist or {@code null} if no blacklist is set
      *
      * @see #setPasswordBlacklist
-     *
-     * TODO(63578054): unhide for P
-     * @hide
      */
     public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
         try {
@@ -3213,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.
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 5b02c22..7cf19ee 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -95,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);
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/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 21e203b..77eb57f2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1558,22 +1558,31 @@
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
-        boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0;
         int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
         if (pkg.applicationInfo.isStaticSharedLibrary()) {
             // must use v2 signing scheme
             minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
         }
-        ApkSignatureVerifier.Result verified =
-                ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir);
+        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);
+                    "No APK Signature Scheme v2 signature in ephemeral package " + 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;
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/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/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/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6da6fb7..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.
@@ -291,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
@@ -323,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));
             }
         };
     }
@@ -513,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));
             }
         };
     }
@@ -564,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
@@ -576,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");
         }
@@ -585,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 {
@@ -599,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/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 d701550..196a3bc 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -505,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;
             }
@@ -517,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;
             }
@@ -527,46 +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() {
-        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     @TestApi
     public static long getLoopbackRxPackets() {
-        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     @TestApi
     public static long getLoopbackTxBytes() {
-        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES);
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     @TestApi
     public static long getLoopbackRxBytes() {
-        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES);
+        try {
+            return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -579,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();
+        }
     }
 
     /**
@@ -592,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();
+        }
     }
 
     /**
@@ -605,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();
+        }
     }
 
     /**
@@ -618,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();
+        }
     }
 
     /**
@@ -644,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;
         }
@@ -673,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;
         }
@@ -702,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;
         }
@@ -731,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;
         }
@@ -859,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 5faebff..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;
 
@@ -330,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.
@@ -510,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 {
@@ -648,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.
 
@@ -692,11 +729,32 @@
          */
         public static final int NUM_PROCESS_STATE = 7;
 
+        // Used in dump
         static final String[] PROCESS_STATE_NAMES = {
                 "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);
         public abstract Timer getProcessStateTimer(int state);
 
@@ -4005,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
@@ -5615,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/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/UserManager.java b/core/java/android/os/UserManager.java
index 75cbd57..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,
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 8632aad..bf145a0 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -266,7 +266,9 @@
         if (mUids == null) mUids = new int[2];
         mUids[0] = uid;
         mNames = null;
-        mChains.clear();
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
     /** @hide */
@@ -281,7 +283,9 @@
         }
         mUids[0] = uid;
         mNames[0] = name;
-        mChains.clear();
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/ims/internal/ISmsListener.aidl b/core/java/android/os/connectivity/CellularBatteryStats.aidl
similarity index 61%
copy from telephony/java/com/android/ims/internal/ISmsListener.aidl
copy to core/java/android/os/connectivity/CellularBatteryStats.aidl
index 1266f04..ca0a585 100644
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ b/core/java/android/os/connectivity/CellularBatteryStats.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 The Android Open Source Project
+ * 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.
@@ -14,14 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.ims.internal;
+package android.os.connectivity;
 
-/**
- * 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
+/** {@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/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/Settings.java b/core/java/android/provider/Settings.java
index d118219..c6deecc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5345,6 +5345,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
                 "autofill_user_data_max_user_data_size";
 
@@ -5355,6 +5356,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
                 "autofill_user_data_max_field_classification_size";
 
@@ -5364,6 +5366,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
                 "autofill_user_data_max_value_length";
 
@@ -5373,6 +5376,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
                 "autofill_user_data_min_value_length";
 
@@ -9567,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:
          *
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f88768b..2f372ce 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -423,19 +423,21 @@
     /**
      * 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.
+     * @return Map from alias to raw key material.
      */
-    public void recoverKeys(
+    public Map<String, byte[]> recoverKeys(
             @NonNull String sessionId,
             @NonNull byte[] recoveryKeyBlob,
             @NonNull List<KeyEntryRecoveryData> applicationKeys)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.recoverKeys(
+            return (Map<String, byte[]>) mBinder.recoverKeys(
                     sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -443,4 +445,21 @@
             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/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/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 75c1000..17b11a9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -58,37 +58,19 @@
     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.
      */
-    public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir)
+    public static Result verify(String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
         // first try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
         try {
-            Certificate[][] signerCerts;
-            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, VERSION_APK_SIGNATURE_SCHEME_V2);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
@@ -96,9 +78,6 @@
                 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,
@@ -109,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;
 
@@ -121,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) {
@@ -139,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();
@@ -153,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)) {
@@ -245,6 +235,43 @@
     }
 
     /**
+     * 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 {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 81b065c..2697454 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1009,6 +1009,21 @@
     }
 
     /**
+     * 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>.
      *
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/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/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index e3efad0..b3522ec 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,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";
 
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/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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6510a70..9ab16d8 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;
@@ -51,6 +52,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;
@@ -120,7 +122,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 171 + (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 +190,8 @@
     @VisibleForTesting
     protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
             new KernelUidCpuFreqTimeReader();
+    @VisibleForTesting
+    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
@@ -196,6 +200,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 +284,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 +441,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 +3773,7 @@
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
+            mExternalSync.scheduleCopyFromAllUidsCpuTimes();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
             if (updateOnBatteryTimeBase) {
@@ -3652,6 +3803,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 +3825,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) {
@@ -5899,6 +6058,11 @@
         LongSamplingCounterArray mCpuFreqTimeMs;
         LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
 
+        LongSamplingCounterArray[] mProcStateTimeMs;
+        LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+        IntArray mChildUids;
+
         /**
          * The statistics we have collected for this uid's wake locks.
          */
@@ -5984,40 +6148,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 +7230,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 +7435,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 +7709,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 +8026,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,25 +8968,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_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 if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
-                uidRunningState = PROCESS_STATE_TOP_SLEEPING;
-            } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
-                uidRunningState = PROCESS_STATE_HEAVY_WEIGHT;
-            } else {
-                uidRunningState = PROCESS_STATE_CACHED;
-            }
+            uidRunningState = BatteryStats.mapToInternalProcessState(procState);
 
             if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
                 return;
@@ -8701,6 +8980,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) {
@@ -11599,6 +11890,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;
@@ -11769,11 +12105,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) {
@@ -11792,6 +12140,9 @@
     public void removeUidStatsLocked(int uid) {
         mKernelUidCpuTimeReader.removeUid(uid);
         mKernelUidCpuFreqTimeReader.removeUid(uid);
+        if (mKernelSingleUidTimeReader != null) {
+            mKernelSingleUidTimeReader.removeUid(uid);
+        }
         mUidStats.remove(uid);
     }
 
@@ -12393,6 +12744,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);
@@ -12846,6 +13219,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/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/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 43536a5..77250eb 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -66,6 +66,7 @@
     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);
@@ -78,6 +79,6 @@
     byte[] startRecoverySession(in String sessionId,
             in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
             in List<KeyStoreRecoveryMetadata> secrets, int userId);
-    void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
+    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 297bae2..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",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d1868..6d7fe05 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,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);
@@ -1433,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/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index a51253f..59582ec 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -327,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;
 
@@ -413,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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e303927..86d2ee3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3505,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 -->
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-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-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-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/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-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-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-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-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-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-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-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/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-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-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-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/symbols.xml b/core/res/res/values/symbols.xml
index 7f71446..8e391d3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1574,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" />
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/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/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 7350db7..704b780 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -205,4 +205,17 @@
         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));
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 78d7785..eef9866 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -173,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,
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/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/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/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/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/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/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/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/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/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/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/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c4d9cf5..91ae448 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -872,7 +872,7 @@
 
         // From DevicePolicyAdmin
         final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
-                .getMaximumTimeToLockForUserAndProfiles(userId);
+                .getMaximumTimeToLock(null, userId);
 
         long timeout;
 
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/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1cbb440..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;
@@ -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++;
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/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/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 3361824..9ecf63d 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;
@@ -835,7 +834,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);
 
@@ -1095,7 +1094,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 7b85a6c..6d4a525 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -65,7 +65,6 @@
 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;
@@ -500,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: "
@@ -510,13 +511,13 @@
                 processNullResponseLocked(requestFlags);
                 return;
             }
-        }
 
-        final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
-        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);
@@ -1558,7 +1559,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>
      *
@@ -1566,14 +1567,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;
@@ -1728,7 +1725,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/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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7dfde56..bc25a32 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19900,16 +19900,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;
             }
         }
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/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..b920b57 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);
         }
@@ -1237,8 +1242,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 +1306,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 +1409,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 +1429,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 +1453,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/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/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 2d7a6ad..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;
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/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 26edf62..dc95d41 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -482,7 +482,7 @@
 
                 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.");
@@ -642,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;
         }
 
@@ -817,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);
             }
@@ -828,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 7a57dd3..c356b63 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -215,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 3bd6446..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
@@ -1833,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();
     }
 
     /**
@@ -1869,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 0481dab..9275497 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -38,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;
@@ -90,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;
@@ -100,6 +102,7 @@
 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;
@@ -891,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
@@ -1234,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,
@@ -1741,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);
@@ -1931,14 +1951,14 @@
 
     @Override
     public void initRecoveryService(@NonNull String rootCertificateAlias,
-            @NonNull byte[] signedPublicKeyList, int userId)
+            @NonNull byte[] signedPublicKeyList, @UserIdInt int userId)
             throws RemoteException {
         mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias,
                 signedPublicKeyList, userId);
     }
 
     @Override
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId)
             throws RemoteException {
         return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
     }
@@ -1953,13 +1973,14 @@
     }
 
     @Override
-    public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+    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, int userId) throws RemoteException {
+            int status, @UserIdInt int userId) throws RemoteException {
         mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
     }
 
@@ -1969,25 +1990,24 @@
 
     @Override
     public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
-            int[] secretTypes, int userId) throws RemoteException {
+            int[] secretTypes, @UserIdInt int userId) throws RemoteException {
         mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId);
     }
 
     @Override
-    public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+    public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
         return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId);
 
     }
 
     @Override
-    public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+    public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
         throw new SecurityException("Not implemented");
     }
 
     @Override
     public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret,
-            int userId)
-            throws RemoteException {
+            @UserIdInt int userId) throws RemoteException {
         mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId);
     }
 
@@ -1995,17 +2015,22 @@
     public byte[] startRecoverySession(@NonNull String sessionId,
             @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
             @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets,
-            int userId) throws RemoteException {
+            @UserIdInt int userId) throws RemoteException {
         return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
                 vaultParams, vaultChallenge, secrets, userId);
     }
 
     @Override
-    public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys, int userId)
+    public Map<String, byte[]> recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId)
             throws RemoteException {
-        mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys,
-                userId);
+        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[] {
@@ -2438,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/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 25428e7..4597fad 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -54,6 +54,8 @@
             "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);
 
@@ -74,7 +76,7 @@
      *
      * @hide
      */
-    public byte[] thmEncryptRecoveryKey(
+    public static byte[] thmEncryptRecoveryKey(
             PublicKey publicKey,
             byte[] lockScreenHash,
             byte[] vaultParams,
@@ -116,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,
@@ -203,6 +206,28 @@
     }
 
     /**
+     * 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.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 24f3f65..95f5cb7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -332,4 +332,17 @@
         }
         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 d50a736..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,22 +16,15 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.util.Log;
-
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
 import java.security.InvalidKeyException;
-import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 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.
@@ -43,8 +36,6 @@
  * @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;
@@ -62,20 +53,16 @@
         // 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, database, new AndroidKeyStoreFactory.Impl());
+        return new RecoverableKeyGenerator(keyGenerator, database);
     }
 
     private final KeyGenerator mKeyGenerator;
     private final RecoverableKeyStoreDb mDatabase;
-    private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
 
     private RecoverableKeyGenerator(
             KeyGenerator keyGenerator,
-            RecoverableKeyStoreDb recoverableKeyStoreDb,
-            AndroidKeyStoreFactory androidKeyStoreFactory) {
+            RecoverableKeyStoreDb recoverableKeyStoreDb) {
         mKeyGenerator = keyGenerator;
-        mAndroidKeyStoreFactory = androidKeyStoreFactory;
         mDatabase = recoverableKeyStoreDb;
     }
 
@@ -87,69 +74,31 @@
      * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
      *
      * @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 AndroidKeyStore.
+     * @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 AndroidKeyStore or the database.
+     *     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.
      *
      * @hide
      */
-    public void generateAndStoreKey(PlatformEncryptionKey platformKey, int uid, String alias)
+    public byte[] generateAndStoreKey(
+            PlatformEncryptionKey platformKey, int userId, int uid, String alias)
             throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
         mKeyGenerator.init(KEY_SIZE_BITS);
         SecretKey key = mKeyGenerator.generateKey();
 
-        KeyStoreProxy keyStore;
-
-        try {
-            keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
-        } catch (NoSuchProviderException e) {
-            throw new RecoverableKeyStorageException(
-                    "Impossible: AndroidKeyStore provider did not exist", e);
-        } catch (KeyStoreException e) {
-            throw new RecoverableKeyStorageException(
-                    "Could not load AndroidKeyStore for " + uid, e);
-        }
-
-        try {
-            keyStore.setEntry(
-                    alias,
-                    new KeyStore.SecretKeyEntry(key),
-                    new KeyProtection.Builder(
-                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                            .build());
-        } catch (KeyStoreException e) {
-            throw new RecoverableKeyStorageException(
-                    "Failed to load (%d, %s) into AndroidKeyStore", e);
-        }
-
         WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
-        try {
-            // Keep raw key material in memory for minimum possible time.
-            key.destroy();
-        } catch (DestroyFailedException e) {
-            Log.w(TAG, "Could not destroy SecretKey.");
-        }
-        long result = mDatabase.insertKey(uid, alias, wrappedKey);
+        long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
 
         if (result == RESULT_CANNOT_INSERT_ROW) {
-            // Attempt to clean up
-            try {
-                keyStore.deleteEntry(alias);
-            } catch (KeyStoreException e) {
-                Log.e(TAG, String.format(Locale.US,
-                        "Could not delete recoverable key (%d, %s) from "
-                                + "AndroidKeyStore after error writing to database.", uid, alias),
-                        e);
-            }
-
             throw new RecoverableKeyStorageException(
                     String.format(
                             Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
         }
+
+        return key.getEncoded();
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 48f4626..7fba57c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -29,18 +29,30 @@
 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
@@ -49,13 +61,20 @@
  * @hide
  */
 public class RecoverableKeyStoreManager {
-    private static final String TAG = "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.
@@ -68,7 +87,9 @@
             mInstance = new RecoverableKeyStoreManager(
                     mContext.getApplicationContext(),
                     db,
-                    new RecoverySessionStorage());
+                    new RecoverySessionStorage(),
+                    Executors.newSingleThreadExecutor(),
+                    ListenersStorage.getInstance());
         }
         return mInstance;
     }
@@ -77,18 +98,40 @@
     RecoverableKeyStoreManager(
             Context context,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
-            RecoverySessionStorage recoverySessionStorage) {
+            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 int initRecoveryService(
+    public void initRecoveryService(
             @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        // TODO open /system/etc/security/... cert file
-        throw new UnsupportedOperationException();
+        // 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);
     }
 
     /**
@@ -123,7 +166,8 @@
     public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        final int recoveryAgentUid = Binder.getCallingUid();
+        mListenersStorage.setSnapshotListener(recoveryAgentUid, intent);
     }
 
     /**
@@ -143,18 +187,37 @@
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * 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();
-        throw new UnsupportedOperationException();
+        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 keys {@code packageName}.
+     * Gets recovery status for caller or other application {@code packageName}.
+     * @param packageName which recoverable keys statuses will be returned.
      *
-     * @param packageName which recoverable keys statuses will be returned
-     * @return Map from KeyStore alias to recovery status
+     * @return {@code Map} from KeyStore alias to recovery status.
      */
     public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
             throws RemoteException {
@@ -162,7 +225,7 @@
         // If caller is a recovery agent it can check statuses for other packages, but
         // only for recoverable keys it manages.
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
     }
 
     /**
@@ -218,7 +281,7 @@
      * @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. Should have a single element. TODO: why is this a list?
+     * @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
@@ -241,7 +304,8 @@
         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
         byte[] kfHash = secrets.get(0).getSecret();
         mRecoverySessionStorage.add(
-                userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant));
+                userId,
+                new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
 
         try {
             byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
@@ -268,29 +332,168 @@
         }
     }
 
-    public void recoverKeys(
+    /**
+     * 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[] recoveryKeyBlob,
+            @NonNull byte[] encryptedRecoveryKey,
             @NonNull List<KeyEntryRecoveryData> applicationKeys,
-            int userId)
+            int uid)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+
+        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);
+        }
     }
 
-    /** This function can only be used inside LockSettingsService. */
+    /**
+     * 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(
-            @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
-            String unencryptedPassword,
-            int userId) {
-        // TODO: compute SHA256 or Argon2id depending on secret type.
-        throw new UnsupportedOperationException();
+            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 unencryptedPassword,
+            @Nullable String credential,
             int userId) {
         throw new UnsupportedOperationException();
     }
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 d8a2d31..801d4de 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -230,7 +230,7 @@
      * @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
+     *     cannot be validated, or if the payload is not a valid SecureBox V2 payload.
      * @hide
      */
     public static byte[] decrypt(
@@ -244,12 +244,14 @@
             throw new IllegalArgumentException("Both the private key and shared secret are empty");
         }
         header = emptyByteArrayIfNull(header);
-        encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+        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 IllegalArgumentException("The payload was not encrypted by SecureBox v2");
+            throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
         }
 
         byte[] senderPublicKeyBytes;
@@ -271,12 +273,13 @@
         return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
     }
 
-    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
+            throws AEADBadTagException {
         byte[] output = new byte[length];
         try {
             buffer.get(output);
         } catch (BufferUnderflowException ex) {
-            throw new IllegalArgumentException("The encrypted payload is too short");
+            throw new AEADBadTagException("The encrypted payload is too short");
         }
         return output;
     }
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 dfa173c..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;
@@ -45,6 +46,7 @@
     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;
 
@@ -94,7 +96,25 @@
         return new WrappedKey(
                 /*nonce=*/ cipher.getIV(),
                 /*keyMaterial=*/ encryptedKeyMaterial,
-                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId());
+                /*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;
     }
 
     /**
@@ -103,13 +123,16 @@
      * @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, int platformKeyGenerationId) {
+    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId,
+            int recoveryStatus) {
         mNonce = nonce;
         mKeyMaterial = keyMaterial;
         mPlatformKeyGenerationId = platformKeyGenerationId;
+        mRecoveryStatus = recoveryStatus;
     }
 
     /**
@@ -130,7 +153,6 @@
         return mKeyMaterial;
     }
 
-
     /**
      * Returns the generation ID of the platform key, with which this key was wrapped.
      *
@@ -141,6 +163,15 @@
     }
 
     /**
+     * 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.
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
index 3644d36..8d1aed2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -16,18 +16,30 @@
 
 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.RecoveryServicePublicKeyEntry;
 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;
 
@@ -39,6 +51,7 @@
 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;
 
@@ -61,6 +74,7 @@
     /**
      * 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.
@@ -68,15 +82,17 @@
      *
      * @hide
      */
-    public long insertKey(int uid, String alias, WrappedKey wrappedKey) {
+    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, -1);
+        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);
     }
 
@@ -91,7 +107,8 @@
                 KeysEntry._ID,
                 KeysEntry.COLUMN_NAME_NONCE,
                 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
-                KeysEntry.COLUMN_NAME_GENERATION_ID};
+                KeysEntry.COLUMN_NAME_GENERATION_ID,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
         String selection =
                 KeysEntry.COLUMN_NAME_UID + " = ? AND "
                 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
@@ -125,32 +142,95 @@
                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
             int generationId = cursor.getInt(
                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
-            return new WrappedKey(nonce, keyMaterial, generationId);
+            int recoveryStatus = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+            return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus);
         }
     }
 
     /**
-     * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}.
+     * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
      *
-     * @param uid User id of the profile to which all the keys are associated.
+     * @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 uid, int platformKeyGenerationId) {
+    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_ALIAS,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
         String selection =
-                KeysEntry.COLUMN_NAME_UID + " = ? AND "
+                KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
                 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
         String[] selectionArguments = {
-                Integer.toString(uid), Integer.toString(platformKeyGenerationId) };
+                Integer.toString(userId), Integer.toString(platformKeyGenerationId) };
 
         try (
             Cursor cursor = db.query(
@@ -170,7 +250,10 @@
                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
                 String alias = cursor.getString(
                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
-                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId));
+                int recoveryStatus = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId,
+                        recoveryStatus));
             }
             return keys;
         }
@@ -223,6 +306,88 @@
     }
 
     /**
+     * Inserts or 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(RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_UID, uid);
+        values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
+        return db.replace(RecoveryServicePublicKeyEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+                values);
+    }
+
+    /**
+     * 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 = {
+                RecoveryServicePublicKeyEntry._ID,
+                RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID,
+                RecoveryServicePublicKeyEntry.COLUMN_NAME_UID,
+                RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY};
+        String selection =
+                RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = { Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+                Cursor cursor = db.query(
+                        RecoveryServicePublicKeyEntry.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();
+            byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
+                    RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY));
+            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;
+            }
+        }
+    }
+
+    /**
      * Closes all open connections to the database.
      */
     public void close() {
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
index b6c168f..e575687 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -29,6 +29,11 @@
         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";
@@ -57,6 +62,11 @@
          * 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";
     }
 
     /**
@@ -76,4 +86,26 @@
          */
         static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
     }
+
+    /**
+     * Table holding public keys of the recovery service.
+     */
+    static class RecoveryServicePublicKeyEntry implements BaseColumns {
+        static final String TABLE_NAME = "recovery_service_public_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 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";
+    }
 }
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
index 6868203..76106d9 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -5,6 +5,7 @@
 import android.database.sqlite.SQLiteOpenHelper;
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServicePublicKeyEntry;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
 
 /**
@@ -17,12 +18,14 @@
     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 + "))";
 
@@ -32,12 +35,25 @@
                     + 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 " + RecoveryServicePublicKeyEntry.TABLE_NAME + " ("
+                    + RecoveryServicePublicKeyEntry._ID + " INTEGER PRIMARY KEY,"
+                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+                    + "UNIQUE("
+                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID  + ","
+                    + RecoveryServicePublicKeyEntry.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 " + RecoveryServicePublicKeyEntry.TABLE_NAME;
+
     RecoverableKeyStoreDbHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }
@@ -46,12 +62,14 @@
     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
index bc56ae1..f7633e4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -129,15 +129,17 @@
     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) {
-            this.mLskfHash = lskfHash;
-            this.mSessionId = sessionId;
-            this.mKeyClaimant = keyClaimant;
+        public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) {
+            mLskfHash = lskfHash;
+            mSessionId = sessionId;
+            mKeyClaimant = keyClaimant;
+            mVaultParams = vaultParams;
         }
 
         /**
@@ -160,6 +162,15 @@
         }
 
         /**
+         * 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
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/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/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..0b590bc 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);
@@ -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/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/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c2cbced..4a74e29 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -44,17 +44,17 @@
 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;
@@ -64,6 +64,7 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 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
@@ -1605,6 +1613,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 +1852,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() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9fc9f3c..58a5ca4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2706,6 +2706,12 @@
         }
     }
 
+    public void overridePendingAppTransitionStartCrossProfileApps() {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionStartCrossProfileApps();
+        }
+    }
+
     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
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..79f482c6 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_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/core/jni/android_net_TrafficStats.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
similarity index 79%
rename from core/jni/android_net_TrafficStats.cpp
rename to services/core/jni/com_android_server_net_NetworkStatsService.cpp
index d0c237d..8de24e5 100644
--- a/core/jni/android_net_TrafficStats.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "TrafficStats"
+#define LOG_TAG "NetworkStatsNative"
 
 #include <dirent.h>
 #include <errno.h>
@@ -191,8 +191,24 @@
     {"nativeGetUidStat", "(II)J", (void*) getUidStat},
 };
 
-int register_android_net_TrafficStats(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+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/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 387818b..e5351b4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4674,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
@@ -4734,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;
             }
         }
@@ -9451,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;
                 }
             }
@@ -9913,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/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/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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 4779474..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.
     }
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 8cb0459..4232c44 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -181,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/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 ac3abed..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
@@ -39,6 +48,15 @@
     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 {
@@ -97,6 +115,233 @@
                         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);
     }
@@ -106,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/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 3012931..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,13 +16,12 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static junit.framework.Assert.fail;
+import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
-import android.security.keystore.AndroidKeyStoreProvider;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
@@ -32,8 +31,6 @@
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
-import com.google.common.collect.ImmutableMap;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,13 +38,10 @@
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
 import java.security.KeyStore;
-import java.util.Arrays;
 
 import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
 import javax.crypto.spec.GCMParameterSpec;
 
 @SmallTest
@@ -57,13 +51,13 @@
     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 String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
-    private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+    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;
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
 
     private PlatformEncryptionKey mPlatformKey;
     private PlatformDecryptionKey mDecryptKey;
@@ -94,63 +88,33 @@
     }
 
     @Test
-    public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        assertTrue(keyStore.containsAlias(TEST_ALIAS));
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
-            throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
-        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
-        cipher.init(Cipher.ENCRYPT_MODE, key);
-        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
-        Arrays.fill(nonce, (byte) 0);
-        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
-        Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
-
-        try {
-            cipher.init(Cipher.ENCRYPT_MODE, key);
-            fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
-        } catch (InvalidKeyException e) {
-            // expected
-        }
-    }
-
-    @Test
     public void generateAndStoreKey_storesWrappedKey() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
+        mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
         WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
-        SecretKey unwrappedKey = WrappedKey
-                .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
-                .get(TEST_ALIAS);
+        assertNotNull(wrappedKey);
+    }
 
-        // key and unwrappedKey should be equivalent. let's check!
-        byte[] plaintext = getUtf8Bytes("dtianpos");
-        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
-        cipher.init(Cipher.ENCRYPT_MODE, key);
-        byte[] encrypted = cipher.doFinal(plaintext);
-        byte[] iv = cipher.getIV();
-        cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
-        byte[] decrypted = cipher.doFinal(encrypted);
-        assertArrayEquals(decrypted, plaintext);
+    @Test
+    public void generateAndStoreKey_returnsRawMaterialOfCorrectLength() throws Exception {
+        byte[] rawKey = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+        assertEquals(KEY_SIZE_BYTES, rawKey.length);
+    }
+
+    @Test
+    public void generateAndStoreKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception {
+        byte[] rawMaterial = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+        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 {
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
index 35b18b1..419d7b4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -19,16 +19,27 @@
 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;
@@ -39,6 +50,7 @@
 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;
@@ -49,12 +61,21 @@
 
 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,
@@ -76,8 +97,18 @@
     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;
@@ -91,11 +122,19 @@
         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(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
+
         mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
                 mMockContext,
                 mRecoverableKeyStoreDb,
-                mRecoverySessionStorage);
+                mRecoverySessionStorage,
+                Executors.newSingleThreadExecutor(),
+                mMockListenersStorage);
     }
 
     @After
@@ -105,22 +144,59 @@
     }
 
     @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 generateAndStoreKey_storesTheWrappedFormOfTheReturnedBytes() throws Exception {
+        int uid = Binder.getCallingUid();
+
+        byte[] rawKey = mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+
+        WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS);
+        PlatformEncryptionKey encryptionKey = PlatformKeyManager.getInstance(
+                mMockContext,
+                mRecoverableKeyStoreDb,
+                Binder.getCallingUserHandle().getIdentifier())
+                .getEncryptKey();
+        Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
+        cipher.init(
+                Cipher.ENCRYPT_MODE,
+                encryptionKey.getKey(),
+                new GCMParameterSpec(GCM_TAG_SIZE_BITS, wrappedKey.getNonce()));
+        byte[] encryptedBytes = cipher.update(rawKey);
+        assertArrayEquals(encryptedBytes, wrappedKey.getKeyMaterial());
+    }
+
+    @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)),
+                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());
+        verify(mMockContext, times(1))
+                .enforceCallingOrSelfPermission(
+                        eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
     }
 
     @Test
@@ -130,16 +206,17 @@
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
-                        TYPE_LOCKSCREEN,
-                        TYPE_PASSWORD,
-                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
-                        TEST_SECRET)),
+                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);
+        RecoverySessionStorage.Entry entry =
+                mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID);
         assertArrayEquals(TEST_SECRET, entry.getLskfHash());
         assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
     }
@@ -154,9 +231,9 @@
                     TEST_VAULT_CHALLENGE,
                     ImmutableList.of(),
                     TEST_USER_ID);
+            fail("should have thrown");
         } catch (RemoteException e) {
-            assertEquals("Only a single KeyStoreRecoveryMetadata is supported",
-                    e.getMessage());
+            assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
         }
     }
 
@@ -168,19 +245,234 @@
                     getUtf8Bytes("0"),
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
-                    ImmutableList.of(new KeyStoreRecoveryMetadata(
-                            TYPE_LOCKSCREEN,
-                            TYPE_PASSWORD,
-                            KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
-                            TEST_SECRET)),
+                    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",
+            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
index 72b69f0..35ec23b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -274,9 +274,9 @@
 
     @Test
     public void decrypt_nullEncryptedPayload() throws Exception {
-        IllegalArgumentException expected =
+        NullPointerException expected =
                 expectThrows(
-                        IllegalArgumentException.class,
+                        NullPointerException.class,
                         () ->
                                 SecureBox.decrypt(
                                         THM_PRIVATE_KEY,
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
index d5ad959..e172c49 100644
--- 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
@@ -16,6 +16,7 @@
 
 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;
@@ -27,6 +28,7 @@
 import org.junit.runner.RunWith;
 
 import android.content.Context;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -34,6 +36,9 @@
 
 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
@@ -60,22 +65,23 @@
     @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, alias, oldWrappedKey);
+                userId, uid, alias, oldWrappedKey);
         byte[] nonce = getUtf8Bytes("nonce2");
         byte[] keyMaterial = getUtf8Bytes("keymaterial2");
         WrappedKey newWrappedKey = new WrappedKey(
                 nonce, keyMaterial, /*platformKeyGenerationId=*/2);
 
         mRecoverableKeyStoreDb.insertKey(
-                userId, alias, newWrappedKey);
+                userId, uid, alias, newWrappedKey);
 
-        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
         assertArrayEquals(nonce, retrievedKey.getNonce());
         assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
         assertEquals(2, retrievedKey.getPlatformKeyGenerationId());
@@ -83,6 +89,7 @@
 
     @Test
     public void insertKey_allowsTwoUidsToHaveSameAlias() {
+        int userId = 6;
         String alias = "pcoulton";
         WrappedKey key1 = new WrappedKey(
                 getUtf8Bytes("nonce1"),
@@ -93,8 +100,8 @@
                 getUtf8Bytes("key2"),
                 /*platformKeyGenerationId=*/ 1);
 
-        mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1);
-        mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2);
+        mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 1, alias, key1);
+        mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 2, alias, key2);
 
         assertArrayEquals(
                 getUtf8Bytes("nonce1"),
@@ -115,29 +122,33 @@
     @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);
-        mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey);
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, 120);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
 
-        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+        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, alias, wrappedKey);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
 
         Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
 
@@ -152,12 +163,13 @@
     @Test
     public void getAllKeys_doesNotReturnKeysWithBadGenerationId() {
         int userId = 12;
+        int uid = 6000;
         WrappedKey wrappedKey = new WrappedKey(
                 getUtf8Bytes("nonce"),
                 getUtf8Bytes("keymaterial"),
                 /*platformKeyGenerationId=*/ 5);
         mRecoverableKeyStoreDb.insertKey(
-                userId, /*alias=*/ "test", wrappedKey);
+                userId, uid, /*alias=*/ "test", wrappedKey);
 
         Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
                 userId, /*generationId=*/ 7);
@@ -168,10 +180,11 @@
     @Test
     public void getAllKeys_doesNotReturnKeysWithBadUserId() {
         int generationId = 12;
+        int uid = 10009;
         WrappedKey wrappedKey = new WrappedKey(
                 getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId);
         mRecoverableKeyStoreDb.insertKey(
-                /*userId=*/ 1, /*alias=*/ "test", wrappedKey);
+                /*userId=*/ 1, uid, /*alias=*/ "test", wrappedKey);
 
         Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
                 /*userId=*/ 2, generationId);
@@ -202,7 +215,149 @@
         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 setRecoveryServicePublicKey_allowsTwoUsersToHaveTheSameUidAndKey()
+            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 setRecoveryServicePublicKey_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 getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertNull(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid));
+    }
+
+    @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);
+    }
+
     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
index 6aeff28..6f93fe4 100644
--- 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
@@ -37,6 +37,7 @@
     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() {
@@ -47,7 +48,7 @@
     public void size_incrementsAfterAdd() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
 
         assertEquals(1, storage.size());
     }
@@ -56,7 +57,7 @@
     public void size_decrementsAfterRemove() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
         storage.remove(TEST_USER_ID);
 
         assertEquals(0, storage.size());
@@ -66,7 +67,7 @@
     public void remove_overwritesLskfHashMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.remove(TEST_USER_ID);
@@ -78,7 +79,7 @@
     public void remove_overwritesKeyClaimantMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.remove(TEST_USER_ID);
@@ -90,7 +91,7 @@
     public void destroy_overwritesLskfHashMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.destroy();
@@ -102,7 +103,7 @@
     public void destroy_overwritesKeyClaimantMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.destroy();
@@ -126,6 +127,10 @@
         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/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/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/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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9e77992..af5b190 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3155,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;
 
     /**
@@ -3165,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;
 
     /**
@@ -3180,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;
 
     /**
@@ -3187,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.
@@ -3202,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.
@@ -3228,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)
@@ -3241,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
@@ -3250,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)
@@ -3265,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
@@ -3275,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)
@@ -4849,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) {
@@ -4905,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) {
@@ -4926,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) {
@@ -4959,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) {
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/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/com/android/ims/internal/ISmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
similarity index 65%
rename from telephony/java/com/android/ims/internal/ISmsListener.aidl
rename to telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
index 1266f04..468629a 100644
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.ims.internal;
+package android.telephony.ims.internal.aidl;
 
 /**
- * See SmsFeature for more information.
+ * See MMTelFeature 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);
+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/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8ea53a5..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);
 
     /**
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/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644
index 0000000..12568a0
--- /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/telephony/java/com/android/ims/internal/ISmsListener.aidl b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
similarity index 61%
copy from telephony/java/com/android/ims/internal/ISmsListener.aidl
copy to tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
index 1266f04..4464e87 100644
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 The Android Open Source Project
+ * 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.
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.ims.internal;
+package com.android.benchmark;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
 
 /**
- * See SmsFeature for more information.
- * {@hide}
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
  */
-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
+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/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/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/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();