Merge "Import translations. DO NOT MERGE"
diff --git a/Android.bp b/Android.bp
index 0210bb3..fb018a5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,7 @@
         "core/java/android/app/backup/IRestoreObserver.aidl",
         "core/java/android/app/backup/IRestoreSession.aidl",
         "core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
+        "core/java/android/app/role/IOnRoleHoldersChangedListener.aidl",
         "core/java/android/app/role/IRoleManager.aidl",
         "core/java/android/app/role/IRoleManagerCallback.aidl",
         "core/java/android/app/slice/ISliceManager.aidl",
@@ -784,18 +785,6 @@
     ],
 }
 
-// A host library containing the inspector annotations for inspector-annotation-processor.
-java_library_host {
-    name: "inspector-annotation",
-    srcs: [
-        "core/java/android/view/inspector/InspectableNodeName.java",
-        "core/java/android/view/inspector/InspectableProperty.java",
-        // Needed for the ResourceId.ID_NULL constant
-        "core/java/android/content/res/ResourceId.java",
-        "core/java/android/annotation/AnyRes.java",
-    ],
-}
-
 // A host library including just UnsupportedAppUsage.java so that the annotation
 // processor can also use this annotation.
 java_library_host {
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
index 5be0cb0..99e4ba1 100644
--- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
@@ -39,6 +39,7 @@
 @LargeTest
 public class BinderCallsStatsPerfTest {
     private static final int DEFAULT_BUCKET_SIZE = 1000;
+    private static final int WORKSOURCE_UID = 1;
     static class FakeCpuTimeBinderCallsStats extends BinderCallsStats {
         private int mTimeMs;
 
@@ -117,8 +118,8 @@
         Binder b = new Binder();
         while (state.keepRunning()) {
             for (int i = 0; i < 10000; i++) {
-                CallSession s = mBinderCallsStats.callStarted(b, i % maxBucketSize);
-                mBinderCallsStats.callEnded(s, 0, 0);
+                CallSession s = mBinderCallsStats.callStarted(b, i % maxBucketSize, WORKSOURCE_UID);
+                mBinderCallsStats.callEnded(s, 0, 0, WORKSOURCE_UID);
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 931b9fa..b8a37d4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9402,6 +9402,7 @@
     method public static void cancelSync(android.content.SyncRequest);
     method public final android.net.Uri canonicalize(android.net.Uri);
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+    method public android.os.Bundle getCache(android.net.Uri);
     method public static deprecated android.content.SyncInfo getCurrentSync();
     method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
     method public static int getIsSyncable(android.accounts.Account, java.lang.String);
@@ -9432,6 +9433,7 @@
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
+    method public void putCache(android.net.Uri, android.os.Bundle);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
@@ -10236,6 +10238,7 @@
     field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
     field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
     field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID";
+    field public static final java.lang.String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
     field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";
     field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
     field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC";
@@ -23394,6 +23397,7 @@
     method public int getStreamType();
     method public boolean getTimestamp(android.media.AudioTimestamp);
     method public int getUnderrunCount();
+    method public static boolean isDirectPlaybackSupported(android.media.AudioFormat, android.media.AudioAttributes);
     method public void pause() throws java.lang.IllegalStateException;
     method public void play() throws java.lang.IllegalStateException;
     method public void registerStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback);
@@ -24751,6 +24755,7 @@
     field public static final java.lang.String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
     field public static final java.lang.String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
     field public static final java.lang.String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
+    field public static final java.lang.String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc";
     field public static final java.lang.String MIMETYPE_AUDIO_FLAC = "audio/flac";
     field public static final java.lang.String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
     field public static final java.lang.String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
@@ -37449,6 +37454,8 @@
     field public static final java.lang.String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
     field public static final java.lang.String EXTRA_PROMPT = "android.provider.extra.PROMPT";
     field public static final java.lang.String METADATA_EXIF = "android:documentExif";
+    field public static final java.lang.String METADATA_TREE_COUNT = "android:metadataTreeCount";
+    field public static final java.lang.String METADATA_TREE_SIZE = "android:metadataTreeSize";
     field public static final java.lang.String METADATA_TYPES = "android:documentMetadataTypes";
     field public static final java.lang.String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
     field public static final java.lang.String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name";
@@ -37825,6 +37832,7 @@
 
   public static final class MediaStore.Downloads implements android.provider.MediaStore.DownloadColumns {
     method public static android.net.Uri getContentUri(java.lang.String);
+    field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/download";
     field public static final android.net.Uri EXTERNAL_CONTENT_URI;
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
@@ -40564,6 +40572,7 @@
   public final class UserData implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.String getFieldClassificationAlgorithm();
+    method public java.lang.String getFieldClassificationAlgorithmForCategory(java.lang.String);
     method public java.lang.String getId();
     method public static int getMaxCategoryCount();
     method public static int getMaxFieldClassificationIdsSize();
@@ -40579,6 +40588,7 @@
     method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
     method public android.service.autofill.UserData build();
     method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle);
+    method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithmForCategory(java.lang.String, java.lang.String, android.os.Bundle);
   }
 
   public abstract interface Validator {
@@ -42328,8 +42338,9 @@
     method public void swapConference();
     method public void unhold();
     method public void unregisterCallback(android.telecom.Call.Callback);
-    field public static final java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
+    field public static final deprecated java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
     field public static final java.lang.String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
+    field public static final java.lang.String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_CONNECTING = 9; // 0x9
     field public static final int STATE_DIALING = 1; // 0x1
@@ -42900,6 +42911,20 @@
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountHandle> CREATOR;
   }
 
+  public final class PhoneAccountSuggestion implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public int getReason();
+    method public boolean shouldAutoSelect();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountSuggestion> CREATOR;
+    field public static final int REASON_FREQUENT = 2; // 0x2
+    field public static final int REASON_INTRA_CARRIER = 1; // 0x1
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_OTHER = 4; // 0x4
+    field public static final int REASON_USER_SET = 3; // 0x3
+  }
+
   public final class RemoteConference {
     method public void disconnect();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
diff --git a/api/system-current.txt b/api/system-current.txt
index 29089b3..cd01cd5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -91,6 +91,7 @@
     field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
     field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
     field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
+    field public static final java.lang.String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
     field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
     field public static final java.lang.String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL";
     field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
@@ -135,6 +136,7 @@
     field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
     field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
     field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
+    field public static final java.lang.String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS";
     field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
     field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
@@ -172,6 +174,7 @@
     field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
     field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
     field public static final java.lang.String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
+    field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final java.lang.String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
     field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
@@ -598,6 +601,8 @@
     method public boolean isDeviceManaged();
     method public boolean isDeviceProvisioned();
     method public boolean isDeviceProvisioningConfigApplied();
+    method public boolean isManagedKiosk();
+    method public boolean isUnattendedManagedKiosk();
     method public void notifyPendingSystemUpdate(long);
     method public void notifyPendingSystemUpdate(long, boolean);
     method public boolean packageHasActiveAdmins(java.lang.String);
@@ -884,12 +889,18 @@
 
 package android.app.role {
 
+  public abstract interface OnRoleHoldersChangedListener {
+    method public abstract void onRoleHoldersChanged(java.lang.String, android.os.UserHandle);
+  }
+
   public final class RoleManager {
+    method public void addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle);
     method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
     method public boolean addRoleHolderFromController(java.lang.String, java.lang.String);
     method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
     method public java.util.List<java.lang.String> getRoleHolders(java.lang.String);
     method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle);
+    method public void removeOnRoleHoldersChangedListenerAsUser(android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle);
     method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
     method public boolean removeRoleHolderFromController(java.lang.String, java.lang.String);
     method public void setRoleNamesFromController(java.util.List<java.lang.String>);
@@ -1043,6 +1054,10 @@
     method public void setDetectNotResponding(long);
   }
 
+  public abstract class ContentResolver {
+    method public android.graphics.drawable.Drawable getTypeDrawable(java.lang.String);
+  }
+
   public abstract class Context {
     method public boolean bindServiceAsUser(android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
@@ -1141,6 +1156,10 @@
     field public int targetSandboxVersion;
   }
 
+  public class CrossProfileApps {
+    method public void startAnyActivity(android.content.ComponentName, android.os.UserHandle);
+  }
+
   public final class InstantAppInfo implements android.os.Parcelable {
     ctor public InstantAppInfo(android.content.pm.ApplicationInfo, java.lang.String[], java.lang.String[]);
     ctor public InstantAppInfo(java.lang.String, java.lang.CharSequence, java.lang.String[], java.lang.String[]);
@@ -1354,6 +1373,7 @@
 
   public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     field public static final int FLAG_REMOVED = 2; // 0x2
+    field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
@@ -4428,6 +4448,7 @@
     ctor public RuntimePermissionPresenterService();
     method public final void attachBaseContext(android.content.Context);
     method public final android.os.IBinder onBind(android.content.Intent);
+    method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean);
     method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String);
     method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String);
     field public static final java.lang.String SERVICE_INTERFACE = "android.permission.RuntimePermissionPresenterService";
@@ -4563,11 +4584,6 @@
     field public static final int FLAG_REMOVABLE_USB = 524288; // 0x80000
   }
 
-  public final class MediaStore {
-    method public static void deleteContributedMedia(android.content.Context, java.lang.String);
-    method public static long getContributedMediaSize(android.content.Context, java.lang.String);
-  }
-
   public abstract class SearchIndexableData {
     ctor public SearchIndexableData();
     ctor public SearchIndexableData(android.content.Context);
@@ -4912,7 +4928,10 @@
 
   public abstract class AutofillFieldClassificationService extends android.app.Service {
     method public android.os.IBinder onBind(android.content.Intent);
-    method public float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
+    method public float[][] onCalculateScores(java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>, java.util.List<java.lang.String>, java.lang.String, android.os.Bundle, java.util.Map, java.util.Map);
+    method public deprecated float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
+    field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
+    field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
     field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms";
     field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm";
@@ -5630,6 +5649,10 @@
     field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
   }
 
+  public final class PhoneAccountSuggestion implements android.os.Parcelable {
+    ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean);
+  }
+
   public final class RemoteConference {
     method public deprecated void setAudioState(android.telecom.AudioState);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 5bedc72..4717969 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -10,6 +10,8 @@
     field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final java.lang.String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
+    field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    field public static final java.lang.String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
 }
@@ -372,6 +374,7 @@
   }
 
   public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
     field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
@@ -1006,8 +1009,8 @@
   }
 
   public final class MediaStore {
-    method public static void deleteContributedMedia(android.content.Context, java.lang.String);
-    method public static long getContributedMediaSize(android.content.Context, java.lang.String);
+    method public static void deleteContributedMedia(android.content.Context, java.lang.String, android.os.UserHandle) throws java.io.IOException;
+    method public static long getContributedMediaSize(android.content.Context, java.lang.String, android.os.UserHandle) throws java.io.IOException;
   }
 
   public final class Settings {
@@ -1093,6 +1096,15 @@
 
 package android.service.autofill {
 
+  public abstract class AutofillFieldClassificationService extends android.app.Service {
+    method public android.os.IBinder onBind(android.content.Intent);
+    field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
+    field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
+    field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
+    field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms";
+    field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm";
+  }
+
   public final class CharSequenceTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
   }
@@ -1149,6 +1161,10 @@
     method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
   }
 
+  public final class UserData implements android.os.Parcelable {
+    method public android.util.ArrayMap<java.lang.String, java.lang.String> getFieldClassificationAlgorithms();
+  }
+
   public abstract interface ValueFinder {
     method public default java.lang.String findByAutofillId(android.view.autofill.AutofillId);
     method public abstract android.view.autofill.AutofillValue findRawValueByAutofillId(android.view.autofill.AutofillId);
@@ -1267,6 +1283,10 @@
     ctor public CallAudioState(boolean, int, int, android.bluetooth.BluetoothDevice, java.util.Collection<android.bluetooth.BluetoothDevice>);
   }
 
+  public final class PhoneAccountSuggestion implements android.os.Parcelable {
+    ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean);
+  }
+
 }
 
 package android.telephony {
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 3723fce..e3748f1 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -201,7 +201,7 @@
 
     private void doEnabled(@UserIdInt int userId) {
         try {
-            boolean isEnabled = mBmgr.isBackupEnabled();
+            boolean isEnabled = mBmgr.isBackupEnabledForUser(userId);
             System.out.println("Backup Manager currently "
                     + enableToString(isEnabled));
         } catch (RemoteException e) {
@@ -219,7 +219,7 @@
 
         try {
             boolean enable = Boolean.parseBoolean(arg);
-            mBmgr.setBackupEnabled(enable);
+            mBmgr.setBackupEnabledForUser(userId, enable);
             System.out.println("Backup Manager now " + enableToString(enable));
         } catch (NumberFormatException e) {
             showUsage();
@@ -232,7 +232,7 @@
 
     void doRun(@UserIdInt int userId) {
         try {
-            mBmgr.backupNow();
+            mBmgr.backupNowForUser(userId);
         } catch (RemoteException e) {
             System.err.println(e.toString());
             System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -416,7 +416,8 @@
                     (monitorState != Monitor.OFF)
                             ? new BackupMonitor(monitorState == Monitor.VERBOSE)
                             : null;
-            int err = mBmgr.requestBackup(
+            int err = mBmgr.requestBackupForUser(
+                    userId,
                     packages.toArray(new String[packages.size()]),
                     observer,
                     monitor,
@@ -477,7 +478,7 @@
         String arg = nextArg();
         if ("backups".equals(arg)) {
             try {
-                mBmgr.cancelBackups();
+                mBmgr.cancelBackupsForUser(userId);
             } catch (RemoteException e) {
                 System.err.println(e.toString());
                 System.err.println(BMGR_NOT_RUNNING_ERR);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 78d8e29..f58caff 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1217,6 +1217,12 @@
     optional bool has_audio = 3;
     optional bool has_hid = 4;
     optional bool has_storage = 5;
+    enum State {
+        STATE_DISCONNECTED = 0;
+        STATE_CONNECTED = 1;
+    }
+    optional State state = 6;
+    optional int64 last_connect_duration_ms = 7;
 }
 
 
@@ -1265,14 +1271,18 @@
  * Logs when something is plugged into or removed from the USB-C connector.
  *
  * Logged from:
- *  Vendor USB HAL.
+ *  UsbService
  */
 message UsbConnectorStateChanged {
     enum State {
-      DISCONNECTED = 0;
-      CONNECTED = 1;
+        STATE_DISCONNECTED = 0;
+        STATE_CONNECTED = 1;
     }
     optional State state = 1;
+    optional string id = 2;
+    // Last active session in ms.
+    // 0 when the port is in connected state.
+    optional int64 last_connect_duration_millis = 3;
 }
 
 /**
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index f25520b..1f22a6a 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -432,6 +432,26 @@
     return false;
 }
 
+bool ValueMetricProducer::hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey) {
+    // ===========GuardRail==============
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mCurrentFullBucket.find(newKey) != mCurrentFullBucket.end()) {
+        return false;
+    }
+    if (mCurrentFullBucket.size() > mDimensionSoftLimit - 1) {
+        size_t newTupleCount = mCurrentFullBucket.size() + 1;
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > mDimensionHardLimit) {
+            ALOGE("ValueMetric %lld dropping data for full bucket dimension key %s",
+                  (long long)mMetricId,
+                  newKey.toString().c_str());
+            return true;
+        }
+    }
+
+    return false;
+}
+
 bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) {
     for (const FieldValue& value : event.getValues()) {
         if (value.mField.matches(matcher)) {
@@ -496,6 +516,7 @@
             VLOG("Failed to get value %d from event %s", i, event.ToString().c_str());
             return;
         }
+        interval.seenNewData = true;
 
         if (mUseDiff) {
             if (!interval.hasBase) {
@@ -648,6 +669,9 @@
         // Accumulate partial buckets with current value and then send to anomaly tracker.
         if (mCurrentFullBucket.size() > 0) {
             for (const auto& slice : mCurrentSlicedBucket) {
+                if (hitFullBucketGuardRailLocked(slice.first)) {
+                    continue;
+                }
                 // TODO: fix this when anomaly can accept double values
                 mCurrentFullBucket[slice.first] += slice.second[0].value.long_value;
             }
@@ -679,11 +703,21 @@
         }
     }
 
-    // Reset counters
-    for (auto& slice : mCurrentSlicedBucket) {
-        for (auto& interval : slice.second) {
+    for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) {
+        bool obsolete = true;
+        for (auto& interval : it->second) {
             interval.hasValue = false;
             interval.sampleSize = 0;
+            if (interval.seenNewData) {
+                obsolete = false;
+            }
+            interval.seenNewData = false;
+        }
+
+        if (obsolete) {
+            it = mCurrentSlicedBucket.erase(it);
+        } else {
+            it++;
         }
     }
 }
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 36ae214..4991af4 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -128,7 +128,9 @@
         int sampleSize;
         // If this dimension has any non-tainted value. If not, don't report the
         // dimension.
-        bool hasValue;
+        bool hasValue = false;
+        // Whether new data is seen in the bucket.
+        bool seenNewData = false;
     } Interval;
 
     std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket;
@@ -146,6 +148,8 @@
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
+    bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey);
+
     void pullAndMatchEventsLocked(const int64_t timestampNs);
 
     // Reset diff base and mHasGlobalBase
@@ -202,6 +206,7 @@
     FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);
+    FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 1bd34f5..5524503 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -1469,7 +1469,7 @@
 }
 
 /*
- * Tests pulled atoms with no conditions
+ * Tests zero default base.
  */
 TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) {
     ValueMetric metric;
@@ -1554,7 +1554,7 @@
 }
 
 /*
- * Tests pulled atoms with no conditions
+ * Tests using zero default base with failed pull.
  */
 TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) {
     ValueMetric metric;
@@ -1681,6 +1681,129 @@
     EXPECT_EQ(2UL, valueProducer.mPastBuckets.size());
 }
 
+/*
+ * Tests trim unused dimension key if no new data is seen in an entire bucket.
+ */
+TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
+    ValueMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    UidMap uidMap;
+    SimpleAtomMatcher atomMatcher;
+    atomMatcher.set_atom_id(tagId);
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, _, _))
+            .WillOnce(Invoke([](int tagId, int64_t timeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
+                event->write(1);
+                event->write(3);
+                event->init();
+                data->push_back(event);
+                return true;
+            }));
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    auto iter = valueProducer.mCurrentSlicedBucket.begin();
+    auto& interval1 = iter->second[0];
+    EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    EXPECT_EQ(true, interval1.hasBase);
+    EXPECT_EQ(3, interval1.base.long_value);
+    EXPECT_EQ(false, interval1.hasValue);
+    EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
+    vector<shared_ptr<LogEvent>> allData;
+
+    allData.clear();
+    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+    event1->write(2);
+    event1->write(4);
+    event1->init();
+    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+    event2->write(1);
+    event2->write(11);
+    event2->init();
+    allData.push_back(event1);
+    allData.push_back(event2);
+
+    valueProducer.onDataPulled(allData);
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval1.hasBase);
+    EXPECT_EQ(11, interval1.base.long_value);
+    EXPECT_EQ(true, interval1.hasValue);
+    EXPECT_EQ(8, interval1.value.long_value);
+    EXPECT_TRUE(interval1.seenNewData);
+
+    auto it = valueProducer.mCurrentSlicedBucket.begin();
+    for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) {
+        if (it != iter) {
+            break;
+        }
+    }
+    EXPECT_TRUE(it != iter);
+    auto& interval2 = it->second[0];
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    EXPECT_EQ(true, interval2.hasBase);
+    EXPECT_EQ(4, interval2.base.long_value);
+    EXPECT_EQ(false, interval2.hasValue);
+    EXPECT_TRUE(interval2.seenNewData);
+    EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
+
+    // next pull somehow did not happen, skip to end of bucket 3
+    allData.clear();
+    event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
+    event1->write(2);
+    event1->write(5);
+    event1->init();
+    allData.push_back(event1);
+    valueProducer.onDataPulled(allData);
+
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+
+    EXPECT_EQ(false, interval1.hasBase);
+    EXPECT_EQ(false, interval1.hasValue);
+    EXPECT_EQ(8, interval1.value.long_value);
+    // on probation now
+    EXPECT_FALSE(interval1.seenNewData);
+
+    EXPECT_EQ(true, interval2.hasBase);
+    EXPECT_EQ(5, interval2.base.long_value);
+    EXPECT_EQ(false, interval2.hasValue);
+    // back to good status
+    EXPECT_TRUE(interval2.seenNewData);
+    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+
+    allData.clear();
+    event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1);
+    event1->write(2);
+    event1->write(13);
+    event1->init();
+    allData.push_back(event1);
+    valueProducer.onDataPulled(allData);
+
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval2.hasBase);
+    EXPECT_EQ(13, interval2.base.long_value);
+    EXPECT_EQ(true, interval2.hasValue);
+    EXPECT_EQ(8, interval2.value.long_value);
+    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 56dc4c1..bacb991 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1639,10 +1639,6 @@
 Landroid/widget/QuickContactBadge$QueryHandler;-><init>(Landroid/widget/QuickContactBadge;Landroid/content/ContentResolver;)V
 Landroid/widget/RelativeLayout$DependencyGraph$Node;-><init>()V
 Landroid/widget/ScrollBarDrawable;-><init>()V
-Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource;->values()[Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType;
 Lcom/android/ims/ImsCall;->deflect(Ljava/lang/String;)V
 Lcom/android/ims/ImsCall;->isMultiparty()Z
 Lcom/android/ims/ImsCall;->reject(I)V
diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java
index 65e3f25..ac3daaf 100644
--- a/core/java/android/annotation/UnsupportedAppUsage.java
+++ b/core/java/android/annotation/UnsupportedAppUsage.java
@@ -26,16 +26,32 @@
 import java.lang.annotation.Target;
 
 /**
- * Indicates that a class member, that is not part of the SDK, is used by apps.
- * Since the member is not part of the SDK, such use is not supported.
+ * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a
+ * class member (field or method) that is not part of the public SDK. Since the
+ * member is not part of the SDK, usage by apps is not supported.
  *
- * <p>This annotation acts as a heads up that changing a given method or field
+ * <h2>If you are an Android App developer</h2>
+ *
+ * This annotation indicates that you may be able to access the member, but that
+ * this access is discouraged and not supported by Android. If there is a value
+ * for {@link #maxTargetSdk()} on the annotation, access will be restricted based
+ * on the {@code targetSdkVersion} value set in your manifest.
+ *
+ * <p>Fields and methods annotated with this are likely to be restricted, changed
+ * or removed in future Android releases. If you rely on these members for
+ * functionality that is not otherwise supported by Android, consider filing a
+ * <a href="http://g.co/dev/appcompat">feature request</a>.
+ *
+ * <h2>If you are an Android OS developer</h2>
+ *
+ * This annotation acts as a heads up that changing a given method or field
  * may affect apps, potentially breaking them when the next Android version is
  * released. In some cases, for members that are heavily used, this annotation
  * may imply restrictions on changes to the member.
  *
  * <p>This annotation also results in access to the member being permitted by the
- * runtime, with a warning being generated in debug builds.
+ * runtime, with a warning being generated in debug builds. Which apps can access
+ * the member is determined by the value of {@link #maxTargetSdk()}.
  *
  * <p>For more details, see go/UnsupportedAppUsage.
  *
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 61b9d55..5e445d14 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6755,8 +6755,8 @@
                 case "--autofill":
                     dumpAutofillManager(prefix, writer);
                     return;
-                case "--intelligence":
-                    dumpIntelligenceManager(prefix, writer);
+                case "--contentcapture":
+                    dumpContentCaptureManager(prefix, writer);
                     return;
             }
         }
@@ -6788,7 +6788,7 @@
         mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
 
         dumpAutofillManager(prefix, writer);
-        dumpIntelligenceManager(prefix, writer);
+        dumpContentCaptureManager(prefix, writer);
 
         ResourcesManager.getInstance().dump(prefix, writer);
     }
@@ -6804,12 +6804,12 @@
         }
     }
 
-    void dumpIntelligenceManager(String prefix, PrintWriter writer) {
-        final ContentCaptureManager im = getContentCaptureManager();
-        if (im != null) {
-            im.dump(prefix, writer);
+    void dumpContentCaptureManager(String prefix, PrintWriter writer) {
+        final ContentCaptureManager cm = getContentCaptureManager();
+        if (cm != null) {
+            cm.dump(prefix, writer);
         } else {
-            writer.print(prefix); writer.println("No IntelligenceManager");
+            writer.print(prefix); writer.println("No ContentCaptureManager");
         }
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8e54961..81eac5a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10239,4 +10239,73 @@
         }
         return Collections.emptySet();
     }
+
+    /**
+     * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of
+     * this release, these requirements are as follows:
+     * <ul>
+     *     <li>The device is in Lock Task (therefore there is also a Device Owner app on the
+     *     device)</li>
+     *     <li>The Lock Task feature {@link DevicePolicyManager#LOCK_TASK_FEATURE_SYSTEM_INFO} is
+     *     not enabled, so the system info in the status bar is not visible</li>
+     *     <li>The device does not have a secure lock screen (e.g. it has no lock screen or has
+     *     swipe-to-unlock)</li>
+     *     <li>The device is not in the middle of an ephemeral user session</li>
+     * </ul>
+     *
+     * <p>Publicly-accessible dedicated devices don't have the same privacy model as
+     * personally-used devices. In particular, user consent popups don't make sense as a barrier to
+     * accessing persistent data on these devices since the user giving consent and the user whose
+     * data is on the device are unlikely to be the same. These consent popups prevent the true
+     * remote management of these devices.
+     *
+     * <p>This condition is not sufficient to cover APIs that would access data that only lives for
+     * the duration of the user's session, since the user has an expectation of privacy in these
+     * conditions that more closely resembles use of a personal device. In those cases, see {@link
+     * #isUnattendedManagedKiosk()}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isManagedKiosk() {
+        throwIfParentInstance("isManagedKiosk");
+        if (mService != null) {
+            try {
+                return mService.isManagedKiosk();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether the device is being used as an unattended managed kiosk, as defined in the
+     * CDD. As of this release, these requirements are as follows:
+     * <ul>
+     *     <li>The device is being used as a managed kiosk, as defined in the CDD and verified at
+     *     {@link #isManagedKiosk()}</li>
+     *     <li>The device has not received user input for at least 30 minutes</li>
+     * </ul>
+     *
+     * <p>See {@link #isManagedKiosk()} for context. This is a stronger requirement that also
+     * ensures that the device hasn't been interacted with recently, making it an appropriate check
+     * for privacy-sensitive APIs that wouldn't be appropriate during an active user session.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isUnattendedManagedKiosk() {
+        throwIfParentInstance("isUnattendedManagedKiosk");
+        if (mService != null) {
+            try {
+                return mService.isUnattendedManagedKiosk();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1148685..1ff4146 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -428,4 +428,7 @@
     List<String> getCrossProfileCalendarPackages(in ComponentName admin);
     boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
     List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
+
+    boolean isManagedKiosk();
+    boolean isUnattendedManagedKiosk();
 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 3e20748..0afb98f 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -91,6 +91,15 @@
      * at some point in the future.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id for which backup service should be enabled/disabled.
+     */
+    void setBackupEnabledForUser(int userId, boolean isEnabled);
+
+    /**
+     * {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id.
      */
     void setBackupEnabled(boolean isEnabled);
 
@@ -120,6 +129,15 @@
      * Report whether the backup mechanism is currently enabled.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id for which the backup service status should be reported.
+     */
+    boolean isBackupEnabledForUser(int userId);
+
+    /**
+     * {@link android.app.backup.IBackupManager.isBackupEnabledForUser} for the calling user id.
      */
     boolean isBackupEnabled();
 
@@ -149,6 +167,15 @@
      * method.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id for which an immediate backup should be scheduled.
+     */
+    void backupNowForUser(int userId);
+
+    /**
+     * {@link android.app.backup.IBackupManager.backupNowForUser} for the calling user id.
      */
     void backupNow();
 
@@ -432,6 +459,12 @@
      * <p>If this method returns zero (meaning success), the OS will attempt to backup all provided
      * packages using the remote transport.
      *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id for which an immediate backup should be requested.
+
      * @param observer The {@link BackupObserver} to receive callbacks during the backup
      * operation.
      *
@@ -442,12 +475,29 @@
      *
      * @return Zero on success; nonzero on error.
      */
+    int requestBackupForUser(int userId, in String[] packages, IBackupObserver observer,
+        IBackupManagerMonitor monitor, int flags);
+
+    /**
+     * {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id.
+     */
     int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
         int flags);
 
     /**
      * Cancel all running backups. After this call returns, no currently running backups will
      * interact with the selected transport.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id for which backups should be cancelled.
+     */
+    void cancelBackupsForUser(int userId);
+
+    /**
+     * {@link android.app.backup.IBackupManager.cancelBackups} for the calling user id.
      */
     void cancelBackups();
 }
diff --git a/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl
new file mode 100644
index 0000000..6cf961f
--- /dev/null
+++ b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+/**
+ * @hide
+ */
+oneway interface IOnRoleHoldersChangedListener {
+
+    void onRoleHoldersChanged(String roleName, int userId);
+}
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 3ca8ec0..4ce0f31 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app.role;
 
+import android.app.role.IOnRoleHoldersChangedListener;
 import android.app.role.IRoleManagerCallback;
 
 /**
@@ -37,6 +38,11 @@
 
     void clearRoleHoldersAsUser(in String roleName, int userId, in IRoleManagerCallback callback);
 
+    void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId);
+
+    void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener,
+            int userId);
+
     void setRoleNamesFromController(in List<String> roleNames);
 
     boolean addRoleHolderFromController(in String roleName, in String packageName);
diff --git a/core/java/android/app/role/OnRoleHoldersChangedListener.java b/core/java/android/app/role/OnRoleHoldersChangedListener.java
new file mode 100644
index 0000000..5958deb
--- /dev/null
+++ b/core/java/android/app/role/OnRoleHoldersChangedListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Listener for role holder changes.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OnRoleHoldersChangedListener {
+
+    /**
+     * Called when the holders of roles are changed.
+     *
+     * @param roleName the name of the role whose holders are changed
+     * @param user the user for this role holder change
+     */
+    void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user);
+}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index f3b2153..5d101ab 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
@@ -30,8 +31,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -125,6 +130,13 @@
     @NonNull
     private final IRoleManager mService;
 
+    @GuardedBy("mListenersLock")
+    @NonNull
+    private final SparseArray<ArrayMap<OnRoleHoldersChangedListener,
+            OnRoleHoldersChangedListenerDelegate>> mListeners = new SparseArray<>();
+    @NonNull
+    private final Object mListenersLock = new Object();
+
     /**
      * @hide
      */
@@ -146,8 +158,6 @@
      * @param roleName the name of requested role
      *
      * @return the {@code Intent} to prompt user to grant the role
-     *
-     * @throws IllegalArgumentException if {@code role} is {@code null} or empty
      */
     @NonNull
     public Intent createRequestRoleIntent(@NonNull String roleName) {
@@ -164,8 +174,6 @@
      * @param roleName the name of role to checking for
      *
      * @return whether the role is available in the system
-     *
-     * @throws IllegalArgumentException if the role name is {@code null} or empty
      */
     public boolean isRoleAvailable(@NonNull String roleName) {
         Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
@@ -182,8 +190,6 @@
      * @param roleName the name of the role to check for
      *
      * @return whether the calling application is holding the role
-     *
-     * @throws IllegalArgumentException if the role name is {@code null} or empty.
      */
     public boolean isRoleHeld(@NonNull String roleName) {
         Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
@@ -204,8 +210,6 @@
      *
      * @return a list of package names of the role holders, or an empty list if none.
      *
-     * @throws IllegalArgumentException if the role name is {@code null} or empty.
-     *
      * @see #getRoleHoldersAsUser(String, UserHandle)
      *
      * @hide
@@ -230,8 +234,6 @@
      *
      * @return a list of package names of the role holders, or an empty list if none.
      *
-     * @throws IllegalArgumentException if the role name is {@code null} or empty.
-     *
      * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
      * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
      * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
@@ -266,8 +268,6 @@
      * @param executor the {@code Executor} to run the callback on.
      * @param callback the callback for whether this call is successful
      *
-     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
-     *
      * @see #getRoleHoldersAsUser(String, UserHandle)
      * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
      * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
@@ -306,8 +306,6 @@
      * @param executor the {@code Executor} to run the callback on.
      * @param callback the callback for whether this call is successful
      *
-     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
-     *
      * @see #getRoleHoldersAsUser(String, UserHandle)
      * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
      * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
@@ -345,8 +343,6 @@
      * @param executor the {@code Executor} to run the callback on.
      * @param callback the callback for whether this call is successful
      *
-     * @throws IllegalArgumentException if the role name is {@code null} or empty.
-     *
      * @see #getRoleHoldersAsUser(String, UserHandle)
      * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
      * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
@@ -371,6 +367,96 @@
     }
 
     /**
+     * Add a listener to observe role holder changes
+     * <p>
+     * <strong>Note:</strong> Using this API requires holding
+     * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param executor the {@code Executor} to call the listener on.
+     * @param listener the listener to be added
+     * @param user the user to add the listener for
+     *
+     * @see #removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener, UserHandle)
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
+    @SystemApi
+    public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        int userId = user.getIdentifier();
+        synchronized (mListenersLock) {
+            ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
+                    mListeners.get(userId);
+            if (listeners == null) {
+                listeners = new ArrayMap<>();
+                mListeners.put(userId, listeners);
+            } else {
+                if (listeners.containsKey(listener)) {
+                    return;
+                }
+            }
+            OnRoleHoldersChangedListenerDelegate listenerDelegate =
+                    new OnRoleHoldersChangedListenerDelegate(executor, listener);
+            try {
+                mService.addOnRoleHoldersChangedListenerAsUser(listenerDelegate, userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            listeners.put(listener, listenerDelegate);
+        }
+    }
+
+    /**
+     * Remove a listener observing role holder changes
+     * <p>
+     * <strong>Note:</strong> Using this API requires holding
+     * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param listener the listener to be removed
+     * @param user the user to remove the listener for
+     *
+     * @see #addOnRoleHoldersChangedListenerAsUser(Executor, OnRoleHoldersChangedListener,
+     *                                             UserHandle)
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
+    @SystemApi
+    public void removeOnRoleHoldersChangedListenerAsUser(
+            @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        int userId = user.getIdentifier();
+        synchronized (mListenersLock) {
+            ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
+                    mListeners.get(userId);
+            if (listeners == null) {
+                return;
+            }
+            OnRoleHoldersChangedListenerDelegate listenerDelegate = listeners.get(listener);
+            if (listenerDelegate == null) {
+                return;
+            }
+            try {
+                mService.removeOnRoleHoldersChangedListenerAsUser(listenerDelegate,
+                        user.getIdentifier());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            listeners.remove(listener);
+            if (listeners.isEmpty()) {
+                mListeners.remove(userId);
+            }
+        }
+    }
+
+    /**
      * Set the names of all the available roles. Should only be called from
      * {@link android.rolecontrollerservice.RoleControllerService}.
      * <p>
@@ -379,8 +465,6 @@
      *
      * @param roleNames the names of all the available roles
      *
-     * @throws IllegalArgumentException if the list of role names is {@code null}.
-     *
      * @hide
      */
     @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
@@ -408,8 +492,6 @@
      * @return whether the operation was successful, and will also be {@code true} if a matching
      *         role holder is already found.
      *
-     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
-     *
      * @see #getRoleHolders(String)
      * @see #removeRoleHolderFromController(String, String)
      *
@@ -442,8 +524,6 @@
      * @return whether the operation was successful, and will also be {@code true} if no matching
      *         role holder was found to remove.
      *
-     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
-     *
      * @see #getRoleHolders(String)
      * @see #addRoleHolderFromController(String, String)
      *
@@ -495,4 +575,31 @@
             }
         }
     }
+
+    private static class OnRoleHoldersChangedListenerDelegate
+            extends IOnRoleHoldersChangedListener.Stub {
+
+        @NonNull
+        private final Executor mExecutor;
+        @NonNull
+        private final OnRoleHoldersChangedListener mListener;
+
+        OnRoleHoldersChangedListenerDelegate(@NonNull Executor executor,
+                @NonNull OnRoleHoldersChangedListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(PooledLambda.obtainRunnable(
+                        OnRoleHoldersChangedListener::onRoleHoldersChanged, mListener, roleName,
+                        UserHandle.of(userId)));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index da65941..6704a45 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.annotation.UserIdInt;
@@ -2978,7 +2979,12 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Put the cache with the key.
+     *
+     * @param key the key to add
+     * @param value the value to add
+     */
     public void putCache(Uri key, Bundle value) {
         try {
             getContentService().putCache(mContext.getPackageName(), key, value,
@@ -2988,7 +2994,13 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Get the cache with the key.
+     *
+     * @param key the key to get the value
+     * @return the matched value. If the key doesn't exist, will return null.
+     * @see #putCache(Uri, Bundle)
+     */
     public Bundle getCache(Uri key) {
         try {
             final Bundle bundle = getContentService().getCache(mContext.getPackageName(), key,
@@ -3176,7 +3188,14 @@
         return mContext.getUserId();
     }
 
-    /** @hide */
+    /**
+     * Get the system drawable of the mime type.
+     *
+     * @param mimeType the requested mime type
+     * @return the matched drawable
+     * @hide
+     */
+    @SystemApi
     public Drawable getTypeDrawable(String mimeType) {
         return MimeIconUtils.loadMimeIcon(mContext, mimeType);
     }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b6f9d15..2f0618c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5267,8 +5267,6 @@
      * Used as a boolean extra field in {@link #ACTION_CHOOSER} intents to specify
      * whether to show the chooser or not when there is only one application available
      * to choose from.
-     *
-     * @hide
      */
     public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE =
             "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1c564f3..740fd7f 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,6 +16,8 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -66,7 +68,30 @@
                     mContext.getIApplicationThread(),
                     mContext.getPackageName(),
                     component,
-                    targetUser);
+                    targetUser.getIdentifier(),
+                    true);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts the specified activity of the caller package in the specified profile if the caller
+     * has {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} permission and
+     * both the caller and target user profiles are in the same user group.
+     *
+     * @param component The ComponentName of the activity to launch. It must be exported.
+     * @param targetUser The UserHandle of the profile, must be one of the users returned by
+     *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+     *        be thrown.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
+    public void startAnyActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
+        try {
+            mService.startActivityAsUser(mContext.getIApplicationThread(),
+                    mContext.getPackageName(), component, targetUser.getIdentifier(), false);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index bc2f92a..d2d66cb 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -28,6 +28,6 @@
  */
 interface ICrossProfileApps {
     void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
-            in ComponentName component, in UserHandle user);
+            in ComponentName component, int userId, boolean launchMainActivity);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0150f6a..da39b63 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1399,6 +1399,16 @@
     public static final int DELETE_DONT_KILL_APP = 0x00000008;
 
     /**
+     * Flag parameter for {@link #deletePackage} to indicate that any
+     * contributed media should also be deleted during this uninstall. The
+     * meaning of "contributed" means it won't automatically be deleted when the
+     * app is uninstalled.
+     *
+     * @hide
+     */
+    public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
+
+    /**
      * Flag parameter for {@link #deletePackage} to indicate that package deletion
      * should be chatty.
      *
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 43c0222..5db9f50 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -54,6 +54,7 @@
     public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
     public static final int PACKAGE_PERMISSION_CONTROLLER = 6;
     public static final int PACKAGE_WELLBEING = 7;
+    public static final int PACKAGE_DOCUMENTER = 8;
     @IntDef(value = {
         PACKAGE_SYSTEM,
         PACKAGE_SETUP_WIZARD,
@@ -63,6 +64,7 @@
         PACKAGE_SYSTEM_TEXT_CLASSIFIER,
         PACKAGE_PERMISSION_CONTROLLER,
         PACKAGE_WELLBEING,
+        PACKAGE_DOCUMENTER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {}
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 20997d6..bb8c92d 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -192,6 +192,17 @@
     @TestApi
     public static final int PROTECTION_FLAG_WELLBEING = 0x20000;
 
+    /**
+     * Additional flag for {@link #protectionLevel}, corresponding to the
+     * {@code documenter} value of {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int PROTECTION_FLAG_DOCUMENTER = 0x40000;
+
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
             PROTECTION_FLAG_PRIVILEGED,
@@ -209,6 +220,7 @@
             PROTECTION_FLAG_VENDOR_PRIVILEGED,
             PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER,
             PROTECTION_FLAG_WELLBEING,
+            PROTECTION_FLAG_DOCUMENTER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -401,6 +413,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0) {
             protLevel += "|wellbeing";
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0) {
+            protLevel += "|documenter";
+        }
         return protLevel;
     }
 
diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java
index 5afc8a9..a607a9f 100644
--- a/core/java/android/content/pm/SharedLibraryNames.java
+++ b/core/java/android/content/pm/SharedLibraryNames.java
@@ -22,15 +22,15 @@
  */
 public class SharedLibraryNames {
 
-    public static final String ANDROID_HIDL_BASE = "android.hidl.base-V1.0-java";
+    static final String ANDROID_HIDL_BASE = "android.hidl.base-V1.0-java";
 
-    public static final String ANDROID_HIDL_MANAGER = "android.hidl.manager-V1.0-java";
+    static final String ANDROID_HIDL_MANAGER = "android.hidl.manager-V1.0-java";
 
-    public static final String ANDROID_TEST_BASE = "android.test.base";
+    static final String ANDROID_TEST_BASE = "android.test.base";
 
-    public static final String ANDROID_TEST_MOCK = "android.test.mock";
+    static final String ANDROID_TEST_MOCK = "android.test.mock";
 
-    public static final String ANDROID_TEST_RUNNER = "android.test.runner";
+    static final String ANDROID_TEST_RUNNER = "android.test.runner";
 
     public static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy";
 }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2ae796c..d45fa11 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -916,23 +916,49 @@
     private static native long getNativeBBinderHolder();
     private static native long getFinalizer();
 
+    /**
+     * By default, we use the calling uid since we can always trust it.
+     */
+    private static volatile BinderInternal.WorkSourceProvider sWorkSourceProvider =
+            Binder::getCallingUid;
+
+    /**
+     * Sets the work source provider.
+     *
+     * <li>The callback is global. Only fast operations should be done to avoid thread
+     * contentions.
+     * <li>The callback implementation needs to handle synchronization if needed. The methods on the
+     * callback can be called concurrently.
+     * <li>The callback is called on the critical path of the binder transaction so be careful about
+     * performance.
+     * <li>Never execute another binder transaction inside the callback.
+     * @hide
+     */
+    public static void setWorkSourceProvider(BinderInternal.WorkSourceProvider workSourceProvider) {
+        if (workSourceProvider == null) {
+            throw new IllegalArgumentException("workSourceProvider cannot be null");
+        }
+        sWorkSourceProvider = workSourceProvider;
+    }
+
     // Entry point from android_util_Binder.cpp's onTransact
     private boolean execTransact(int code, long dataObj, long replyObj,
             int flags) {
-        final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid());
+        final int workSourceUid = sWorkSourceProvider.resolveWorkSourceUid();
+        final long origWorkSource = ThreadLocalWorkSource.setUid(workSourceUid);
         try {
-            return execTransactInternal(code, dataObj, replyObj, flags);
+            return execTransactInternal(code, dataObj, replyObj, flags, workSourceUid);
         } finally {
             ThreadLocalWorkSource.restore(origWorkSource);
         }
     }
 
     private boolean execTransactInternal(int code, long dataObj, long replyObj,
-            int flags) {
+            int flags, int workSourceUid) {
         // Make sure the observer won't change while processing a transaction.
         final BinderInternal.Observer observer = sObserver;
         final CallSession callSession =
-                observer != null ? observer.callStarted(this, code) : null;
+                observer != null ? observer.callStarted(this, code, workSourceUid) : null;
         Parcel data = Parcel.obtain(dataObj);
         Parcel reply = Parcel.obtain(replyObj);
         // theoretically, we should call transact, which will call onTransact,
@@ -971,10 +997,11 @@
             if (tracingEnabled) {
                 Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
             }
+            if (observer != null) {
+                observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid);
+            }
         }
         checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
-        int replySizeBytes = reply.dataSize();
-        int requestSizeBytes = data.dataSize();
         reply.recycle();
         data.recycle();
 
@@ -984,9 +1011,6 @@
         // to the main transaction loop to wait for another incoming transaction.  Either
         // way, strict mode begone!
         StrictMode.clearGatheredViolations();
-        if (observer != null) {
-            observer.callEnded(callSession, requestSizeBytes, replySizeBytes);
-        }
         return res;
     }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
old mode 100644
new mode 100755
index 292543c..9fea873
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1226,7 +1226,8 @@
      * null (if, for instance, the radio is not currently on).
      */
     public static String getRadioVersion() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION, null);
+        String propVal = SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION);
+        return TextUtils.isEmpty(propVal) ? null : propVal;
     }
 
     private static String getString(String property) {
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 2cb5aee..d55489a 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -200,4 +200,7 @@
      * PowerHint defined in android/hardware/power/<version 1.0 & up>/IPower.h
      */
     public abstract void powerHint(int hintId, int data);
+
+    /** Returns whether there hasn't been a user activity event for the given number of ms. */
+    public abstract boolean wasDeviceIdleFor(long ms);
 }
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index 5499181..eee2b52 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -70,6 +70,7 @@
             TYPE_BCL_VOLTAGE,
             TYPE_BCL_CURRENT,
             TYPE_BCL_PERCENTAGE,
+            TYPE_NPU,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -85,6 +86,7 @@
     public static final int TYPE_BCL_VOLTAGE = TemperatureType.BCL_VOLTAGE;
     public static final int TYPE_BCL_CURRENT = TemperatureType.BCL_CURRENT;
     public static final int TYPE_BCL_PERCENTAGE = TemperatureType.BCL_PERCENTAGE;
+    public static final int TYPE_NPU = TemperatureType.NPU;
 
     /**
      * Verify a valid temperature type.
@@ -92,7 +94,7 @@
      * @return true if a temperature type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_UNKNOWN && type <= TYPE_BCL_PERCENTAGE;
+        return type >= TYPE_UNKNOWN && type <= TYPE_NPU;
     }
 
     /**
diff --git a/core/java/android/permission/IRuntimePermissionPresenter.aidl b/core/java/android/permission/IRuntimePermissionPresenter.aidl
index 693d21b..e95428a 100644
--- a/core/java/android/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/permission/IRuntimePermissionPresenter.aidl
@@ -26,4 +26,6 @@
 oneway interface IRuntimePermissionPresenter {
     void getAppPermissions(String packageName, in RemoteCallback callback);
     void revokeRuntimePermission(String packageName, String permissionName);
+    void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
+            boolean countSystem, in RemoteCallback callback);
 }
diff --git a/core/java/android/permission/RuntimePermissionPresenter.java b/core/java/android/permission/RuntimePermissionPresenter.java
index ed9165e..3ce3c9d 100644
--- a/core/java/android/permission/RuntimePermissionPresenter.java
+++ b/core/java/android/permission/RuntimePermissionPresenter.java
@@ -16,6 +16,7 @@
 
 package android.permission;
 
+import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -62,16 +63,31 @@
             "android.permission.RuntimePermissionPresenter.key.result";
 
     /**
-     * Listener for delivering a result.
+     * Listener for delivering the result of {@link #getAppPermissions}.
      */
-    public interface OnResultCallback {
+    public interface OnGetAppPermissionResultCallback {
         /**
-         * The result for {@link #getAppPermissions(String, OnResultCallback, Handler)}.
+         * The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback,
+         * Handler)}.
+         *
          * @param permissions The permissions list.
          */
         void onGetAppPermissions(@NonNull List<RuntimePermissionPresentationInfo> permissions);
     }
 
+    /**
+     * Listener for delivering the result of {@link #countPermissionApps}.
+     */
+    public interface OnCountPermissionAppsResultCallback {
+        /**
+         * The result for {@link #countPermissionApps(List, boolean,
+         * OnCountPermissionAppsResultCallback, Handler)}.
+         *
+         * @param numApps The number of apps that have one of the permissions
+         */
+        void onCountPermissionApps(int numApps);
+    }
+
     private static final Object sLock = new Object();
 
     @GuardedBy("sLock")
@@ -106,7 +122,7 @@
      * @param handler Handler on which to invoke the callback.
      */
     public void getAppPermissions(@NonNull String packageName,
-            @NonNull OnResultCallback callback, @Nullable Handler handler) {
+            @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
         checkNotNull(packageName);
         checkNotNull(callback);
 
@@ -129,6 +145,25 @@
                 mRemoteService, packageName, permissionName));
     }
 
+    /**
+     * Count how many apps have one of a set of permissions.
+     *
+     * @param permissionNames The permissions the app might have
+     * @param countOnlyGranted Count an app only if the permission is granted to the app
+     * @param countSystem Also count system apps
+     * @param callback Callback to receive the result
+     * @param handler Handler on which to invoke the callback
+     */
+    public void countPermissionApps(@NonNull List<String> permissionNames,
+            boolean countOnlyGranted, boolean countSystem,
+            @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) {
+        checkCollectionElementsNotNull(permissionNames, "permissionNames");
+        checkNotNull(callback);
+
+        mRemoteService.processMessage(obtainMessage(RemoteService::countPermissionApps,
+                mRemoteService, permissionNames, countOnlyGranted, countSystem, callback, handler));
+    }
+
     private static final class RemoteService
             extends Handler implements ServiceConnection {
         private static final long UNBIND_TIMEOUT_MILLIS = 10000;
@@ -184,7 +219,7 @@
         }
 
         private void getAppPermissions(@NonNull String packageName,
-                @NonNull OnResultCallback callback, @Nullable Handler handler) {
+                @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
             final IRuntimePermissionPresenter remoteInstance;
             synchronized (mLock) {
                 remoteInstance = mRemoteInstance;
@@ -241,6 +276,45 @@
             }
         }
 
+        private void countPermissionApps(@NonNull List<String> permissionNames,
+                boolean countOnlyGranted, boolean countSystem,
+                @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) {
+            final IRuntimePermissionPresenter remoteInstance;
+
+            synchronized (mLock) {
+                remoteInstance = mRemoteInstance;
+            }
+            if (remoteInstance == null) {
+                return;
+            }
+
+            try {
+                remoteInstance.countPermissionApps(permissionNames, countOnlyGranted, countSystem,
+                        new RemoteCallback(result -> {
+                            final int numApps;
+                            if (result != null) {
+                                numApps = result.getInt(KEY_RESULT);
+                            } else {
+                                numApps = 0;
+                            }
+
+                            if (handler != null) {
+                                handler.post(() -> callback.onCountPermissionApps(numApps));
+                            } else {
+                                callback.onCountPermissionApps(numApps);
+                            }
+                        }, this));
+            } catch (RemoteException re) {
+                Log.e(TAG, "Error counting permission apps", re);
+            }
+
+            scheduleUnbind();
+
+            synchronized (mLock) {
+                scheduleNextMessageIfNeededLocked();
+            }
+        }
+
         private void unbind() {
             synchronized (mLock) {
                 if (mBound) {
diff --git a/core/java/android/permission/RuntimePermissionPresenterService.java b/core/java/android/permission/RuntimePermissionPresenterService.java
index e50fc5a..81ec7be 100644
--- a/core/java/android/permission/RuntimePermissionPresenterService.java
+++ b/core/java/android/permission/RuntimePermissionPresenterService.java
@@ -16,6 +16,7 @@
 
 package android.permission;
 
+import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -81,6 +82,18 @@
     public abstract void onRevokeRuntimePermission(@NonNull String packageName,
             @NonNull String permissionName);
 
+    /**
+     * Count how many apps have one of a set of permissions.
+     *
+     * @param permissionNames The permissions the app might have
+     * @param countOnlyGranted Count an app only if the permission is granted to the app
+     * @param countSystem Also count system apps
+     *
+     * @return the number of apps that have one of the permissions
+     */
+    public abstract int onCountPermissionApps(@NonNull List<String> permissionNames,
+            boolean countOnlyGranted, boolean countSystem);
+
     @Override
     public final IBinder onBind(Intent intent) {
         return new IRuntimePermissionPresenter.Stub() {
@@ -106,6 +119,19 @@
                                 RuntimePermissionPresenterService.this, packageName,
                                 permissionName));
             }
+
+            @Override
+            public void countPermissionApps(List<String> permissionNames, boolean countOnlyGranted,
+                    boolean countSystem, RemoteCallback callback) {
+                checkCollectionElementsNotNull(permissionNames, "permissionNames");
+                checkNotNull(callback, "callback");
+
+                mHandler.sendMessage(
+                        obtainMessage(
+                                RuntimePermissionPresenterService::countPermissionApps,
+                                RuntimePermissionPresenterService.this, permissionNames,
+                                countOnlyGranted, countSystem, callback));
+            }
         };
     }
 
@@ -119,4 +145,13 @@
             callback.sendResult(null);
         }
     }
+
+    private void countPermissionApps(@NonNull List<String> permissionNames,
+            boolean countOnlyGranted, boolean countSystem, @NonNull RemoteCallback callback) {
+        int numApps = onCountPermissionApps(permissionNames, countOnlyGranted, countSystem);
+
+        Bundle result = new Bundle();
+        result.putInt(RuntimePermissionPresenter.KEY_RESULT, numApps);
+        callback.sendResult(result);
+    }
 }
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index ff77228..d9c16a7 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -244,18 +244,34 @@
      * Get string array identifies the type or types of metadata returned
      * using DocumentsContract#getDocumentMetadata.
      *
-     * @see #getDocumentMetadata(ContentResolver, Uri)
+     * @see #getDocumentMetadata(ContentInterface, Uri)
      */
     public static final String METADATA_TYPES = "android:documentMetadataTypes";
 
     /**
      * Get Exif information using DocumentsContract#getDocumentMetadata.
      *
-     * @see #getDocumentMetadata(ContentResolver, Uri)
+     * @see #getDocumentMetadata(ContentInterface, Uri)
      */
     public static final String METADATA_EXIF = "android:documentExif";
 
     /**
+     * Get total count of all documents currently stored under the given
+     * directory tree. Only valid for {@link Document#MIME_TYPE_DIR} documents.
+     *
+     * @see #getDocumentMetadata(ContentInterface, Uri)
+     */
+    public static final String METADATA_TREE_COUNT = "android:metadataTreeCount";
+
+    /**
+     * Get total size of all documents currently stored under the given
+     * directory tree. Only valid for {@link Document#MIME_TYPE_DIR} documents.
+     *
+     * @see #getDocumentMetadata(ContentInterface, Uri)
+     */
+    public static final String METADATA_TREE_SIZE = "android:metadataTreeSize";
+
+    /**
      * Constants related to a document, including {@link Cursor} column names
      * and flags.
      * <p>
@@ -519,7 +535,7 @@
          * using DocumentsContract#getDocumentMetadata
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
+         * @see DocumentsContract#getDocumentMetadata(ContentInterface, Uri)
          */
         public static final int FLAG_SUPPORTS_METADATA = 1 << 14;
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 1451165..f38f740 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -23,7 +23,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
@@ -50,6 +49,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
@@ -70,6 +70,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 /**
  * The Media provider contains meta data for all available media on both internal
@@ -1046,6 +1047,20 @@
                 getContentUri("external");
 
         /**
+         * The MIME type for this table.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download";
+
+        /**
+         * Regex that matches paths that needs to be considered part of downloads collection.
+         * @hide
+         */
+        public static final Pattern PATTERN_DOWNLOADS_FILE = Pattern.compile(
+                "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/.+");
+        private static final Pattern PATTERN_DOWNLOADS_DIRECTORY = Pattern.compile(
+                "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/?");
+
+        /**
          * Get the content:// style URI for the downloads table on the
          * given volume.
          *
@@ -1061,6 +1076,16 @@
         public static Uri getContentUriForPath(@NonNull String path) {
             return getContentUri(getVolumeNameForPath(path));
         }
+
+        /** @hide */
+        public static boolean isDownload(@NonNull String path) {
+            return PATTERN_DOWNLOADS_FILE.matcher(path).matches();
+        }
+
+        /** @hide */
+        public static boolean isDownloadDir(@NonNull String path) {
+            return PATTERN_DOWNLOADS_DIRECTORY.matcher(path).matches();
+        }
     }
 
     private static String getVolumeNameForPath(@NonNull String path) {
@@ -2882,18 +2907,24 @@
      *
      * @hide
      */
-    @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
-    public static @BytesLong long getContributedMediaSize(Context context, String packageName) {
-        try (ContentProviderClient client = context.getContentResolver()
-                .acquireContentProviderClient(AUTHORITY)) {
-            final Bundle in = new Bundle();
-            in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
-            final Bundle out = client.call(GET_CONTRIBUTED_MEDIA_CALL, null, in);
-            return out.getLong(Intent.EXTRA_INDEX);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+    public static @BytesLong long getContributedMediaSize(Context context, String packageName,
+            UserHandle user) throws IOException {
+        final UserManager um = context.getSystemService(UserManager.class);
+        if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
+            try {
+                final ContentResolver resolver = context
+                        .createPackageContextAsUser(packageName, 0, user).getContentResolver();
+                final Bundle in = new Bundle();
+                in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+                final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
+                return out.getLong(Intent.EXTRA_INDEX);
+            } catch (Exception e) {
+                throw new IOException(e);
+            }
+        } else {
+            throw new IOException("User " + user + " must be unlocked and running");
         }
     }
 
@@ -2904,17 +2935,23 @@
      *
      * @hide
      */
-    @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
-    public static void deleteContributedMedia(Context context, String packageName) {
-        try (ContentProviderClient client = context.getContentResolver()
-                .acquireContentProviderClient(AUTHORITY)) {
-            final Bundle in = new Bundle();
-            in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
-            client.call(DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+    public static void deleteContributedMedia(Context context, String packageName,
+            UserHandle user) throws IOException {
+        final UserManager um = context.getSystemService(UserManager.class);
+        if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
+            try {
+                final ContentResolver resolver = context
+                        .createPackageContextAsUser(packageName, 0, user).getContentResolver();
+                final Bundle in = new Bundle();
+                in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+                resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
+            } catch (Exception e) {
+                throw new IOException(e);
+            }
+        } else {
+            throw new IOException("User " + user + " must be unlocked and running");
         }
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de6afcb..9380695 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12852,6 +12852,13 @@
         public static final String HIDDEN_API_POLICY = "hidden_api_policy";
 
         /**
+         * Current version of signed configuration applied.
+         *
+         * @hide
+         */
+        public static final String SIGNED_CONFIG_VERSION = "signed_config_version";
+
+        /**
          * Timeout for a single {@link android.media.soundtrigger.SoundTriggerDetectionService}
          * operation (in ms).
          *
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
index bd3c3d3..2612917 100644
--- a/core/java/android/service/autofill/AutofillFieldClassificationService.java
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Bundle;
@@ -35,6 +36,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A service that calculates field classification scores.
@@ -51,6 +53,7 @@
  * {@hide}
  */
 @SystemApi
+@TestApi
 public abstract class AutofillFieldClassificationService extends Service {
 
     private static final String TAG = "AutofillFieldClassificationService";
@@ -75,17 +78,32 @@
     public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
             "android.autofill.field_classification.available_algorithms";
 
+    /**
+     * Field classification algorithm that computes the edit distance between two Strings.
+     *
+     * <p>Service implementation must provide this algorithm.</p>
+     */
+    public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
+
+    /**
+     * Field classification algorithm that computes whether the last four digits between two
+     * Strings match exactly.
+     *
+     * <p>Service implementation must provide this algorithm.</p>
+     */
+    public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
 
     /** {@hide} **/
     public static final String EXTRA_SCORES = "scores";
 
     private AutofillFieldClassificationServiceWrapper mWrapper;
 
-    private void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
-            List<AutofillValue> actualValues, String[] userDataValues) {
+    private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
+            String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
+            Bundle defaultArgs, Map algorithms, Map args) {
         final Bundle data = new Bundle();
-        final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
-                Arrays.asList(userDataValues));
+        final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
+                Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
         if (scores != null) {
             data.putParcelable(EXTRA_SCORES, new Scores(scores));
         }
@@ -169,9 +187,12 @@
      * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
      *
      * {@hide}
+     *
+     * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
      */
     @Nullable
     @SystemApi
+    @Deprecated
     public float[][] onGetScores(@Nullable String algorithm,
             @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
             @NonNull List<String> userDataValues) {
@@ -179,16 +200,98 @@
         return null;
     }
 
+    /**
+     * Calculates field classification scores in a batch.
+     *
+     * <p>A field classification score is a {@code float} representing how well an
+     * {@link AutofillValue} matches a expected value predicted by an autofill service
+     * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+     *
+     * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
+     * provide at least one default algorithm (which is used when the algorithm is not specified
+     * or is invalid), but it could provide more (in which case the algorithm name should be
+     * specified by the caller when calculating the scores).
+     *
+     * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
+     * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
+     *
+     * <pre>
+     * HashMap algorithms = new HashMap<>();
+     * algorithms.put("email", "EXACT_MATCH");
+     * algorithms.put("phone", "EXACT_MATCH");
+     *
+     * HashMap args = new HashMap<>();
+     * args.put("email", null);
+     * args.put("phone", null);
+     *
+     * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
+     * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
+     * Array.asList("email", "phone"), algorithms, args);
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * <p>If the same algorithm allows the caller to specify whether the comparisons should be
+     * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
+     *
+     * <pre>
+     * Bundle algorithmOptions = new Bundle();
+     * algorithmOptions.putBoolean("case_sensitive", false);
+     * args.put("phone", algorithmOptions);
+     *
+     * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
+     * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
+     * Array.asList("email", "phone"), algorithms, args);
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * @param actualValues values entered by the user.
+     * @param userDataValues values predicted from the user data.
+     * @param categoryIds category Ids correspoinding to userDataValues
+     * @param defaultAlgorithm default field classification algorithm
+     * @param algorithms array of field classification algorithms
+     * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
+     *
+     * {@hide}
+     */
+    @Nullable
+    @SystemApi
+    public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
+            @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
+            @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
+            @Nullable Map algorithms, @Nullable Map args) {
+        Log.e(TAG, "service implementation (" + getClass()
+                + " does not implement onCalculateScore()");
+        return null;
+    }
+
     private final class AutofillFieldClassificationServiceWrapper
             extends IAutofillFieldClassificationService.Stub {
         @Override
-        public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
-                List<AutofillValue> actualValues, String[] userDataValues)
-                        throws RemoteException {
+        public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
+                String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
+                Bundle defaultArgs, Map algorithms, Map args)
+                throws RemoteException {
             mHandler.sendMessage(obtainMessage(
-                    AutofillFieldClassificationService::getScores,
+                    AutofillFieldClassificationService::calculateScores,
                     AutofillFieldClassificationService.this,
-                    callback, algorithmName, algorithmArgs, actualValues, userDataValues));
+                    callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
+                    defaultArgs, algorithms, args));
         }
     }
 
diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
index 398557d..2cd24f9 100644
--- a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
+++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
@@ -20,6 +20,7 @@
 import android.os.RemoteCallback;
 import android.view.autofill.AutofillValue;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Service used to calculate match scores for Autofill Field Classification.
@@ -27,6 +28,8 @@
  * @hide
  */
 oneway interface IAutofillFieldClassificationService {
-    void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs,
-                   in List<AutofillValue> actualValues, in String[] userDataValues);
+    void calculateScores(in RemoteCallback callback, in List<AutofillValue> actualValues,
+                         in String[] userDataValues, in String[] categoryIds,
+                         in String defaultAlgorithm, in Bundle defaultArgs,
+                         in Map algorithms, in Map args);
 }
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
index fccb85b..37f1923 100644
--- a/core/java/android/service/autofill/UserData.java
+++ b/core/java/android/service/autofill/UserData.java
@@ -24,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.content.ContentResolver;
 import android.os.Bundle;
@@ -32,6 +33,7 @@
 import android.provider.Settings;
 import android.service.autofill.FieldClassification.Match;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.autofill.AutofillManager;
@@ -57,28 +59,57 @@
     private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
 
     private final String mId;
-    private final String mAlgorithm;
-    private final Bundle mAlgorithmArgs;
     private final String[] mCategoryIds;
     private final String[] mValues;
 
+    private final String mDefaultAlgorithm;
+    private final Bundle mDefaultArgs;
+    private final ArrayMap<String, String> mCategoryAlgorithms;
+    private final ArrayMap<String, Bundle> mCategoryArgs;
+
     private UserData(Builder builder) {
         mId = builder.mId;
-        mAlgorithm = builder.mAlgorithm;
-        mAlgorithmArgs = builder.mAlgorithmArgs;
         mCategoryIds = new String[builder.mCategoryIds.size()];
         builder.mCategoryIds.toArray(mCategoryIds);
         mValues = new String[builder.mValues.size()];
         builder.mValues.toArray(mValues);
+        builder.mValues.toArray(mValues);
+
+        mDefaultAlgorithm = builder.mDefaultAlgorithm;
+        mDefaultArgs = builder.mDefaultArgs;
+        mCategoryAlgorithms = builder.mCategoryAlgorithms;
+        mCategoryArgs = builder.mCategoryArgs;
     }
 
     /**
-     * Gets the name of the algorithm that is used to calculate
-     * {@link Match#getScore() match scores}.
+     * Gets the name of the default algorithm that is used to calculate
+     * {@link Match#getScore()} match scores}.
      */
     @Nullable
     public String getFieldClassificationAlgorithm() {
-        return mAlgorithm;
+        return mDefaultAlgorithm;
+    }
+
+    /** @hide */
+    public Bundle getDefaultFieldClassificationArgs() {
+        return mDefaultArgs;
+    }
+
+    /**
+     * Gets the name of the algorithm corresponding to the specific autofill category
+     * that is used to calculate {@link Match#getScore() match scores}
+     *
+     * @param categoryId autofill field category
+     *
+     * @return String name of algorithm, null if none found.
+     */
+    @Nullable
+    public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
+        Preconditions.checkNotNull(categoryId);
+        if (mCategoryAlgorithms == null || !mCategoryAlgorithms.containsKey(categoryId)) {
+            return null;
+        }
+        return mCategoryAlgorithms.get(categoryId);
     }
 
     /**
@@ -89,11 +120,6 @@
     }
 
     /** @hide */
-    public Bundle getAlgorithmArgs() {
-        return mAlgorithmArgs;
-    }
-
-    /** @hide */
     public String[] getCategoryIds() {
         return mCategoryIds;
     }
@@ -104,11 +130,29 @@
     }
 
     /** @hide */
+    @TestApi
+    public ArrayMap<String, String> getFieldClassificationAlgorithms() {
+        return mCategoryAlgorithms;
+    }
+
+    /** @hide */
+    public ArrayMap<String, Bundle> getFieldClassificationArgs() {
+        return mCategoryArgs;
+    }
+
+    /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("id: "); pw.print(mId);
-        pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm);
-        pw.print(" Args: "); pw.println(mAlgorithmArgs);
-
+        pw.print(prefix); pw.print("Default Algorithm: "); pw.print(mDefaultAlgorithm);
+        pw.print(prefix); pw.print("Default Args"); pw.print(mDefaultArgs);
+        if (mCategoryAlgorithms != null && mCategoryAlgorithms.size() > 0) {
+            pw.print(prefix); pw.print("Algorithms per category: ");
+            for (int i = 0; i < mCategoryAlgorithms.size(); i++) {
+                pw.print(prefix); pw.print(prefix); pw.print(mCategoryAlgorithms.keyAt(i));
+                pw.print(": "); pw.println(Helper.getRedacted(mCategoryAlgorithms.valueAt(i)));
+                pw.print("args="); pw.print(mCategoryArgs.get(mCategoryAlgorithms.keyAt(i)));
+            }
+        }
         // Cannot disclose field ids or values because they could contain PII
         pw.print(prefix); pw.print("Field ids size: "); pw.println(mCategoryIds.length);
         for (int i = 0; i < mCategoryIds.length; i++) {
@@ -139,8 +183,13 @@
         private final String mId;
         private final ArrayList<String> mCategoryIds;
         private final ArrayList<String> mValues;
-        private String mAlgorithm;
-        private Bundle mAlgorithmArgs;
+        private String mDefaultAlgorithm;
+        private Bundle mDefaultArgs;
+
+        // Map of autofill field categories to fleid classification algorithms and args
+        private ArrayMap<String, String> mCategoryAlgorithms;
+        private ArrayMap<String, Bundle> mCategoryArgs;
+
         private boolean mDestroyed;
 
         // Non-persistent array used to limit the number of unique ids.
@@ -148,7 +197,6 @@
         // Non-persistent array used to ignore duplaicated value/category pairs.
         private final ArraySet<String> mUniqueValueCategoryPairs;
 
-
         /**
          * Creates a new builder for the user data used for <a href="#FieldClassification">field
          * classification</a>.
@@ -169,7 +217,7 @@
          * {@link AutofillManager#getUserData()}).
          *
          * @param value value of the user data.
-         * @param categoryId string used to identify the category the value is associated with.
+         * @param categoryId autofill field category.
          *
          * @throws IllegalArgumentException if any of the following occurs:
          * <ul>
@@ -189,13 +237,15 @@
             mCategoryIds = new ArrayList<>(maxUserDataSize);
             mValues = new ArrayList<>(maxUserDataSize);
             mUniqueValueCategoryPairs = new ArraySet<>(maxUserDataSize);
+
             mUniqueCategoryIds = new ArraySet<>(getMaxCategoryCount());
 
             addMapping(value, categoryId);
         }
 
         /**
-         * Sets the algorithm used for <a href="#FieldClassification">field classification</a>.
+         * Sets the default algorithm used for
+         * <a href="#FieldClassification">field classification</a>.
          *
          * <p>The currently available algorithms can be retrieve through
          * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
@@ -212,8 +262,40 @@
         public Builder setFieldClassificationAlgorithm(@Nullable String name,
                 @Nullable Bundle args) {
             throwIfDestroyed();
-            mAlgorithm = name;
-            mAlgorithmArgs = args;
+            mDefaultAlgorithm = name;
+            mDefaultArgs = args;
+            return this;
+        }
+
+        /**
+         * Sets the algorithm used for <a href="#FieldClassification">field classification</a>
+         * for the specified category.
+         *
+         * <p>The currently available algorithms can be retrieved through
+         * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
+         *
+         * <p>If not set, the
+         * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
+         * used instead.
+         *
+         * @param categoryId autofill field category.
+         * @param name name of the algorithm or {@code null} to used default.
+         * @param args optional arguments to the algorithm.
+         *
+         * @return this builder
+         */
+        public Builder setFieldClassificationAlgorithmForCategory(@NonNull String categoryId,
+                @Nullable String name, @Nullable Bundle args) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(categoryId);
+            if (mCategoryAlgorithms == null) {
+                mCategoryAlgorithms = new ArrayMap<>(getMaxCategoryCount());
+            }
+            if (mCategoryArgs == null) {
+                mCategoryArgs = new ArrayMap<>(getMaxCategoryCount());
+            }
+            mCategoryAlgorithms.put(categoryId, name);
+            mCategoryArgs.put(categoryId, args);
             return this;
         }
 
@@ -317,8 +399,7 @@
     public String toString() {
         if (!sDebug) return super.toString();
 
-        final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId)
-                .append(", algorithm=").append(mAlgorithm);
+        final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId);
         // Cannot disclose category ids or values because they could contain PII
         builder.append(", categoryIds=");
         Helper.appendRedacted(builder, mCategoryIds);
@@ -341,8 +422,10 @@
         parcel.writeString(mId);
         parcel.writeStringArray(mCategoryIds);
         parcel.writeStringArray(mValues);
-        parcel.writeString(mAlgorithm);
-        parcel.writeBundle(mAlgorithmArgs);
+        parcel.writeString(mDefaultAlgorithm);
+        parcel.writeBundle(mDefaultArgs);
+        parcel.writeMap(mCategoryAlgorithms);
+        parcel.writeMap(mCategoryArgs);
     }
 
     public static final Parcelable.Creator<UserData> CREATOR =
@@ -355,10 +438,28 @@
             final String id = parcel.readString();
             final String[] categoryIds = parcel.readStringArray();
             final String[] values = parcel.readStringArray();
+            final String defaultAlgorithm = parcel.readString();
+            final Bundle defaultArgs = parcel.readBundle();
+            final ArrayMap<String, String> categoryAlgorithms = new ArrayMap<>();
+            parcel.readMap(categoryAlgorithms, String.class.getClassLoader());
+            final ArrayMap<String, Bundle> categoryArgs = new ArrayMap<>();
+            parcel.readMap(categoryArgs, Bundle.class.getClassLoader());
+
             final Builder builder = new Builder(id, values[0], categoryIds[0])
-                    .setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle());
+                    .setFieldClassificationAlgorithm(defaultAlgorithm, defaultArgs);
+
             for (int i = 1; i < categoryIds.length; i++) {
-                builder.add(values[i], categoryIds[i]);
+                String categoryId = categoryIds[i];
+                builder.add(values[i], categoryId);
+            }
+
+            final int size = categoryAlgorithms.size();
+            if (size > 0) {
+                for (int i = 0; i < size; i++) {
+                    final String categoryId = categoryAlgorithms.keyAt(i);
+                    builder.setFieldClassificationAlgorithmForCategory(categoryId,
+                            categoryAlgorithms.valueAt(i), categoryArgs.get(categoryId));
+                }
             }
             return builder.build();
         }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index dee6d90..ade7577 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -42,12 +42,15 @@
     static {
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("settings_audio_switcher", "true");
-        DEFAULT_FLAGS.put("settings_systemui_theme", "true");
         DEFAULT_FLAGS.put("settings_dynamic_homepage", "true");
         DEFAULT_FLAGS.put("settings_mobile_network_v2", "true");
-        DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
-        DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false");
+        DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
+        DEFAULT_FLAGS.put("settings_systemui_theme", "true");
+        DEFAULT_FLAGS.put("settings_wifi_dpp", "false");
+        DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false");
+        DEFAULT_FLAGS.put("settings_wifi_sharing", "false");
+        DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SAFETY_HUB, "false");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index ba5340c..fb4f9c0 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -38,9 +38,14 @@
     private final InsetsState mState = new InsetsState();
     private final Rect mFrame = new Rect();
     private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
+    private final ViewRootImpl mViewRoot;
 
     private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
 
+    public InsetsController(ViewRootImpl viewRoot) {
+        mViewRoot = viewRoot;
+    }
+
     void onFrameChanged(Rect frame) {
         mFrame.set(frame);
     }
@@ -49,8 +54,14 @@
         return mState;
     }
 
-    public void setState(InsetsState state) {
+    boolean onStateChanged(InsetsState state) {
+        if (mState.equals(state)) {
+            return false;
+        }
         mState.set(state);
+        applyLocalVisibilityOverride();
+        mViewRoot.notifyInsetsChanged();
+        return true;
     }
 
     /**
@@ -105,17 +116,28 @@
         }
     }
 
+    private void applyLocalVisibilityOverride() {
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i);
+            controller.applyLocalVisibilityOverride();
+        }
+    }
+
     @VisibleForTesting
     public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) {
         InsetsSourceConsumer controller = mSourceConsumers.get(type);
         if (controller != null) {
             return controller;
         }
-        controller = new InsetsSourceConsumer(type, mState, Transaction::new);
+        controller = new InsetsSourceConsumer(type, mState, Transaction::new, this);
         mSourceConsumers.put(type, controller);
         return controller;
     }
 
+    void notifyVisibilityChanged() {
+        mViewRoot.notifyInsetsChanged();
+    }
+
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix); pw.println("InsetsController:");
         mState.dump(prefix + "  ", pw);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index e74aa8d..ec85c4c 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -33,27 +33,31 @@
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetType int mType;
     private final InsetsState mState;
-    private @Nullable InsetsSourceControl mControl;
+    private final InsetsController mController;
+    private @Nullable InsetsSourceControl mSourceControl;
     private boolean mHidden;
 
     public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state,
-            Supplier<Transaction> transactionSupplier) {
+            Supplier<Transaction> transactionSupplier, InsetsController controller) {
         mType = type;
         mState = state;
         mTransactionSupplier = transactionSupplier;
+        mController = controller;
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
-        if (mControl == control) {
+        if (mSourceControl == control) {
             return;
         }
-        mControl = control;
+        mSourceControl = control;
         applyHiddenToControl();
+        applyLocalVisibilityOverride();
+        mController.notifyVisibilityChanged();
     }
 
     @VisibleForTesting
     public InsetsSourceControl getControl() {
-        return mControl;
+        return mSourceControl;
     }
 
     int getType() {
@@ -70,25 +74,36 @@
         setHidden(true);
     }
 
+    void applyLocalVisibilityOverride() {
+
+        // If we don't have control, we are not able to change the visibility.
+        if (mSourceControl == null) {
+            return;
+        }
+        mState.getSource(mType).setVisible(!mHidden);
+    }
+
     private void setHidden(boolean hidden) {
         if (mHidden == hidden) {
             return;
         }
         mHidden = hidden;
         applyHiddenToControl();
+        applyLocalVisibilityOverride();
+        mController.notifyVisibilityChanged();
     }
 
     private void applyHiddenToControl() {
-        if (mControl == null) {
+        if (mSourceControl == null) {
             return;
         }
 
         // TODO: Animation
         final Transaction t = mTransactionSupplier.get();
         if (mHidden) {
-            t.hide(mControl.getLeash());
+            t.hide(mSourceControl.getLeash());
         } else {
-            t.show(mControl.getLeash());
+            t.show(mSourceControl.getLeash());
         }
         t.apply();
     }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index bf1a005..34d076f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -299,6 +299,8 @@
     private boolean mEnabled;
     private boolean mRequested = true;
 
+    private FrameDrawingCallback mNextRtFrameCallback;
+
     ThreadedRenderer(Context context, boolean translucent, String name) {
         super();
         setName(name);
@@ -432,6 +434,17 @@
     }
 
     /**
+     * Registers a callback to be executed when the next frame is being drawn on RenderThread. This
+     * callback will be executed on a RenderThread worker thread, and only used for the next frame
+     * and thus it will only fire once.
+     *
+     * @param callback The callback to register.
+     */
+    void registerRtFrameCallback(FrameDrawingCallback callback) {
+        mNextRtFrameCallback = callback;
+    }
+
+    /**
      * Destroys all hardware rendering resources associated with the specified
      * view hierarchy.
      *
@@ -562,6 +575,15 @@
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
         updateViewTreeDisplayList(view);
 
+        // Consume and set the frame callback after we dispatch draw to the view above, but before
+        // onPostDraw below which may reset the callback for the next frame.  This ensures that
+        // updates to the frame callback during scroll handling will also apply in this frame.
+        final FrameDrawingCallback callback = mNextRtFrameCallback;
+        mNextRtFrameCallback = null;
+        if (callback != null) {
+            setFrameCallback(callback);
+        }
+
         if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
             RecordingCanvas canvas = mRootNode.startRecording(mSurfaceWidth, mSurfaceHeight);
             try {
@@ -619,10 +641,8 @@
      *
      * @param view The view to draw.
      * @param attachInfo AttachInfo tied to the specified view.
-     * @param callbacks Callbacks invoked when drawing happens.
      */
-    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks,
-            FrameDrawingCallback frameDrawingCallback) {
+    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
         final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
         choreographer.mFrameInfo.markDrawStart();
 
@@ -642,9 +662,6 @@
             attachInfo.mPendingAnimatingRenderNodes = null;
         }
 
-        if (frameDrawingCallback != null) {
-            setFrameCallback(frameDrawingCallback);
-        }
         int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
             setEnabled(false);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cb47886..e36e258 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -200,8 +200,6 @@
     static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
     static boolean sFirstDrawComplete = false;
 
-    private FrameDrawingCallback mNextRtFrameCallback;
-
     /**
      * Callback for notifying about global configuration changes.
      */
@@ -464,7 +462,7 @@
     final DisplayCutout.ParcelableWrapper mPendingDisplayCutout =
             new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT);
     boolean mPendingAlwaysConsumeNavBar;
-    private InsetsState mPendingInsets = new InsetsState();
+    private InsetsState mTempInsets = new InsetsState();
     final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
             = new ViewTreeObserver.InternalInsetsInfo();
 
@@ -552,7 +550,7 @@
             InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                     new InputEventConsistencyVerifier(this, 0) : null;
 
-    private final InsetsController mInsetsController = new InsetsController();
+    private final InsetsController mInsetsController = new InsetsController(this);
 
     static final class SystemUiVisibilityInfo {
         int seq;
@@ -823,7 +821,7 @@
                             getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
-                            mInsetsController.getState());
+                            mTempInsets);
                     setFrame(mTmpFrame);
                 } catch (RemoteException e) {
                     mAdded = false;
@@ -851,7 +849,7 @@
                 mAttachInfo.mAlwaysConsumeNavBar =
                         (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
                 mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
-                mPendingInsets = mInsetsController.getState();
+                mInsetsController.onStateChanged(mTempInsets);
                 if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                 if (res < WindowManagerGlobal.ADD_OKAY) {
                     mAttachInfo.mRootView = null;
@@ -1052,7 +1050,9 @@
      * @param callback The callback to register.
      */
     public void registerRtFrameCallback(FrameDrawingCallback callback) {
-        mNextRtFrameCallback = callback;
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(callback);
+        }
     }
 
     @UnsupportedAppUsage
@@ -1342,6 +1342,19 @@
         scheduleTraversals();
     }
 
+    void notifyInsetsChanged() {
+        if (!USE_NEW_INSETS) {
+            return;
+        }
+        mApplyInsetsRequested = true;
+
+        // If this changes during traversal, no need to schedule another one as it will dispatch it
+        // during the current traversal.
+        if (!mIsInTraversal) {
+            scheduleTraversals();
+        }
+    }
+
     @Override
     public void requestLayout() {
         if (!mHandlingLayoutInLayoutRequest) {
@@ -2027,9 +2040,6 @@
                 if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
                     insetsChanged = true;
                 }
-                if (!mPendingInsets.equals(mInsetsController.getState())) {
-                    insetsChanged = true;
-                }
                 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                         || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                     windowSizeMayChange = true;
@@ -2223,8 +2233,6 @@
                         mAttachInfo.mStableInsets);
                 final boolean cutoutChanged = !mPendingDisplayCutout.equals(
                         mAttachInfo.mDisplayCutout);
-                final boolean insetsStateChanged = !mPendingInsets.equals(
-                        mInsetsController.getState());
                 final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
                 final boolean surfaceSizeChanged = (relayoutResult
                         & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
@@ -2262,10 +2270,6 @@
                     mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
                     contentInsetsChanged = true;
                 }
-                if (insetsStateChanged) {
-                    mInsetsController.setState(mPendingInsets);
-                    contentInsetsChanged = true;
-                }
                 if (contentInsetsChanged || mLastSystemUiVisibility !=
                         mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
                         || mLastOverscanRequested != mAttachInfo.mOverscanRequested
@@ -3534,10 +3538,7 @@
 
                 useAsyncReport = true;
 
-                // draw(...) might invoke post-draw, which might register the next callback already.
-                final FrameDrawingCallback callback = mNextRtFrameCallback;
-                mNextRtFrameCallback = null;
-                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
+                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
                 // wrong (an invalidate posted right before we destroyed the hardware surface
@@ -4378,22 +4379,12 @@
                     }
                     break;
                 case MSG_INSETS_CHANGED:
-                    mPendingInsets = (InsetsState) msg.obj;
-
-                    // TODO: Full traversal not needed here.
-                    if (USE_NEW_INSETS) {
-                        requestLayout();
-                    }
+                    mInsetsController.onStateChanged((InsetsState) msg.obj);
                     break;
                 case MSG_INSETS_CONTROL_CHANGED: {
                     SomeArgs args = (SomeArgs) msg.obj;
-                    mPendingInsets = (InsetsState) args.arg1;
                     mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2);
-
-                    // TODO: Full traversal not necessarily needed here.
-                    if (USE_NEW_INSETS) {
-                        requestLayout();
-                    }
+                    mInsetsController.onStateChanged((InsetsState) args.arg1);
                     break;
                 }
                 case MSG_WINDOW_MOVED:
@@ -6794,7 +6785,7 @@
                 insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                 mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                 mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
-                mPendingMergedConfiguration, mSurface, mPendingInsets);
+                mPendingMergedConfiguration, mSurface, mTempInsets);
 
         mPendingAlwaysConsumeNavBar =
                 (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
@@ -6811,7 +6802,7 @@
             mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets);
         }
         setFrame(mTmpFrame);
-
+        mInsetsController.onStateChanged(mTempInsets);
         return relayoutResult;
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 48831da..1889692 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -422,7 +422,7 @@
 
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.println("IntelligenceManager");
+        pw.print(prefix); pw.println("ContentCaptureManager");
         final String prefix2 = prefix + "  ";
         pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
         pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 8bc90a8..a27dbea 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -39,6 +39,7 @@
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
 import android.provider.MetadataReader;
+import android.system.Int64Ref;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,6 +54,12 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -122,18 +129,56 @@
             throw new FileNotFoundException("Can't find the file for documentId: " + documentId);
         }
 
+        final String mimeType = getDocumentType(documentId);
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            final Int64Ref treeCount = new Int64Ref(0);
+            final Int64Ref treeSize = new Int64Ref(0);
+            try {
+                final Path path = FileSystems.getDefault().getPath(file.getAbsolutePath());
+                Files.walkFileTree(path, new FileVisitor<Path>() {
+                    @Override
+                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+                        treeCount.value += 1;
+                        treeSize.value += attrs.size();
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFileFailed(Path file, IOException exc) {
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
+                        return FileVisitResult.CONTINUE;
+                    }
+                });
+            } catch (IOException e) {
+                Log.e(TAG, "An error occurred retrieving the metadata", e);
+                return null;
+            }
+
+            final Bundle res = new Bundle();
+            res.putLong(DocumentsContract.METADATA_TREE_COUNT, treeCount.value);
+            res.putLong(DocumentsContract.METADATA_TREE_SIZE, treeSize.value);
+            return res;
+        }
+
         if (!file.isFile()) {
             Log.w(TAG, "Can't stream non-regular file. Returning empty metadata.");
             return null;
         }
-
         if (!file.canRead()) {
             Log.w(TAG, "Can't stream non-readable file. Returning empty metadata.");
             return null;
         }
-
-        String mimeType = getDocumentType(documentId);
         if (!MetadataReader.isSupportedMimeType(mimeType)) {
+            Log.w(TAG, "Unsupported type " + mimeType + ". Returning empty metadata.");
             return null;
         }
 
@@ -562,7 +607,8 @@
     }
 
     protected boolean typeSupportsMetadata(String mimeType) {
-        return MetadataReader.isSupportedMimeType(mimeType);
+        return MetadataReader.isSupportedMimeType(mimeType)
+                || Document.MIME_TYPE_DIR.equals(mimeType);
     }
 
     protected final File getFileForDocId(String docId) throws FileNotFoundException {
diff --git a/core/java/com/android/internal/os/AppIdToPackageMap.java b/core/java/com/android/internal/os/AppIdToPackageMap.java
new file mode 100644
index 0000000..65aa989
--- /dev/null
+++ b/core/java/com/android/internal/os/AppIdToPackageMap.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.app.AppGlobals;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Maps AppIds to their package names. */
+public final class AppIdToPackageMap {
+    private final Map<Integer, String> mAppIdToPackageMap;
+
+    @VisibleForTesting
+    public AppIdToPackageMap(Map<Integer, String> appIdToPackageMap) {
+        mAppIdToPackageMap = appIdToPackageMap;
+    }
+
+    /** Creates a new {@link AppIdToPackageMap} for currently installed packages. */
+    public static AppIdToPackageMap getSnapshot() {
+        List<PackageInfo> packages;
+        try {
+            packages = AppGlobals.getPackageManager()
+                    .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE,
+                            UserHandle.USER_SYSTEM).getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        final Map<Integer, String> map = new HashMap<>();
+        for (PackageInfo pkg : packages) {
+            final int uid = pkg.applicationInfo.uid;
+            if (pkg.sharedUserId != null && map.containsKey(uid)) {
+                // Use sharedUserId string as package name if there are collisions
+                map.put(uid, "shared:" + pkg.sharedUserId);
+            } else {
+                map.put(uid, pkg.packageName);
+            }
+        }
+        return new AppIdToPackageMap(map);
+    }
+
+    /** Maps the AppId to a package name. */
+    public String mapAppId(int appId) {
+        String pkgName = mAppIdToPackageMap.get(appId);
+        return pkgName == null ? String.valueOf(appId) : pkgName;
+    }
+
+    /** Maps the UID to a package name. */
+    public String mapUid(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        final String pkgName = mAppIdToPackageMap.get(appId);
+        final String uidStr = UserHandle.formatUid(uid);
+        return pkgName == null ? uidStr : pkgName + '/' + uidStr;
+    }
+}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index ff34036..5465485 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -21,8 +21,6 @@
 import android.os.Binder;
 import android.os.Process;
 import android.os.SystemClock;
-import android.os.ThreadLocalWorkSource;
-import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.Pair;
@@ -62,7 +60,11 @@
     private static final int CALL_SESSIONS_POOL_SIZE = 100;
     private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
     private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
+    // Default values for overflow entry. The work source uid does not use a default value in order
+    // to have on overflow entry per work source uid.
     private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class;
+    private static final boolean OVERFLOW_SCREEN_INTERACTIVE = false;
+    private static final int OVERFLOW_DIRECT_CALLING_UID = -1;
     private static final int OVERFLOW_TRANSACTION_CODE = -1;
 
     // Whether to collect all the data: cpu + exceptions + reply/request sizes.
@@ -106,7 +108,7 @@
 
     @Override
     @Nullable
-    public CallSession callStarted(Binder binder, int code) {
+    public CallSession callStarted(Binder binder, int code, int workSourceUid) {
         if (mDeviceState == null || mDeviceState.isCharging()) {
             return null;
         }
@@ -130,19 +132,21 @@
     }
 
     @Override
-    public void callEnded(@Nullable CallSession s, int parcelRequestSize, int parcelReplySize) {
+    public void callEnded(@Nullable CallSession s, int parcelRequestSize,
+            int parcelReplySize, int workSourceUid) {
         if (s == null) {
             return;
         }
 
-        processCallEnded(s, parcelRequestSize, parcelReplySize);
+        processCallEnded(s, parcelRequestSize, parcelReplySize, workSourceUid);
 
         if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
             mCallSessionsPool.add(s);
         }
     }
 
-    private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
+    private void processCallEnded(CallSession s,
+            int parcelRequestSize, int parcelReplySize, int workSourceUid) {
         // Non-negative time signals we need to record data for this call.
         final boolean recordCall = s.cpuTimeStarted >= 0;
         final long duration;
@@ -155,7 +159,6 @@
             latencyDuration = 0;
         }
         final int callingUid = getCallingUid();
-        final int workSourceUid = getWorkSourceUid();
 
         synchronized (mLock) {
             // This was already checked in #callStart but check again while synchronized.
@@ -356,14 +359,13 @@
     }
 
     /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
-    public void dump(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, boolean verbose) {
+    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
         synchronized (mLock) {
-            dumpLocked(pw, appIdToPkgNameMap, verbose);
+            dumpLocked(pw, packageMap, verbose);
         }
     }
 
-    private void dumpLocked(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap,
-            boolean verbose) {
+    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
         long totalCallsCount = 0;
         long totalRecordedCallsCount = 0;
         long totalCpuTime = 0;
@@ -397,9 +399,9 @@
         for (ExportedCallStat e : exportedCallStats) {
             sb.setLength(0);
             sb.append("    ")
-                    .append(uidToString(e.callingUid, appIdToPkgNameMap))
+                    .append(packageMap.mapUid(e.callingUid))
                     .append(',')
-                    .append(uidToString(e.workSourceUid, appIdToPkgNameMap))
+                    .append(packageMap.mapUid(e.workSourceUid))
                     .append(',').append(e.className)
                     .append('#').append(e.methodName)
                     .append(',').append(e.screenInteractive)
@@ -420,7 +422,7 @@
         final List<UidEntry> summaryEntries = verbose ? entries
                 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
         for (UidEntry entry : summaryEntries) {
-            String uidStr = uidToString(entry.workSourceUid, appIdToPkgNameMap);
+            String uidStr = packageMap.mapUid(entry.workSourceUid);
             pw.println(String.format("  %10d %3.0f%% %8d %8d %s",
                     entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
                     entry.recordedCallCount, entry.callCount, uidStr));
@@ -448,13 +450,6 @@
         }
     }
 
-    private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
-        final int appId = UserHandle.getAppId(uid);
-        final String pkgName = pkgNameMap == null ? null : pkgNameMap.get(appId);
-        final String uidStr = UserHandle.formatUid(uid);
-        return pkgName == null ? uidStr : pkgName + '/' + uidStr;
-    }
-
     protected long getThreadTimeMicro() {
         return SystemClock.currentThreadTimeMicro();
     }
@@ -463,10 +458,6 @@
         return Binder.getCallingUid();
     }
 
-    protected int getWorkSourceUid() {
-        return ThreadLocalWorkSource.getUid();
-    }
-
     protected long getElapsedRealtimeMicro() {
         return SystemClock.elapsedRealtimeNanos() / 1000;
     }
@@ -669,14 +660,16 @@
             // Only create CallStat if it's a new entry, otherwise update existing instance.
             if (mapCallStat == null) {
                 if (maxCallStatsReached) {
-                    mapCallStat = get(callingUid, OVERFLOW_BINDER, OVERFLOW_TRANSACTION_CODE,
-                            screenInteractive);
+                    mapCallStat = get(OVERFLOW_DIRECT_CALLING_UID, OVERFLOW_BINDER,
+                            OVERFLOW_TRANSACTION_CODE, OVERFLOW_SCREEN_INTERACTIVE);
                     if (mapCallStat != null) {
                         return mapCallStat;
                     }
 
+                    callingUid = OVERFLOW_DIRECT_CALLING_UID;
                     binderClass = OVERFLOW_BINDER;
                     transactionCode = OVERFLOW_TRANSACTION_CODE;
+                    screenInteractive = OVERFLOW_SCREEN_INTERACTIVE;
                 }
 
                 mapCallStat = new CallStat(callingUid, binderClass, transactionCode,
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 0155067..5b69979 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -22,7 +22,6 @@
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.SparseIntArray;
 
 import com.android.internal.util.Preconditions;
@@ -86,6 +85,22 @@
         boolean exceptionThrown;
     }
 
+
+    /**
+     * Responsible for resolving a work source.
+     */
+    @FunctionalInterface
+    public interface WorkSourceProvider {
+        /**
+         * <p>This method is called in a critical path of the binder transaction.
+         * <p>The implementation should never execute a binder call since it is called during a
+         * binder transaction.
+         *
+         * @return the uid of the process to attribute the binder transaction to.
+         */
+        int resolveWorkSourceUid();
+    }
+
     /**
      * Allows to track various steps of an API call.
      */
@@ -95,14 +110,16 @@
          *
          * @return a CallSession to pass to the callEnded method.
          */
-        CallSession callStarted(Binder binder, int code);
+        CallSession callStarted(Binder binder, int code, int workSourceUid);
 
         /**
          * Called when a binder call stops.
          *
-         * <li>This method will be called even when an exception is thrown.
+         * <li>This method will be called even when an exception is thrown by the binder stub
+         * implementation.
          */
-        void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize);
+        void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize,
+                int workSourceUid);
 
         /**
          * Called if an exception is thrown while executing the binder transaction.
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index b97a9fa..b00e6fd 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,7 +25,6 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
 import android.text.TextUtils;
@@ -78,10 +77,23 @@
 
     final ArrayList<SplitPermissionInfo> mSplitPermissions = new ArrayList<>();
 
+    public static final class SharedLibraryEntry {
+        public final String name;
+        public final String filename;
+        public final String[] dependencies;
+
+        SharedLibraryEntry(String name, String filename, String[] dependencies) {
+            this.name = name;
+            this.filename = filename;
+            this.dependencies = dependencies;
+        }
+    }
+
     // These are the built-in shared libraries that were read from the
-    // system configuration files.  Keys are the library names; strings are the
-    // paths to the libraries.
-    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
+    // system configuration files. Keys are the library names; values are
+    // the individual entries that contain information such as filename
+    // and dependencies.
+    final ArrayMap<String, SharedLibraryEntry> mSharedLibraries = new ArrayMap<>();
 
     // These are the features this devices supports that were read from the
     // system configuration files.
@@ -200,7 +212,7 @@
         return mSplitPermissions;
     }
 
-    public ArrayMap<String, String> getSharedLibraries() {
+    public ArrayMap<String, SharedLibraryEntry> getSharedLibraries() {
         return mSharedLibraries;
     }
 
@@ -497,6 +509,7 @@
                 } else if ("library".equals(name) && allowLibs) {
                     String lname = parser.getAttributeValue(null, "name");
                     String lfile = parser.getAttributeValue(null, "file");
+                    String ldependency = parser.getAttributeValue(null, "dependency");
                     if (lname == null) {
                         Slog.w(TAG, "<library> without name in " + permFile + " at "
                                 + parser.getPositionDescription());
@@ -505,11 +518,12 @@
                                 + parser.getPositionDescription());
                     } else {
                         //Log.i(TAG, "Got library " + lname + " in " + lfile);
-                        mSharedLibraries.put(lname, lfile);
+                        SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
+                                ldependency == null ? new String[0] : ldependency.split(":"));
+                        mSharedLibraries.put(lname, entry);
                     }
                     XmlUtils.skipCurrentTag(parser);
                     continue;
-
                 } else if ("feature".equals(name) && allowFeatures) {
                     String fname = parser.getAttributeValue(null, "name");
                     int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 98bc735..8a28034 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -225,7 +225,8 @@
                                     gAudioFormatCstor,
                                     audioFormatFromNative(event->audio_config.format),
                                     event->audio_config.sample_rate,
-                                    inChannelMaskFromNative(event->audio_config.channel_mask));
+                                    inChannelMaskFromNative(event->audio_config.channel_mask),
+                                    (jint)0 /* channelIndexMask */);
 
     }
     if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 7ebb2b2..516093e 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -479,6 +479,24 @@
 }
 
 // ----------------------------------------------------------------------------
+static jboolean
+android_media_AudioTrack_is_direct_output_supported(JNIEnv *env, jobject thiz,
+                                             jint encoding, jint sampleRate,
+                                             jint channelMask, jint channelIndexMask,
+                                             jint contentType, jint usage, jint flags) {
+    audio_config_base_t config = {};
+    audio_attributes_t attributes = {};
+    config.format = static_cast<audio_format_t>(audioFormatToNative(encoding));
+    config.sample_rate = static_cast<uint32_t>(sampleRate);
+    config.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask);
+    attributes.content_type = static_cast<audio_content_type_t>(contentType);
+    attributes.usage = static_cast<audio_usage_t>(usage);
+    attributes.flags = static_cast<audio_flags_mask_t>(flags);
+    // ignore source and tags attributes as they don't affect querying whether output is supported
+    return AudioTrack::isDirectOutputSupported(config, attributes);
+}
+
+// ----------------------------------------------------------------------------
 static void
 android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
 {
@@ -1297,6 +1315,9 @@
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
     // name,              signature,     funcPtr
+    {"native_is_direct_output_supported",
+                             "(IIIIIII)Z",
+                                         (void *)android_media_AudioTrack_is_direct_output_supported},
     {"native_start",         "()V",      (void *)android_media_AudioTrack_start},
     {"native_stop",          "()V",      (void *)android_media_AudioTrack_stop},
     {"native_pause",         "()V",      (void *)android_media_AudioTrack_pause},
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0286730..4aa88e7 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -276,15 +276,17 @@
     }
   }
 
-  // We don't want core dumps, though, so set the soft limit on core dump size
-  // to 0 without changing the hard limit.
-  rlimit rl;
-  if (getrlimit(RLIMIT_CORE, &rl) == -1) {
-    ALOGE("getrlimit(RLIMIT_CORE) failed");
-  } else {
-    rl.rlim_cur = 0;
-    if (setrlimit(RLIMIT_CORE, &rl) == -1) {
-      ALOGE("setrlimit(RLIMIT_CORE) failed");
+  // Set the core dump size to zero unless wanted (see also coredump_setup in build/envsetup.sh).
+  if (!GetBoolProperty("persist.zygote.core_dump", false)) {
+    // Set the soft limit on core dump size to 0 without changing the hard limit.
+    rlimit rl;
+    if (getrlimit(RLIMIT_CORE, &rl) == -1) {
+      ALOGE("getrlimit(RLIMIT_CORE) failed");
+    } else {
+      rl.rlim_cur = 0;
+      if (setrlimit(RLIMIT_CORE, &rl) == -1) {
+        ALOGE("setrlimit(RLIMIT_CORE) failed");
+      }
     }
   }
 }
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index ed040f4..f7dcee2 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -206,6 +206,8 @@
     optional bool can_change_mode = 3;
     optional bool can_change_power_role = 4;
     optional bool can_change_data_role = 5;
+    optional int64 connected_at_millis = 6;
+    optional int64 last_connect_duration_millis = 7;
 }
 
 message UsbPortProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 5726d9a..c7a6b68 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -51,7 +51,7 @@
   SET_ALWAYS_ON_VPN_PACKAGE = 26;
   SET_PERMITTED_INPUT_METHODS = 27;
   SET_PERMITTED_ACCESSIBILITY_SERVICES = 28;
-  SET_SCREEN_CAPTURE_DISABLE = 29;
+  SET_SCREEN_CAPTURE_DISABLED = 29;
   SET_CAMERA_DISABLED = 30;
   QUERY_SUMMARY_FOR_USER = 31;
   QUERY_SUMMARY = 32;
@@ -64,7 +64,7 @@
   SET_ORGANIZATION_COLOR = 39;
   SET_PROFILE_NAME = 40;
   SET_USER_ICON = 41;
-  SET_DEVICE_OWNER_LOCKSCREEN_INFO = 42;
+  SET_DEVICE_OWNER_LOCK_SCREEN_INFO = 42;
   SET_SHORT_SUPPORT_MESSAGE = 43;
   SET_LONG_SUPPORT_MESSAGE = 44;
   SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED = 45;
@@ -139,4 +139,6 @@
   SET_GLOBAL_SETTING = 111;
   PM_IS_INSTALLER_DEVICE_OWNER_OR_AFFILIATED_PROFILE_OWNER = 112;
   PM_UNINSTALL = 113;
+  WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114;
+  WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4dedd49..9c55c75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -897,7 +897,7 @@
         android:protectionLevel="dangerous"
         android:usageInfoRequired="true" />
 
-    <!-- @hide @SystemApi
+    <!-- @hide @SystemApi @TestApi
          Allows an application to modify OBB files visible to other apps. -->
     <permission android:name="android.permission.WRITE_OBB"
         android:protectionLevel="signature|privileged" />
@@ -2087,10 +2087,9 @@
          <p>This permission should <em>only</em> be requested by the platform
          document management app.  This permission cannot be granted to
          third-party apps.
-         <p>Protection level: signature
     -->
     <permission android:name="android.permission.MANAGE_DOCUMENTS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @hide Allows an application to cache content.
          <p>Not for use by third-party applications.
@@ -2171,6 +2170,13 @@
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
         android:protectionLevel="signature|installer" />
 
+    <!-- @SystemApi Allows an application to start an activity within its managed profile from
+         the personal profile.
+         This permission is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
          third party applications. -->
@@ -2206,9 +2212,9 @@
         android:description="@string/permdesc_reorderTasks"
         android:protectionLevel="normal" />
 
-    <!-- @hide Allows an application to change to remove/kill tasks -->
+    <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
     <permission android:name="android.permission.REMOVE_TASKS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
@@ -3344,6 +3350,11 @@
     <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
                 android:protectionLevel="signature|installer" />
 
+    <!-- @SystemApi Allows an application to observe role holder changes.
+         @hide -->
+    <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
+                android:protectionLevel="signature|installer" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f8004ea..ab4bd05 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -271,6 +271,9 @@
         <!-- Additional flag from base permission type: this permission will be granted to the
              wellbeing app, as defined by the OEM. -->
         <flag name="wellbeing" value="0x20000" />
+        <!-- Additional flag from base permission type: this permission can be automatically
+            granted to the document manager -->
+        <flag name="documenter" value="0x40000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceService.java b/core/tests/coretests/src/android/os/BinderWorkSourceService.java
index ac8d7ab..3bca5fb 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceService.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceService.java
@@ -25,9 +25,25 @@
 public class BinderWorkSourceService extends Service {
     private final IBinderWorkSourceService.Stub mBinder =
             new IBinderWorkSourceService.Stub() {
+        public int getBinderCallingUid() {
+            return Binder.getCallingUid();
+        }
+
         public int getIncomingWorkSourceUid() {
             return Binder.getCallingWorkSourceUid();
         }
+
+        public int getThreadLocalWorkSourceUid() {
+            return ThreadLocalWorkSource.getUid();
+        }
+
+        public void setWorkSourceProvider(int uid) {
+            Binder.setWorkSourceProvider(() -> uid);
+        }
+
+        public void clearWorkSourceProvider() {
+            Binder.setWorkSourceProvider(Binder::getCallingUid);
+        }
     };
 
     public IBinder onBind(Intent intent) {
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index ec17803..d1dbd3c 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -163,4 +163,24 @@
         // Initial work source restored.
         assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
+
+    @Test
+    public void workSourceProvider_default() throws Exception {
+        Binder.clearCallingWorkSource();
+        mService.clearWorkSourceProvider();
+        assertEquals(Process.myUid(), mService.getThreadLocalWorkSourceUid());
+    }
+
+    @Test
+    public void workSourceProvider_customProvider() throws Exception {
+        Binder.clearCallingWorkSource();
+        mService.clearWorkSourceProvider();
+        // Calling uid should not be used.
+        mService.setWorkSourceProvider(SECOND_UID);
+        try {
+            assertEquals(SECOND_UID, mService.getThreadLocalWorkSourceUid());
+        } finally {
+            mService.clearWorkSourceProvider();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl b/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl
index 05d4e82..9322400 100644
--- a/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl
+++ b/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl
@@ -18,4 +18,8 @@
 
 interface IBinderWorkSourceService {
     int getIncomingWorkSourceUid();
+    int getBinderCallingUid();
+    int getThreadLocalWorkSourceUid();
+    void setWorkSourceProvider(int uid);
+    void clearWorkSourceProvider();
 }
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index da17b56..a828f44 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -16,13 +16,32 @@
 
 package android.os;
 
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
 public class PowerManagerTest extends AndroidTestCase {
 
     private PowerManager mPm;
+    private UiDevice mUiDevice;
+    private Executor mExec = Executors.newSingleThreadExecutor();
+    @Mock
+    private PowerManager.ThermalStatusCallback mCallback;
+    private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
 
     /**
      * Setup any common data for the upcoming tests.
@@ -30,7 +49,18 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mUiDevice.executeShellCommand("cmd thermalservice override-status 0");
+    }
+
+    /**
+     * Reset data for the upcoming tests.
+     */
+    @After
+    public void tearDown() throws Exception {
+        mUiDevice.executeShellCommand("cmd thermalservice reset");
     }
 
     /**
@@ -137,4 +167,35 @@
 
         // TODO: Threaded test (needs handler) to make sure timed wakelocks work too
     }
+
+    @Test
+    public void testGetThermalStatus() throws Exception {
+        int status = 0;
+        assertEquals(status, mPm.getCurrentThermalStatus());
+        status = 3;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + Integer.toString(status));
+        assertEquals(status, mPm.getCurrentThermalStatus());
+    }
+
+    @Test
+    public void testThermalStatusCallback() throws Exception {
+        mPm.registerThermalStatusCallback(mCallback, mExec);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(0);
+        reset(mCallback);
+        int status = 3;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + Integer.toString(status));
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(status);
+        reset(mCallback);
+        mPm.unregisterThermalStatusCallback(mCallback);
+        status = 2;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + Integer.toString(status));
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onStatusChange(status);
+
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 49a7555..3a37fb6 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -415,6 +415,7 @@
                     Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
                     Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
                     Settings.Global.SHOW_TEMPERATURE_WARNING,
+                    Settings.Global.SIGNED_CONFIG_VERSION,
                     Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
                     Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
                     Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED,
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
index 8d6fbd5..61ab152 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
@@ -28,7 +28,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-// TODO(b/73862682): Add tests for RecoveryCertPath
+import java.security.cert.CertPath;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class KeyChainSnapshotTest {
@@ -43,35 +44,41 @@
     private static final int USER_SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN;
     private static final String KEY_ALIAS = "steph";
     private static final byte[] KEY_MATERIAL = new byte[] { 3, 5, 7, 9, 1 };
+    private static final CertPath CERT_PATH = TestData.getThmCertPath();
 
     @Test
-    public void build_setsCounterId() {
+    public void build_setsCounterId() throws Exception {
         assertEquals(COUNTER_ID, createKeyChainSnapshot().getCounterId());
     }
 
     @Test
-    public void build_setsSnapshotVersion() {
+    public void build_setsSnapshotVersion() throws Exception {
         assertEquals(SNAPSHOT_VERSION, createKeyChainSnapshot().getSnapshotVersion());
     }
 
     @Test
-    public void build_setsMaxAttempts() {
+    public void build_setsMaxAttempts() throws Exception {
         assertEquals(MAX_ATTEMPTS, createKeyChainSnapshot().getMaxAttempts());
     }
 
     @Test
-    public void build_setsServerParams() {
+    public void build_setsServerParams() throws Exception {
         assertArrayEquals(SERVER_PARAMS, createKeyChainSnapshot().getServerParams());
     }
 
     @Test
-    public void build_setsRecoveryKeyBlob() {
+    public void build_setsRecoveryKeyBlob() throws Exception {
         assertArrayEquals(RECOVERY_KEY_BLOB,
                 createKeyChainSnapshot().getEncryptedRecoveryKeyBlob());
     }
 
     @Test
-    public void build_setsKeyChainProtectionParams() {
+    public void build_setsCertPath() throws Exception {
+        assertEquals(CERT_PATH, createKeyChainSnapshot().getTrustedHardwareCertPath());
+    }
+
+    @Test
+    public void build_setsKeyChainProtectionParams() throws Exception {
         KeyChainSnapshot snapshot = createKeyChainSnapshot();
 
         assertEquals(1, snapshot.getKeyChainProtectionParams().size());
@@ -85,7 +92,7 @@
     }
 
     @Test
-    public void build_setsWrappedApplicationKeys() {
+    public void build_setsWrappedApplicationKeys() throws Exception {
         KeyChainSnapshot snapshot = createKeyChainSnapshot();
 
         assertEquals(1, snapshot.getWrappedApplicationKeys().size());
@@ -95,42 +102,49 @@
     }
 
     @Test
-    public void writeToParcel_writesCounterId() {
+    public void writeToParcel_writesCounterId() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertEquals(COUNTER_ID, snapshot.getCounterId());
     }
 
     @Test
-    public void writeToParcel_writesSnapshotVersion() {
+    public void writeToParcel_writesSnapshotVersion() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertEquals(SNAPSHOT_VERSION, snapshot.getSnapshotVersion());
     }
 
     @Test
-    public void writeToParcel_writesMaxAttempts() {
+    public void writeToParcel_writesMaxAttempts() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertEquals(MAX_ATTEMPTS, snapshot.getMaxAttempts());
     }
 
     @Test
-    public void writeToParcel_writesServerParams() {
+    public void writeToParcel_writesServerParams() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertArrayEquals(SERVER_PARAMS, snapshot.getServerParams());
     }
 
     @Test
-    public void writeToParcel_writesKeyRecoveryBlob() {
+    public void writeToParcel_writesKeyRecoveryBlob() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertArrayEquals(RECOVERY_KEY_BLOB, snapshot.getEncryptedRecoveryKeyBlob());
     }
 
     @Test
-    public void writeToParcel_writesKeyChainProtectionParams() {
+    public void writeToParcel_writesCertPath() throws Exception {
+        KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
+
+        assertEquals(CERT_PATH, snapshot.getTrustedHardwareCertPath());
+    }
+
+    @Test
+    public void writeToParcel_writesKeyChainProtectionParams() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertEquals(1, snapshot.getKeyChainProtectionParams().size());
@@ -144,7 +158,7 @@
     }
 
     @Test
-    public void writeToParcel_writesWrappedApplicationKeys() {
+    public void writeToParcel_writesWrappedApplicationKeys() throws Exception {
         KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot());
 
         assertEquals(1, snapshot.getWrappedApplicationKeys().size());
@@ -153,7 +167,7 @@
         assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial());
     }
 
-    private static KeyChainSnapshot createKeyChainSnapshot() {
+    private static KeyChainSnapshot createKeyChainSnapshot() throws Exception {
         return new KeyChainSnapshot.Builder()
                 .setCounterId(COUNTER_ID)
                 .setSnapshotVersion(SNAPSHOT_VERSION)
@@ -162,6 +176,7 @@
                 .setEncryptedRecoveryKeyBlob(RECOVERY_KEY_BLOB)
                 .setKeyChainProtectionParams(Lists.newArrayList(createKeyChainProtectionParams()))
                 .setWrappedApplicationKeys(Lists.newArrayList(createWrappedApplicationKey()))
+                .setTrustedHardwareCertPath(CERT_PATH)
                 .build();
     }
 
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
new file mode 100644
index 0000000..dd8cd8d
--- /dev/null
+++ b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.keystore.recovery;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.cert.CertificateException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecoveryCertPathTest {
+
+    @Test
+    public void createRecoveryCertPath_getCertPath_succeeds() throws Exception {
+        RecoveryCertPath recoveryCertPath = RecoveryCertPath.createRecoveryCertPath(
+                TestData.getThmCertPath());
+        assertEquals(TestData.getThmCertPath(), recoveryCertPath.getCertPath());
+    }
+
+    @Test
+    public void getCertPath_throwsIfCannnotDecode() {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeByteArray(new byte[]{0, 1, 2, 3});
+        parcel.setDataPosition(0);
+        RecoveryCertPath recoveryCertPath = RecoveryCertPath.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        try {
+            recoveryCertPath.getCertPath();
+            fail("Did not throw when attempting to decode invalid cert path");
+        } catch (CertificateException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void writeToParcel_writesCertPath() throws Exception {
+        RecoveryCertPath recoveryCertPath =
+                writeToThenReadFromParcel(
+                        RecoveryCertPath.createRecoveryCertPath(TestData.getThmCertPath()));
+        assertEquals(TestData.getThmCertPath(), recoveryCertPath.getCertPath());
+    }
+
+    private RecoveryCertPath writeToThenReadFromParcel(RecoveryCertPath recoveryCertPath) {
+        Parcel parcel = Parcel.obtain();
+        recoveryCertPath.writeToParcel(parcel, /*flags=*/ 0);
+        parcel.setDataPosition(0);
+        RecoveryCertPath fromParcel = RecoveryCertPath.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return fromParcel;
+    }
+}
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/TestData.java b/core/tests/coretests/src/android/security/keystore/recovery/TestData.java
new file mode 100644
index 0000000..829a92a
--- /dev/null
+++ b/core/tests/coretests/src/android/security/keystore/recovery/TestData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.keystore.recovery;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateFactory;
+import java.util.Base64;
+
+/** This class provides data for testing purposes. */
+class TestData {
+
+    private static final String THM_CERT_PATH_BASE64 = ""
+            + "MIIIXTCCBRowggMCoAMCAQICEB35ZwzVpI9ssXg9SAehnU0wDQYJKoZIhvcNAQEL"
+            + "BQAwMTEvMC0GA1UEAxMmR29vZ2xlIENsb3VkIEtleSBWYXVsdCBTZXJ2aWNlIFJv"
+            + "b3QgQ0EwHhcNMTgwNTA3MTg1ODEwWhcNMjgwNTA4MTg1ODEwWjA5MTcwNQYDVQQD"
+            + "Ey5Hb29nbGUgQ2xvdWQgS2V5IFZhdWx0IFNlcnZpY2UgSW50ZXJtZWRpYXRlIENB"
+            + "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA73TrvH3j6zEimpcc32tx"
+            + "2iupWwfyzdE5l4Ejc5EBYzx0aZH6b/KDuutwustk0IoyjlGySMBz/21YgWejIm+n"
+            + "duAlpk7WY5kYHp0XWtzdmxZknmWTqugPeNZeiKEjoDmpyIbY6N+f13hQ2RVh+WDT"
+            + "EowQ/i04WBL75chshlIG+3A42g5Qr7DZEKdT9oJQqkntzj0cGyJ5X8BwjeTiJrvY"
+            + "k2Kn/0555/Kpp65G3Rf29VPPU3i67kthAT3SavLBpH03S4WZ+QlfrAiGQziydtz9"
+            + "t7mSk1xefjax5ZWAuJAfCbKfI3VWAcaUr4P57BzmDcSi0jgs1aM3t2BrPfAMRxWv"
+            + "35yDZnrC+HipzkjyDGBfHmFgoglyhc9e/Kj3mSusO0Rq1wguVXKs2hKXRoaGJuHt"
+            + "e3YIwTC1pLznqvolhD1nPoXf8rMzgHRzlc9H8iXsgB1p7975nh5WCPrMDX2eAmYd"
+            + "a0xTMccTeBzIM2ohxQsxlh5rsjXVNU3ihbWkHquzIiwFcAtldP3dMksj0dn/DnYD"
+            + "yokjEgU/z2I216E93x9hmKkEk6Pp7o8t/z6lwMT9FJIuzp7NREnWCSi+e5s2E7FD"
+            + "j6S7xY2zEIUHrmwuuJc0jzJnwdZ+0myinaTmBDvBXR5cU1cmEAZoheCAoRv9Z/6o"
+            + "ASczLF0C4uuVfA5GXcAm14cCAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgGGMBIGA1Ud"
+            + "EwEB/wQIMAYBAf8CAQEwDQYJKoZIhvcNAQELBQADggIBAEPht79yQm8woQbPB1Bs"
+            + "eotkzJtTWTO9fnIWwNiRfQ3vJFXf69ghE77wUS13Ez3FlgNPj0Qxmg5ouE0d2yYV"
+            + "4AUrXnEGZELcyN2XHRXyNK0zXgnr3x6eZyY7QfgGKJgkyja5TS6ZPWGyaLKhClS0"
+            + "AYZSzWJtz0+AkGCdTbmyy7ShdXJ+GfnmssbndZA62VhcjeQmHsDq7V3PKAsp4/B9"
+            + "PzcnTrgkUFNnP1F1pr7JpUUX3xyRFy6gjIrUx1fcOFRxFYPWGLLMZ6P41rafm+M/"
+            + "CbBNr5CY7NrZjr34jLqWycfYes49o9OK44X/wPrxj0Sjg+VrW21+AJ9vrM7DS5hE"
+            + "QX1lDbDtQGkk3N1vgCTo6xt9LXsEu4xUT5bk7YAfpJqM0ltDFPwYAGCbjSkVT/M5"
+            + "JVZkKiUW668Us67x8yZc/5bxbvTA+5xrYhak/VYIBY6qub4J+bKwadw6uBgxnq4P"
+            + "hwgwjfaoJy9YAXCswjCtaE9GwkVmRnJE9vFjJ33IGf37hFTYEHBFy4FomVmQwRFZ"
+            + "TIe7tkKDq9i18F7lzBPJPO6wEG8bxi4csatrjcVHR9erpY5u6ebtkKG8qsan9qzh"
+            + "iWAgSytiT++HejZeoQ+RRgQWjupjdDo5/0oSdQqvaN8Ah6C2J+ecCZ12Lu0FwF+t"
+            + "t9Ie3pF6W8TzxzuMdFWq+afvMIIDOzCCASOgAwIBAgIRAOTj/iNQb6/Qit7zAW9n"
+            + "cL0wDQYJKoZIhvcNAQELBQAwOTE3MDUGA1UEAxMuR29vZ2xlIENsb3VkIEtleSBW"
+            + "YXVsdCBTZXJ2aWNlIEludGVybWVkaWF0ZSBDQTAeFw0xODA1MDcyMjE4MTFaFw0y"
+            + "MzA1MDgyMjE4MTFaMDIxMDAuBgNVBAMTJ0dvb2dsZSBDbG91ZCBLZXkgVmF1bHQg"
+            + "U2VydmljZSBFbmRwb2ludDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI4MEUp5"
+            + "IHwATNfpBuJYIUX6JMsHZt798YO0JlWYy6nVVa1lxf9c+xxONJh+T5aio370RlIE"
+            + "uiq5R7vCHt0VGsCjEDAOMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB"
+            + "AGf6QU58lU+gGzy8hnp0suR/ixC++CExtf39pDHkdfU/e3ui4ROR+pjQ5F7okDFW"
+            + "eKSCNcJZ7tyXMJ9g7/I0qVY8Bj/gRnlVokdl/wD5PiL9GIzqWfnHNe3T+xrAAAgO"
+            + "D0bEmjgwNYmekfUIYQczd04d7ZMGnmAkpVH/0O2mf9q5x9fMlbKuAygUqQ/gmnlg"
+            + "xKfl9DSRWi4oMBOqlKlCRP1XAh3anu92+M/EhsFbyc07CWZY0SByX5M/cHVMLhUX"
+            + "jZHvcYLyOmJWJmXznidgyNeIR6t9yDB55iCt7WSn3qMY+9vA9ELzt8jYpBNaKc0G"
+            + "bWQkRzYWegkf4kMis98eQ3SnAKbRz6669nmuAdxKs9/LK6BlFOFw1xvsTRQ96dBa"
+            + "oiX2XGhou+Im0Td/AMs0Aigz2N+Ujq/yW//35GZQfdGGIYtFbkcltStygjIJyAM1"
+            + "pBhyBBkJhOhRpO4fXh98aq8H5J7R9i5A9WpnDstAxPxcNCDWn0O/WxhPvVZkFTpi"
+            + "NXh9dnlJ/kZe+j+z5ZMaxW435drLPx2AQKjXA9GgGrFPltTUyGycmEGtuxLvSnm/"
+            + "zPlmk5FUk7x2wEr0+bZ3cx0JHHgAtgXpe0jkDi8Bw8O3X7mUOjxVhYU6auiYJezW"
+            + "9LGmweaKwYvS04UCWOReolUVexob9LI/VX1JrrwD3s7k";
+
+    static CertPath getThmCertPath() {
+        try {
+            return decodeCertPath(THM_CERT_PATH_BASE64);
+        } catch (Exception e) {
+            // Should never happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static CertPath decodeCertPath(String base64CertPath) throws Exception {
+        byte[] certPathBytes = Base64.getDecoder().decode(base64CertPath);
+        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        return certFactory.generateCertPath(new ByteArrayInputStream(certPathBytes), "PkiPath");
+    }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ed80cd7..2ad6028 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -19,6 +19,7 @@
 import static android.view.InsetsState.TYPE_TOP_BAR;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
+import static org.mockito.Mockito.mock;
 
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.FlakyTest;
@@ -33,7 +34,7 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsControllerTest {
 
-    private InsetsController mController = new InsetsController();
+    private InsetsController mController = new InsetsController(mock(ViewRootImpl.class));
 
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 5a20ba2..6d0f984 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -53,7 +53,7 @@
                 .setName("testSurface")
                 .build();
         mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, new InsetsState(),
-                () -> mMockTransaction);
+                () -> mMockTransaction, mMockController);
         mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash));
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index e261819..97f02cb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -57,9 +57,9 @@
         bcs.setSamplingInterval(5);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(1, uidEntries.size());
@@ -73,17 +73,17 @@
         assertEquals(1, callStatsList.get(0).transactionCode);
 
         // CPU usage is sampled, should not be tracked here.
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 20;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
         assertEquals(2, uidEntry.callCount);
         assertEquals(1, uidEntry.recordedCallCount);
         assertEquals(10, uidEntry.cpuTimeMicros);
         assertEquals(1, callStatsList.size());
 
-        callSession = bcs.callStarted(binder, 2);
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
         bcs.time += 50;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
         uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
         assertEquals(3, uidEntry.callCount);
         assertEquals(1, uidEntry.recordedCallCount);
@@ -98,9 +98,9 @@
         bcs.setDetailedTracking(true);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(1, uidEntries.size());
@@ -116,9 +116,9 @@
         assertEquals(binder.getClass(), callStatsList.get(0).binderClass);
         assertEquals(1, callStatsList.get(0).transactionCode);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 20;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
         assertEquals(2, uidEntry.callCount);
@@ -126,9 +126,9 @@
         callStatsList = new ArrayList(uidEntry.getCallStatsList());
         assertEquals(1, callStatsList.size());
 
-        callSession = bcs.callStarted(binder, 2);
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
         bcs.time += 50;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
         uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
         assertEquals(3, uidEntry.callCount);
 
@@ -142,7 +142,7 @@
     public void testEnableInBetweenCall() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         Binder binder = new Binder();
-        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(0, uidEntries.size());
@@ -153,7 +153,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         Binder binder = new Binder();
         bcs.callThrewException(null, new IllegalStateException());
-        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(0, uidEntries.size());
@@ -166,17 +166,17 @@
         bcs.setSamplingInterval(2);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 1000;  // shoud be ignored.
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 50;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(1, uidEntries.size());
@@ -203,13 +203,13 @@
         bcs.setSamplingInterval(2);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 2 /* another method */);
+        callSession = bcs.callStarted(binder, 2 /* another method */, WORKSOURCE_UID);
         bcs.time += 1000;  // shoud be ignored.
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
@@ -246,9 +246,9 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new BinderWithGetTransactionName();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.ExportedCallStat> callStatsList =
                 bcs.getExportedCallStats();
@@ -261,18 +261,18 @@
         bcs.setDetailedTracking(true);
 
         Binder binder = new AnotherBinderWithGetTransactionName();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         Binder binder2 = new BinderWithGetTransactionName();
-        callSession = bcs.callStarted(binder2, 1);
+        callSession = bcs.callStarted(binder2, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 2);
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.ExportedCallStat> callStatsList =
                 bcs.getExportedCallStats();
@@ -292,9 +292,9 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.ExportedCallStat> callStatsList =
                 bcs.getExportedCallStats();
@@ -306,9 +306,9 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.CallStat> callStatsList =
                 new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList());
@@ -322,13 +322,13 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 50;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.CallStat> callStatsList =
                 new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList());
@@ -341,13 +341,13 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.elapsedTime += 5;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.elapsedTime += 1;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.CallStat> callStatsList =
                 new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList());
@@ -368,17 +368,17 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.callThrewException(callSession, new IllegalStateException());
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.callThrewException(callSession, new IllegalStateException());
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.callThrewException(callSession, new RuntimeException());
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         ArrayMap<String, Integer> expected = new ArrayMap<>();
         expected.put("java.lang.IllegalStateException", 2);
@@ -391,9 +391,9 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats(null);
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         bcs.setDeviceState(mDeviceState.getReadonlyClient());
 
@@ -408,8 +408,8 @@
         mDeviceState.setCharging(true);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         assertEquals(0, bcs.getUidEntries().size());
     }
@@ -420,8 +420,8 @@
         bcs.setDetailedTracking(true);
         mDeviceState.setScreenInteractive(false);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(1, uidEntries.size());
@@ -437,8 +437,8 @@
         bcs.setDetailedTracking(true);
         mDeviceState.setScreenInteractive(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(1, uidEntries.size());
@@ -455,8 +455,8 @@
         mDeviceState.setCharging(true);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         assertEquals(0, bcs.getExportedCallStats().size());
     }
@@ -468,8 +468,8 @@
         mDeviceState.setCharging(false);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         assertEquals(1, bcs.getExportedCallStats().size());
     }
@@ -479,12 +479,12 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.callThrewException(callSession, new IllegalStateException());
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         PrintWriter pw = new PrintWriter(new StringWriter());
-        bcs.dump(pw, new HashMap<>(), true);
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), true);
     }
 
     @Test
@@ -492,8 +492,8 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(false);
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         assertEquals(0, bcs.getExportedCallStats().size());
     }
@@ -504,10 +504,10 @@
         bcs.setDetailedTracking(true);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
         bcs.elapsedTime += 20;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         assertEquals(1, bcs.getExportedCallStats().size());
         BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0);
@@ -549,15 +549,15 @@
         bcs.setMaxBinderCallStats(2);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
         bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 1);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
         List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList());
@@ -574,12 +574,15 @@
         bcs.setMaxBinderCallStats(1);
 
         Binder binder = new Binder();
-        CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        callSession = bcs.callStarted(binder, 2);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+        // Should use the same overflow entry.
+        callSession = bcs.callStarted(binder, 3, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats();
         assertEquals(2, callStatsList.size());
@@ -590,11 +593,46 @@
         assertEquals(CALLING_UID, callStats.callingUid);
 
         callStats = callStatsList.get(1);
-        assertEquals(1, callStats.callCount);
+        assertEquals(2, callStats.callCount);
         assertEquals("-1", callStats.methodName);
         assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder",
                 callStats.className);
-        assertEquals(CALLING_UID, callStats.callingUid);
+        assertEquals(false , callStats.screenInteractive);
+        assertEquals(-1 , callStats.callingUid);
+    }
+
+    @Test
+    public void testOverflow_oneOverflowEntryPerUid() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        bcs.setSamplingInterval(1);
+        bcs.setMaxBinderCallStats(1);
+
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID + 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID + 1);
+
+        // Different uids have different overflow entries.
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID + 2);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID + 2);
+
+        List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats();
+        assertEquals(3, callStatsList.size());
+
+        BinderCallsStats.ExportedCallStat callStats = callStatsList.get(1);
+        assertEquals(WORKSOURCE_UID + 1, callStats.workSourceUid);
+        assertEquals(1, callStats.callCount);
+        assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder",
+                callStats.className);
+
+        callStats = callStatsList.get(2);
+        assertEquals(WORKSOURCE_UID + 2, callStats.workSourceUid);
+        assertEquals(1, callStats.callCount);
+        assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder",
+                callStats.className);
     }
 
     @Test
@@ -620,7 +658,6 @@
 
     class TestBinderCallsStats extends BinderCallsStats {
         public int callingUid = CALLING_UID;
-        public int workSourceUid = WORKSOURCE_UID;
         public long time = 1234;
         public long elapsedTime = 0;
 
@@ -662,11 +699,6 @@
         protected int getCallingUid() {
             return callingUid;
         }
-
-        @Override
-        protected int getWorkSourceUid() {
-            return workSourceUid;
-        }
     }
 
 }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 141948f..4a2db0a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -225,15 +225,18 @@
     <library name="android.test.base"
             file="/system/framework/android.test.base.impl.jar" />
     <library name="android.test.mock"
-            file="/system/framework/android.test.mock.impl.jar" />
+            file="/system/framework/android.test.mock.impl.jar"
+            dependency="android.test.base" />
     <library name="android.test.runner"
-            file="/system/framework/android.test.runner.impl.jar" />
+            file="/system/framework/android.test.runner.impl.jar"
+            dependency="android.test.base:android.test.mock" />
 
     <!-- In BOOT_JARS historically, and now added to legacy applications. -->
     <library name="android.hidl.base-V1.0-java"
             file="/system/framework/android.hidl.base-V1.0-java.jar" />
     <library name="android.hidl.manager-V1.0-java"
-            file="/system/framework/android.hidl.manager-V1.0-java.jar" />
+            file="/system/framework/android.hidl.manager-V1.0-java.jar"
+            dependency="android.hidl.base-V1.0-java" />
 
     <!-- These are the standard packages that are white-listed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 2abb3d5..4be8bd9 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -831,6 +831,11 @@
 
     createBuffers(surface, surfaceFormat, extent);
 
+    // The window content is not updated (frozen) until a buffer of the window size is received.
+    // This prevents temporary stretching of the window after it is resized, but before the first
+    // buffer with new size is enqueued.
+    native_window_set_scaling_mode(surface->mNativeWindow, NATIVE_WINDOW_SCALING_MODE_FREEZE);
+
     return true;
 }
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index f1d9397..89d3cc4 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -14,7 +14,6 @@
 
 cc_library_shared {
     name: "libinputservice",
-
     srcs: [
         "PointerController.cpp",
         "SpriteController.cpp",
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 0a90f85..80d8e72 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -89,10 +89,6 @@
 
     mLocked.animationPending = false;
 
-    mLocked.displayWidth = -1;
-    mLocked.displayHeight = -1;
-    mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
-
     mLocked.presentation = PRESENTATION_POINTER;
     mLocked.presentationChanged = false;
 
@@ -110,15 +106,6 @@
     mLocked.lastFrameUpdatedTime = 0;
 
     mLocked.buttonState = 0;
-
-    mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-
-    loadResources();
-
-    if (mLocked.pointerIcon.isValid()) {
-        mLocked.pointerIconChanged = true;
-        updatePointerLocked();
-    }
 }
 
 PointerController::~PointerController() {
@@ -144,23 +131,15 @@
 
 bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
         float* outMaxX, float* outMaxY) const {
-    if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+
+    if (!mLocked.viewport.isValid()) {
         return false;
     }
 
-    *outMinX = 0;
-    *outMinY = 0;
-    switch (mLocked.displayOrientation) {
-    case DISPLAY_ORIENTATION_90:
-    case DISPLAY_ORIENTATION_270:
-        *outMaxX = mLocked.displayHeight - 1;
-        *outMaxY = mLocked.displayWidth - 1;
-        break;
-    default:
-        *outMaxX = mLocked.displayWidth - 1;
-        *outMaxY = mLocked.displayHeight - 1;
-        break;
-    }
+    *outMinX = mLocked.viewport.logicalLeft;
+    *outMinY = mLocked.viewport.logicalTop;
+    *outMaxX = mLocked.viewport.logicalRight - 1;
+    *outMaxY = mLocked.viewport.logicalBottom - 1;
     return true;
 }
 
@@ -231,6 +210,12 @@
     *outY = mLocked.pointerY;
 }
 
+int32_t PointerController::getDisplayId() const {
+    AutoMutex _l(mLock);
+
+    return mLocked.viewport.displayId;
+}
+
 void PointerController::fade(Transition transition) {
     AutoMutex _l(mLock);
 
@@ -355,48 +340,57 @@
 void PointerController::reloadPointerResources() {
     AutoMutex _l(mLock);
 
-    loadResources();
-
-    if (mLocked.presentation == PRESENTATION_POINTER) {
-        mLocked.additionalMouseResources.clear();
-        mLocked.animationResources.clear();
-        mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
-                                              &mLocked.animationResources);
-    }
-
-    mLocked.presentationChanged = true;
+    loadResourcesLocked();
     updatePointerLocked();
 }
 
-void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
-    AutoMutex _l(mLock);
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+    if (viewport.orientation == DISPLAY_ORIENTATION_90
+            || viewport.orientation == DISPLAY_ORIENTATION_270) {
+        width = viewport.deviceHeight;
+        height = viewport.deviceWidth;
+    } else {
+        width = viewport.deviceWidth;
+        height = viewport.deviceHeight;
+    }
+}
 
-    // Adjust to use the display's unrotated coordinate frame.
-    if (orientation == DISPLAY_ORIENTATION_90
-            || orientation == DISPLAY_ORIENTATION_270) {
-        int32_t temp = height;
-        height = width;
-        width = temp;
+void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
+    AutoMutex _l(mLock);
+    if (viewport == mLocked.viewport) {
+        return;
     }
 
-    if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
-        mLocked.displayWidth = width;
-        mLocked.displayHeight = height;
+    const DisplayViewport oldViewport = mLocked.viewport;
+    mLocked.viewport = viewport;
+
+    int32_t oldDisplayWidth, oldDisplayHeight;
+    getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+    int32_t newDisplayWidth, newDisplayHeight;
+    getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+    // Reset cursor position to center if size or display changed.
+    if (oldViewport.displayId != viewport.displayId
+            || oldDisplayWidth != newDisplayWidth
+            || oldDisplayHeight != newDisplayHeight) {
 
         float minX, minY, maxX, maxY;
         if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
             mLocked.pointerX = (minX + maxX) * 0.5f;
             mLocked.pointerY = (minY + maxY) * 0.5f;
+            // Reload icon resources for density may be changed.
+            loadResourcesLocked();
         } else {
             mLocked.pointerX = 0;
             mLocked.pointerY = 0;
         }
 
         fadeOutAndReleaseAllSpotsLocked();
-    }
-
-    if (mLocked.displayOrientation != orientation) {
+    } else if (oldViewport.orientation != viewport.orientation) {
         // Apply offsets to convert from the pixel top-left corner position to the pixel center.
         // This creates an invariant frame of reference that we can easily rotate when
         // taking into account that the pointer may be located at fractional pixel offsets.
@@ -405,37 +399,37 @@
         float temp;
 
         // Undo the previous rotation.
-        switch (mLocked.displayOrientation) {
+        switch (oldViewport.orientation) {
         case DISPLAY_ORIENTATION_90:
             temp = x;
-            x = mLocked.displayWidth - y;
+            x =  oldViewport.deviceHeight - y;
             y = temp;
             break;
         case DISPLAY_ORIENTATION_180:
-            x = mLocked.displayWidth - x;
-            y = mLocked.displayHeight - y;
+            x = oldViewport.deviceWidth - x;
+            y = oldViewport.deviceHeight - y;
             break;
         case DISPLAY_ORIENTATION_270:
             temp = x;
             x = y;
-            y = mLocked.displayHeight - temp;
+            y = oldViewport.deviceWidth - temp;
             break;
         }
 
         // Perform the new rotation.
-        switch (orientation) {
+        switch (viewport.orientation) {
         case DISPLAY_ORIENTATION_90:
             temp = x;
             x = y;
-            y = mLocked.displayWidth - temp;
+            y = viewport.deviceHeight - temp;
             break;
         case DISPLAY_ORIENTATION_180:
-            x = mLocked.displayWidth - x;
-            y = mLocked.displayHeight - y;
+            x = viewport.deviceWidth - x;
+            y = viewport.deviceHeight - y;
             break;
         case DISPLAY_ORIENTATION_270:
             temp = x;
-            x = mLocked.displayHeight - y;
+            x = viewport.deviceWidth - y;
             y = temp;
             break;
         }
@@ -444,7 +438,6 @@
         // and save the results.
         mLocked.pointerX = x - 0.5f;
         mLocked.pointerY = y - 0.5f;
-        mLocked.displayOrientation = orientation;
     }
 
     updatePointerLocked();
@@ -614,11 +607,16 @@
     mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
 }
 
-void PointerController::updatePointerLocked() {
+void PointerController::updatePointerLocked() REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+
     mSpriteController->openTransaction();
 
     mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
     mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+    mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
 
     if (mLocked.pointerAlpha > 0) {
         mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
@@ -729,8 +727,18 @@
     }
 }
 
-void PointerController::loadResources() {
+void PointerController::loadResourcesLocked() REQUIRES(mLock) {
     mPolicy->loadPointerResources(&mResources);
+
+    if (mLocked.presentation == PRESENTATION_POINTER) {
+        mLocked.additionalMouseResources.clear();
+        mLocked.animationResources.clear();
+        mPolicy->loadPointerIcon(&mLocked.pointerIcon);
+        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                              &mLocked.animationResources);
+    }
+
+    mLocked.pointerIconChanged = true;
 }
 
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 7f4e5a5..a32cc42 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include <ui/DisplayInfo.h>
+#include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <PointerControllerInterface.h>
 #include <utils/BitSet.h>
@@ -96,6 +97,7 @@
     virtual int32_t getButtonState() const;
     virtual void setPosition(float x, float y);
     virtual void getPosition(float* outX, float* outY) const;
+    virtual int32_t getDisplayId() const;
     virtual void fade(Transition transition);
     virtual void unfade(Transition transition);
 
@@ -106,7 +108,7 @@
 
     void updatePointerIcon(int32_t iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
-    void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+    void setDisplayViewport(const DisplayViewport& viewport);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void reloadPointerResources();
 
@@ -156,9 +158,7 @@
         size_t animationFrameIndex;
         nsecs_t lastFrameUpdatedTime;
 
-        int32_t displayWidth;
-        int32_t displayHeight;
-        int32_t displayOrientation;
+        DisplayViewport viewport;
 
         InactivityTimeout inactivityTimeout;
 
@@ -182,7 +182,7 @@
 
         Vector<Spot*> spots;
         Vector<sp<Sprite> > recycledSprites;
-    } mLocked;
+    } mLocked GUARDED_BY(mLock);
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     void setPositionLocked(float x, float y);
@@ -207,7 +207,7 @@
     void fadeOutAndReleaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseAllSpotsLocked();
 
-    void loadResources();
+    void loadResourcesLocked();
 };
 
 } // namespace android
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index eb2bc98..c1868d3 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -144,13 +144,16 @@
         }
     }
 
-    // Resize sprites if needed.
+    // Resize and/or reparent sprites if needed.
     SurfaceComposerClient::Transaction t;
     bool needApplyTransaction = false;
     for (size_t i = 0; i < numSprites; i++) {
         SpriteUpdate& update = updates.editItemAt(i);
+        if (update.state.surfaceControl == nullptr) {
+            continue;
+        }
 
-        if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+        if (update.state.wantSurfaceVisible()) {
             int32_t desiredWidth = update.state.icon.bitmap.width();
             int32_t desiredHeight = update.state.icon.bitmap.height();
             if (update.state.surfaceWidth < desiredWidth
@@ -170,6 +173,12 @@
                 }
             }
         }
+
+        // If surface is a new one, we have to set right layer stack.
+        if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
+            t.setLayerStack(update.state.surfaceControl, update.state.displayId);
+            needApplyTransaction = true;
+        }
     }
     if (needApplyTransaction) {
         t.apply();
@@ -236,7 +245,7 @@
         if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
                 || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
                         | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
-                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -445,6 +454,15 @@
     }
 }
 
+void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+    AutoMutex _l(mController->mLock);
+
+    if (mLocked.state.displayId != displayId) {
+        mLocked.state.displayId = displayId;
+        invalidateLocked(DIRTY_DISPLAY_ID);
+    }
+}
+
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
     bool wasDirty = mLocked.state.dirty;
     mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 31e43e9..5b216f5 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -125,6 +125,9 @@
 
     /* Sets the sprite transformation matrix. */
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+    /* Sets the id of the display where the sprite should be shown. */
+    virtual void setDisplayId(int32_t displayId) = 0;
 };
 
 /*
@@ -170,6 +173,7 @@
         DIRTY_LAYER = 1 << 4,
         DIRTY_VISIBILITY = 1 << 5,
         DIRTY_HOTSPOT = 1 << 6,
+        DIRTY_DISPLAY_ID = 1 << 7,
     };
 
     /* Describes the state of a sprite.
@@ -180,7 +184,7 @@
     struct SpriteState {
         inline SpriteState() :
                 dirty(0), visible(false),
-                positionX(0), positionY(0), layer(0), alpha(1.0f),
+                positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT),
                 surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
         }
 
@@ -193,6 +197,7 @@
         int32_t layer;
         float alpha;
         SpriteTransformationMatrix transformationMatrix;
+        int32_t displayId;
 
         sp<SurfaceControl> surfaceControl;
         int32_t surfaceWidth;
@@ -225,6 +230,7 @@
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+        virtual void setDisplayId(int32_t displayId);
 
         inline const SpriteState& getStateLocked() const {
             return mLocked.state;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e4d4795..5e77fdf 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -995,6 +995,35 @@
         }
     }
 
+    /**
+     * Returns whether direct playback of an audio format with the provided attributes is
+     * currently supported on the system.
+     * <p>Direct playback means that the audio stream is not resampled or downmixed
+     * by the framework. Checking for direct support can help the app select the representation
+     * of audio content that most closely matches the capabilities of the device and peripherials
+     * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded
+     * or mixed with other streams, if needed.
+     * <p>Also note that this query only provides information about the support of an audio format.
+     * It does not indicate whether the resources necessary for the playback are available
+     * at that instant.
+     * @param format a non-null {@link AudioFormat} instance describing the format of
+     *   the audio data.
+     * @param attributes a non-null {@link AudioAttributes} instance.
+     * @return true if the given audio format can be played directly.
+     */
+    public static boolean isDirectPlaybackSupported(@NonNull AudioFormat format,
+            @NonNull AudioAttributes attributes) {
+        if (format == null) {
+            throw new IllegalArgumentException("Illegal null AudioFormat argument");
+        }
+        if (attributes == null) {
+            throw new IllegalArgumentException("Illegal null AudioAttributes argument");
+        }
+        return native_is_direct_output_supported(format.getEncoding(), format.getSampleRate(),
+                format.getChannelMask(), format.getChannelIndexMask(),
+                attributes.getContentType(), attributes.getUsage(), attributes.getFlags());
+    }
+
     // mask of all the positional channels supported, however the allowed combinations
     // are further restricted by the matching left/right rule and
     // AudioSystem.OUT_CHANNEL_COUNT_MAX
@@ -3328,6 +3357,9 @@
     // Native methods called from the Java side
     //--------------------
 
+    private static native boolean native_is_direct_output_supported(int encoding, int sampleRate,
+            int channelMask, int channelIndexMask, int contentType, int usage, int flags);
+
     // post-condition: mStreamType is overwritten with a value
     //     that reflects the audio attributes (e.g. an AudioAttributes object with a usage of
     //     AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 95e3df2..9025829 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1135,6 +1135,10 @@
                 maxChannels = 6;
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
                 maxChannels = 16;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
+                sampleRates = new int[] { 48000 };
+                bitRates = Range.create(32000, 6144000);
+                maxChannels = 16;
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
                 sampleRates = new int[] { 44100, 48000, 96000, 192000 };
                 bitRates = Range.create(16000, 2688000);
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index b62108f..594a224 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -147,6 +147,7 @@
     public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
     public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
     public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
+    public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc";
     public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4";
     public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
 
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index ed2afdd..5f51320 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -513,7 +513,8 @@
 
 static status_t decode(int fd, int64_t offset, int64_t length,
         uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
-        sp<MemoryHeapBase> heap, size_t *memsize) {
+        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+        size_t *memsize) {
 
     ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length);
     AMediaExtractor *ex = AMediaExtractor_new();
@@ -650,6 +651,10 @@
                 (void)AMediaFormat_delete(format);
                 return UNKNOWN_ERROR;
             }
+            if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK,
+                    (int32_t*) channelMask)) {
+                *channelMask = AUDIO_CHANNEL_NONE;
+            }
             (void)AMediaFormat_delete(format);
             *memsize = written;
             return OK;
@@ -665,12 +670,13 @@
     uint32_t sampleRate;
     int numChannels;
     audio_format_t format;
+    audio_channel_mask_t channelMask;
     status_t status;
     mHeap = new MemoryHeapBase(kDefaultHeapSize);
 
     ALOGV("Start decode");
     status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
-                                 mHeap, &mSize);
+                    &channelMask, mHeap, &mSize);
     ALOGV("close(%d)", mFd);
     ::close(mFd);
     mFd = -1;
@@ -697,6 +703,7 @@
     mSampleRate = sampleRate;
     mNumChannels = numChannels;
     mFormat = format;
+    mChannelMask = channelMask;
     mState = READY;
     return NO_ERROR;
 
@@ -781,7 +788,11 @@
             // wrong audio audio buffer size  (mAudioBufferSize)
             unsigned long toggle = mToggle ^ 1;
             void *userData = (void *)((unsigned long)this | toggle);
-            audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
+            audio_channel_mask_t sampleChannelMask = sample->channelMask();
+            // When sample contains a not none channel mask, use it as is.
+            // Otherwise, use channel count to calculate channel mask.
+            audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE
+                    ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels);
 
             // do not create a new audio track if current track is compatible with sample parameters
     #ifdef USE_SHARED_MEM_BUFFER
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 5c48a90..9d74103 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -58,6 +58,7 @@
     int numChannels() { return mNumChannels; }
     int sampleRate() { return mSampleRate; }
     audio_format_t format() { return mFormat; }
+    audio_channel_mask_t channelMask() { return mChannelMask; }
     size_t size() { return mSize; }
     int state() { return mState; }
     uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
@@ -68,18 +69,19 @@
 private:
     void init();
 
-    size_t              mSize;
-    volatile int32_t    mRefCount;
-    uint16_t            mSampleID;
-    uint16_t            mSampleRate;
-    uint8_t             mState;
-    uint8_t             mNumChannels;
-    audio_format_t      mFormat;
-    int                 mFd;
-    int64_t             mOffset;
-    int64_t             mLength;
-    sp<IMemory>         mData;
-    sp<MemoryHeapBase>  mHeap;
+    size_t               mSize;
+    volatile int32_t     mRefCount;
+    uint16_t             mSampleID;
+    uint16_t             mSampleRate;
+    uint8_t              mState;
+    uint8_t              mNumChannels;
+    audio_format_t       mFormat;
+    audio_channel_mask_t mChannelMask;
+    int                  mFd;
+    int64_t              mOffset;
+    int64_t              mLength;
+    sp<IMemory>          mData;
+    sp<MemoryHeapBase>   mHeap;
 };
 
 // stores pending events for stolen channels
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
index 617e49a..a9a5450 100644
--- a/packages/ExtServices/res/values/strings.xml
+++ b/packages/ExtServices/res/values/strings.xml
@@ -22,5 +22,6 @@
     <string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string>
     <string-array name="autofill_field_classification_available_algorithms">
         <item>EDIT_DISTANCE</item>
+        <item>EXACT_MATCH</item>
     </string-array>
 </resources>
diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
index 9ba7e09..e379db8 100644
--- a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
+++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
@@ -15,8 +15,6 @@
  */
 package android.ext.services.autofill;
 
-import static android.ext.services.autofill.EditDistanceScorer.DEFAULT_ALGORITHM;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -26,27 +24,72 @@
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
 
     private static final String TAG = "AutofillFieldClassificationServiceImpl";
 
+    private static final String DEFAULT_ALGORITHM = REQUIRED_ALGORITHM_EDIT_DISTANCE;
+
     @Nullable
     @Override
-    public float[][] onGetScores(@Nullable String algorithmName,
-            @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
-            @NonNull List<String> userDataValues) {
+    /** @hide */
+    public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
+            @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
+            @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
+            @Nullable Map algorithms, @Nullable Map args) {
         if (ArrayUtils.isEmpty(actualValues) || ArrayUtils.isEmpty(userDataValues)) {
-            Log.w(TAG, "getScores(): empty currentvalues (" + actualValues + ") or userValues ("
-                    + userDataValues + ")");
+            Log.w(TAG, "calculateScores(): empty currentvalues (" + actualValues
+                    + ") or userValues (" + userDataValues + ")");
             return null;
         }
-        if (algorithmName != null && !algorithmName.equals(DEFAULT_ALGORITHM)) {
-            Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
-                    + DEFAULT_ALGORITHM + " instead");
-        }
 
-        return EditDistanceScorer.getScores(actualValues, userDataValues);
+        return calculateScores(actualValues, userDataValues, categoryIds, defaultAlgorithm,
+                defaultArgs, (HashMap<String, String>) algorithms,
+                (HashMap<String, Bundle>) args);
+    }
+
+    /** @hide */
+    public float[][] calculateScores(@NonNull List<AutofillValue> actualValues,
+            @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
+            @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
+            @Nullable HashMap<String, String> algorithms,
+            @Nullable HashMap<String, Bundle> args) {
+        final int actualValuesSize = actualValues.size();
+        final int userDataValuesSize = userDataValues.size();
+        final float[][] scores = new float[actualValuesSize][userDataValuesSize];
+
+        for (int j = 0; j < userDataValuesSize; j++) {
+            final String categoryId = categoryIds.get(j);
+            String algorithmName = defaultAlgorithm;
+            Bundle arg = defaultArgs;
+            if (algorithms != null && algorithms.containsKey(categoryId)) {
+                algorithmName = algorithms.get(categoryId);
+            }
+            if (args != null && args.containsKey(categoryId)) {
+                arg = args.get(categoryId);
+            }
+
+            if (algorithmName == null || (!algorithmName.equals(DEFAULT_ALGORITHM)
+                    && !algorithmName.equals(REQUIRED_ALGORITHM_EXACT_MATCH))) {
+                Log.w(TAG, "algorithmName is " + algorithmName + ", defaulting to "
+                        + DEFAULT_ALGORITHM);
+                algorithmName = DEFAULT_ALGORITHM;
+            }
+
+            for (int i = 0; i < actualValuesSize; i++) {
+                if (algorithmName.equals(DEFAULT_ALGORITHM)) {
+                    scores[i][j] = EditDistanceScorer.calculateScore(actualValues.get(i),
+                            userDataValues.get(j));
+                } else {
+                    scores[i][j] = ExactMatch.calculateScore(actualValues.get(i),
+                            userDataValues.get(j), arg);
+                }
+            }
+        }
+        return scores;
     }
 }
diff --git a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
index 302b160..6a47901 100644
--- a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
+++ b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
@@ -17,13 +17,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.util.Log;
 import android.view.autofill.AutofillValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.List;
-
 final class EditDistanceScorer {
 
     private static final String TAG = "EditDistanceScorer";
@@ -31,15 +28,14 @@
     // TODO(b/70291841): STOPSHIP - set to false before launching
     private static final boolean DEBUG = true;
 
-    static final String DEFAULT_ALGORITHM = "EDIT_DISTANCE";
-
     /**
      * Gets the field classification score of 2 values based on the edit distance between them.
      *
      * <p>The score is defined as: @(max_length - edit_distance) / max_length
      */
     @VisibleForTesting
-    static float getScore(@Nullable AutofillValue actualValue, @Nullable String userDataValue) {
+    static float calculateScore(@Nullable AutofillValue actualValue,
+            @Nullable String userDataValue) {
         if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
 
         final String actualValueText = actualValue.getTextValue().toString();
@@ -123,26 +119,5 @@
 
         return d[m][n];
     }
-    /**
-     * Gets the scores in a batch.
-     */
-    static float[][] getScores(@NonNull List<AutofillValue> actualValues,
-            @NonNull List<String> userDataValues) {
-        final int actualValuesSize = actualValues.size();
-        final int userDataValuesSize = userDataValues.size();
-        if (DEBUG) {
-            Log.d(TAG, "getScores() will return a " + actualValuesSize + "x"
-                    + userDataValuesSize + " matrix for " + DEFAULT_ALGORITHM);
-        }
-        final float[][] scores = new float[actualValuesSize][userDataValuesSize];
-
-        for (int i = 0; i < actualValuesSize; i++) {
-            for (int j = 0; j < userDataValuesSize; j++) {
-                final float score = getScore(actualValues.get(i), userDataValues.get(j));
-                scores[i][j] = score;
-            }
-        }
-        return scores;
-    }
 
 }
diff --git a/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java
new file mode 100644
index 0000000..3e55c5c
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ext.services.autofill;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+final class ExactMatch {
+
+    /**
+     * Gets the field classification score of 2 values based on whether they are an exact match
+     *
+     * @return {@code 1.0} if the two values are an exact match, {@code 0.0} otherwise.
+     */
+    @VisibleForTesting
+    static float calculateScore(@Nullable AutofillValue actualValue,
+            @Nullable String userDataValue, @Nullable Bundle args) {
+        if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
+
+        final String actualValueText = actualValue.getTextValue().toString();
+
+        final int suffixLength;
+        if (args != null) {
+            suffixLength = args.getInt("suffix", -1);
+
+            if (suffixLength < 0) {
+                throw new IllegalArgumentException("suffix argument is invalid");
+            }
+
+            final String actualValueSuffix;
+            if (suffixLength < actualValueText.length()) {
+                actualValueSuffix = actualValueText.substring(actualValueText.length()
+                        - suffixLength);
+            } else {
+                actualValueSuffix = actualValueText;
+            }
+
+            final String userDataValueSuffix;
+            if (suffixLength < userDataValue.length()) {
+                userDataValueSuffix = userDataValue.substring(userDataValue.length()
+                        - suffixLength);
+            } else {
+                userDataValueSuffix = userDataValue;
+            }
+
+            return (actualValueSuffix.equalsIgnoreCase(userDataValueSuffix)) ? 1 : 0;
+        } else {
+            return actualValueText.equalsIgnoreCase(userDataValue) ? 1 : 0;
+        }
+    }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 0cad5af..133d8ba 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -27,20 +27,15 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.IPackageManager;
-import android.database.ContentObserver;
 import android.ext.services.notification.AgingHelper.Callback;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationStats;
@@ -92,8 +87,6 @@
         PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
     }
 
-    private float mDismissToViewRatioLimit;
-    private int mStreakLimit;
     private SmartActionsHelper mSmartActionsHelper;
     private NotificationCategorizer mNotificationCategorizer;
     private AgingHelper mAgingHelper;
@@ -107,7 +100,11 @@
     private Ranking mFakeRanking = null;
     private AtomicFile mFile = null;
     private IPackageManager mPackageManager;
-    protected SettingsObserver mSettingsObserver;
+
+    @VisibleForTesting
+    protected AssistantSettings.Factory mSettingsFactory = AssistantSettings.FACTORY;
+    @VisibleForTesting
+    protected AssistantSettings mSettings;
 
     public Assistant() {
     }
@@ -118,7 +115,8 @@
         // Contexts are correctly hooked up by the creation step, which is required for the observer
         // to be hooked up/initialized.
         mPackageManager = ActivityThread.getPackageManager();
-        mSettingsObserver = new SettingsObserver(mHandler);
+        mSettings = mSettingsFactory.createAndRegister(mHandler,
+                getApplicationContext().getContentResolver(), getUserId(), this::updateThresholds);
         mSmartActionsHelper = new SmartActionsHelper();
         mNotificationCategorizer = new NotificationCategorizer();
         mAgingHelper = new AgingHelper(getContext(),
@@ -216,11 +214,11 @@
         if (!isForCurrentUser(sbn)) {
             return null;
         }
-        NotificationEntry entry = new NotificationEntry(
-                ActivityThread.getPackageManager(), sbn, channel);
+        NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel);
         ArrayList<Notification.Action> actions =
-                mSmartActionsHelper.suggestActions(this, entry);
-        ArrayList<CharSequence> replies = mSmartActionsHelper.suggestReplies(this, entry);
+                mSmartActionsHelper.suggestActions(this, entry, mSettings);
+        ArrayList<CharSequence> replies =
+                mSmartActionsHelper.suggestReplies(this, entry, mSettings);
         return createEnqueuedNotificationAdjustment(entry, actions, replies);
     }
 
@@ -239,8 +237,7 @@
         if (!smartReplies.isEmpty()) {
             signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
         }
-        if (Settings.Secure.getInt(getContentResolver(),
-                Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
+        if (mSettings.mNewInterruptionModel) {
             if (mNotificationCategorizer.shouldSilence(entry)) {
                 final int importance = entry.getImportance() < IMPORTANCE_LOW
                         ? entry.getImportance() : IMPORTANCE_LOW;
@@ -460,6 +457,11 @@
     }
 
     @VisibleForTesting
+    public void setSmartActionsHelper(SmartActionsHelper smartActionsHelper) {
+        mSmartActionsHelper = smartActionsHelper;
+    }
+
+    @VisibleForTesting
     public ChannelImpressions getImpressions(String key) {
         synchronized (mkeyToImpressions) {
             return mkeyToImpressions.get(key);
@@ -475,10 +477,20 @@
 
     private ChannelImpressions createChannelImpressionsWithThresholds() {
         ChannelImpressions impressions = new ChannelImpressions();
-        impressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
+        impressions.updateThresholds(mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
         return impressions;
     }
 
+    private void updateThresholds() {
+        // Update all existing channel impression objects with any new limits/thresholds.
+        synchronized (mkeyToImpressions) {
+            for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
+                channelImpressions.updateThresholds(
+                        mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
+            }
+        }
+    }
+
     protected final class AgingCallback implements Callback {
         @Override
         public void sendAdjustment(String key, int newImportance) {
@@ -495,51 +507,4 @@
         }
     }
 
-    /**
-     * Observer for updates on blocking helper threshold values.
-     */
-    protected final class SettingsObserver extends ContentObserver {
-        private final Uri STREAK_LIMIT_URI =
-                Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
-        private final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
-                Settings.Global.getUriFor(
-                        Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-
-        public SettingsObserver(Handler handler) {
-            super(handler);
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            resolver.registerContentObserver(
-                    DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, getUserId());
-            resolver.registerContentObserver(STREAK_LIMIT_URI, false, this, getUserId());
-
-            // Update all uris on creation.
-            update(null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            update(uri);
-        }
-
-        private void update(Uri uri) {
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
-                mDismissToViewRatioLimit = Settings.Global.getFloat(
-                        resolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                        ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
-            }
-            if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
-                mStreakLimit = Settings.Global.getInt(
-                        resolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
-                        ChannelImpressions.DEFAULT_STREAK_LIMIT);
-            }
-
-            // Update all existing channel impression objects with any new limits/thresholds.
-            synchronized (mkeyToImpressions) {
-                for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
-                    channelImpressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
-                }
-            }
-        }
-    }
 }
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
new file mode 100644
index 0000000..39a1676
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ext.services.notification;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Observes the settings for {@link Assistant}.
+ */
+final class AssistantSettings extends ContentObserver {
+    public static Factory FACTORY = AssistantSettings::createAndRegister;
+    private static final boolean DEFAULT_GENERATE_REPLIES = true;
+    private static final boolean DEFAULT_GENERATE_ACTIONS = true;
+    private static final int DEFAULT_NEW_INTERRUPTION_MODEL_INT = 1;
+
+    private static final Uri STREAK_LIMIT_URI =
+            Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
+    private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
+            Settings.Global.getUriFor(
+                    Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
+    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
+            Settings.Global.getUriFor(
+                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
+    private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
+            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
+
+    private static final String KEY_GENERATE_REPLIES = "generate_replies";
+    private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+    private final ContentResolver mResolver;
+    private final int mUserId;
+
+    @VisibleForTesting
+    protected final Runnable mOnUpdateRunnable;
+
+    // Actuall configuration settings.
+    float mDismissToViewRatioLimit;
+    int mStreakLimit;
+    boolean mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+    boolean mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+    boolean mNewInterruptionModel;
+
+    private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
+            Runnable onUpdateRunnable) {
+        super(handler);
+        mResolver = resolver;
+        mUserId = userId;
+        mOnUpdateRunnable = onUpdateRunnable;
+    }
+
+    private static AssistantSettings createAndRegister(
+            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
+        AssistantSettings assistantSettings =
+                new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
+        assistantSettings.register();
+        return assistantSettings;
+    }
+
+    /**
+     * Creates an instance but doesn't register it as an observer.
+     */
+    @VisibleForTesting
+    protected static AssistantSettings createForTesting(
+            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
+        return new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
+    }
+
+    private void register() {
+        mResolver.registerContentObserver(
+                DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
+        mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
+        mResolver.registerContentObserver(
+                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
+
+        // Update all uris on creation.
+        update(null);
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        update(uri);
+    }
+
+    private void update(Uri uri) {
+        if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
+            mDismissToViewRatioLimit = Settings.Global.getFloat(
+                    mResolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
+                    ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
+        }
+        if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
+            mStreakLimit = Settings.Global.getInt(
+                    mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
+                    ChannelImpressions.DEFAULT_STREAK_LIMIT);
+        }
+        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
+            mParser.setString(
+                    Settings.Global.getString(mResolver,
+                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+            mGenerateReplies =
+                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
+            mGenerateActions =
+                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
+        }
+        if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
+            int mNewInterruptionModelInt = Settings.Secure.getInt(
+                    mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
+                    DEFAULT_NEW_INTERRUPTION_MODEL_INT);
+            mNewInterruptionModel = mNewInterruptionModelInt == 1;
+        }
+
+        mOnUpdateRunnable.run();
+    }
+
+    public interface Factory {
+        AssistantSettings createAndRegister(Handler handler, ContentResolver resolver, int userId,
+                Runnable onUpdateRunnable);
+    }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 892267b..6f2b6c9 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -69,8 +69,11 @@
      * from notification text / message, we can replace most of the code here by consuming that API.
      */
     @NonNull
-    ArrayList<Notification.Action> suggestActions(
-            @Nullable Context context, @NonNull NotificationEntry entry) {
+    ArrayList<Notification.Action> suggestActions(@Nullable Context context,
+            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
+        if (!settings.mGenerateActions) {
+            return EMPTY_ACTION_LIST;
+        }
         if (!isEligibleForActionAdjustment(entry)) {
             return EMPTY_ACTION_LIST;
         }
@@ -86,8 +89,11 @@
                 getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
     }
 
-    ArrayList<CharSequence> suggestReplies(
-            @Nullable Context context, @NonNull NotificationEntry entry) {
+    ArrayList<CharSequence> suggestReplies(@Nullable Context context,
+            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
+        if (!settings.mGenerateReplies) {
+            return EMPTY_REPLY_LIST;
+        }
         if (!isEligibleForReplyAdjustment(entry)) {
             return EMPTY_REPLY_LIST;
         }
diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk
index 0a95b85..a57fa94 100644
--- a/packages/ExtServices/tests/Android.mk
+++ b/packages/ExtServices/tests/Android.mk
@@ -12,7 +12,8 @@
     mockito-target-minus-junit4 \
     espresso-core \
     truth-prebuilt \
-    testables
+    testables \
+    testng
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java
index 48c076e..6fda4c7 100644
--- a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java
@@ -16,14 +16,21 @@
 
 package android.ext.services.autofill;
 
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
+import static android.view.autofill.AutofillValue.forText;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Bundle;
+import android.view.autofill.AutofillValue;
+
 import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Collections;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.view.autofill.AutofillValue;
+import java.util.HashMap;
+import java.util.List;
 
 /**
  * Contains the base tests that does not rely on the specific algorithm implementation.
@@ -34,26 +41,78 @@
             new AutofillFieldClassificationServiceImpl();
 
     @Test
-    public void testOnGetScores_nullActualValues() {
-        assertThat(mService.onGetScores(null, null, null, Arrays.asList("whatever"))).isNull();
+    public void testOnCalculateScores_nullActualValues() {
+        assertThat(mService.onCalculateScores(null, null, null, null, null, null, null)).isNull();
     }
 
     @Test
-    public void testOnGetScores_emptyActualValues() {
-        assertThat(mService.onGetScores(null, null, Collections.emptyList(),
-                Arrays.asList("whatever"))).isNull();
+    public void testOnCalculateScores_emptyActualValues() {
+        assertThat(mService.onCalculateScores(Collections.emptyList(), Arrays.asList("whatever"),
+                null, null, null, null, null)).isNull();
     }
 
     @Test
-    public void testOnGetScores_nullUserDataValues() {
-        assertThat(mService.onGetScores(null, null,
-                Arrays.asList(AutofillValue.forText("whatever")), null)).isNull();
+    public void testOnCalculateScores_nullUserDataValues() {
+        assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")),
+                null, null, null, null, null, null)).isNull();
     }
 
     @Test
-    public void testOnGetScores_emptyUserDataValues() {
-        assertThat(mService.onGetScores(null, null,
-                Arrays.asList(AutofillValue.forText("whatever")), Collections.emptyList()))
-                        .isNull();
+    public void testOnCalculateScores_emptyUserDataValues() {
+        assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")),
+                Collections.emptyList(), null, null, null, null, null))
+                .isNull();
+    }
+
+    @Test
+    public void testCalculateScores() {
+        final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"),
+                forText("dude"));
+        final List<String> userDataValues = Arrays.asList("a", "b", "B", "ab", "c", "dude",
+                "sweet_dude", "dude_sweet");
+        final List<String> categoryIds = Arrays.asList("cat", "cat", "cat", "cat", "cat", "last4",
+                "last4", "last4");
+        final HashMap<String, String> algorithms = new HashMap<>(1);
+        algorithms.put("last4", REQUIRED_ALGORITHM_EXACT_MATCH);
+
+        final Bundle last4Bundle = new Bundle();
+        last4Bundle.putInt("suffix", 4);
+
+        final HashMap<String, Bundle> args = new HashMap<>(1);
+        args.put("last4", last4Bundle);
+
+        final float[][] expectedScores = new float[][] {
+                new float[] { 1F, 0F, 0F, 0.5F, 0F, 0F, 0F, 0F },
+                new float[] { 0F, 1F, 1F, 0.5F, 0F, 0F, 0F, 0F },
+                new float[] { 0F, 0F, 0F, 0F  , 0F, 1F, 1F, 0F }
+        };
+        final float[][] actualScores = mService.onCalculateScores(actualValues, userDataValues,
+                categoryIds, null, null, algorithms, args);
+
+        // Unfortunately, Truth does not have an easy way to compare float matrices and show useful
+        // messages in case of error, so we need to check.
+        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
+                toString(expectedScores)).that(actualScores.length).isEqualTo(3);
+        for (int i = 0; i < 3; i++) {
+            assertWithMessage("actual=%s, expected=%s", toString(actualScores),
+                    toString(expectedScores)).that(actualScores[i].length).isEqualTo(8);
+        }
+
+        for (int i = 0; i < actualScores.length; i++) {
+            final float[] line = actualScores[i];
+            for (int j = 0; j < line.length; j++) {
+                float cell = line[j];
+                assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F)
+                        .of(expectedScores[i][j]);
+            }
+        }
+    }
+
+    public static String toString(float[][] matrix) {
+        final StringBuilder string = new StringBuilder("[ ");
+        for (int i = 0; i < matrix.length; i++) {
+            string.append(Arrays.toString(matrix[i])).append(" ");
+        }
+        return string.append(" ]").toString();
     }
 }
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
index afe2236..9b9d4be 100644
--- a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
@@ -15,107 +15,67 @@
  */
 package android.ext.services.autofill;
 
-import static android.ext.services.autofill.EditDistanceScorer.getScore;
-import static android.ext.services.autofill.EditDistanceScorer.getScores;
-import static android.view.autofill.AutofillValue.forText;
+import static android.ext.services.autofill.EditDistanceScorer.calculateScore;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.view.autofill.AutofillValue;
 
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.List;
-
 public class EditDistanceScorerTest {
 
     @Test
-    public void testGetScore_nullValue() {
-        assertFloat(getScore(null, "D'OH!"), 0);
+    public void testCalculateScore_nullValue() {
+        assertFloat(calculateScore(null, "D'OH!"), 0);
     }
 
     @Test
-    public void testGetScore_nonTextValue() {
-        assertFloat(getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
+    public void testCalculateScore_nonTextValue() {
+        assertFloat(calculateScore(AutofillValue.forToggle(true), "D'OH!"), 0);
     }
 
     @Test
-    public void testGetScore_nullUserData() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), null), 0);
+    public void testCalculateScore_nullUserData() {
+        assertFloat(calculateScore(AutofillValue.forText("D'OH!"), null), 0);
     }
 
     @Test
-    public void testGetScore_fullMatch() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
-        assertFloat(getScore(AutofillValue.forText(""), ""), 1);
+    public void testCalculateScore_fullMatch() {
+        assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
+        assertFloat(calculateScore(AutofillValue.forText(""), ""), 1);
     }
 
     @Test
-    public void testGetScore_fullMatchMixedCase() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
+    public void testCalculateScore_fullMatchMixedCase() {
+        assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
     }
 
     @Test
-    public void testGetScore_mismatchDifferentSizes() {
-        assertFloat(getScore(AutofillValue.forText("X"), "Xy"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("Xy"), "X"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F);
-        assertFloat(getScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F);
-        assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Parkway"),
+    public void testCalculateScore_mismatchDifferentSizes() {
+        assertFloat(calculateScore(AutofillValue.forText("X"), "Xy"), 0.50F);
+        assertFloat(calculateScore(AutofillValue.forText("Xy"), "X"), 0.50F);
+        assertFloat(calculateScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F);
+        assertFloat(calculateScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F);
+        assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Parkway"),
                 "1600 Amphitheatre Pkwy"), 0.88F);
-        assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Pkwy"),
+        assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Pkwy"),
                 "1600 Amphitheatre Parkway"), 0.88F);
     }
 
     @Test
-    public void testGetScore_partialMatch() {
-        assertFloat(getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
-        assertFloat(getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
-        assertFloat(getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
-        assertFloat(getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
-    }
-
-    @Test
-    public void testGetScores() {
-        final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"));
-        final List<String> userDataValues = Arrays.asList("a", "B", "ab", "c");
-        final float[][] expectedScores = new float[][] {
-            new float[] { 1F, 0F, 0.5F, 0F },
-            new float[] { 0F, 1F, 0.5F, 0F }
-        };
-        final float[][] actualScores = getScores(actualValues, userDataValues);
-
-        // Unfortunately, Truth does not have an easy way to compare float matrices and show useful
-        // messages in case of error, so we need to check.
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores.length).isEqualTo(2);
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores[0].length).isEqualTo(4);
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores[1].length).isEqualTo(4);
-        for (int i = 0; i < actualScores.length; i++) {
-            final float[] line = actualScores[i];
-            for (int j = 0; j < line.length; j++) {
-                float cell = line[j];
-                assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F)
-                        .of(expectedScores[i][j]);
-            }
-        }
+    public void testCalculateScore_partialMatch() {
+        assertFloat(calculateScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
+        assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
+        assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
+        assertFloat(calculateScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
+        assertFloat(calculateScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
+        assertFloat(calculateScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
     }
 
     public static void assertFloat(float actualValue, float expectedValue) {
         assertThat(actualValue).isWithin(0.01F).of(expectedValue);
     }
 
-    public static String toString(float[][] matrix) {
-        final StringBuilder string = new StringBuilder("[ ");
-        for (int i = 0; i < matrix.length; i++) {
-            string.append(Arrays.toString(matrix[i])).append(" ");
-        }
-        return string.append(" ]").toString();
-    }
+
 }
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java
new file mode 100644
index 0000000..bf5e160
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ext.services.autofill;
+
+import static android.ext.services.autofill.ExactMatch.calculateScore;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+
+public class ExactMatchTest {
+
+    private Bundle last4Bundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putInt("suffix", 4);
+        return bundle;
+    }
+
+    @Test
+    public void testCalculateScore_nullValue() {
+        assertFloat(calculateScore(null, "TEST", null), 0);
+    }
+
+    @Test
+    public void testCalculateScore_nonTextValue() {
+        assertFloat(calculateScore(AutofillValue.forToggle(true), "TEST", null), 0);
+    }
+
+    @Test
+    public void testCalculateScore_nullUserData() {
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), null, null), 0);
+    }
+
+    @Test
+    public void testCalculateScore_succeedMatchMixedCases_last4() {
+        final Bundle last4 = last4Bundle();
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "1234 test", last4), 1);
+        assertFloat(calculateScore(AutofillValue.forText("test"), "1234 TEST", last4), 1);
+    }
+
+    @Test
+    public void testCalculateScore_mismatchDifferentSizes_last4() {
+        final Bundle last4 = last4Bundle();
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST1", last4), 0);
+        assertFloat(calculateScore(AutofillValue.forText(""), "TEST", last4), 0);
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "", last4), 0);
+    }
+
+    @Test
+    public void testCalculateScore_match() {
+        final Bundle last4 = last4Bundle();
+        assertFloat(calculateScore(AutofillValue.forText("1234 1234 1234 1234"),
+                "xxxx xxxx xxxx 1234", last4), 1);
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", null), 1);
+        assertFloat(calculateScore(AutofillValue.forText("TEST 1234"), "1234", last4), 1);
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "test", null), 1);
+    }
+
+    @Test
+    public void testCalculateScore_badBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putInt("suffix", -2);
+        assertThrows(IllegalArgumentException.class, () -> calculateScore(
+                AutofillValue.forText("TEST"), "TEST", bundle));
+
+        final Bundle largeBundle = new Bundle();
+        largeBundle.putInt("suffix", 10);
+        assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", largeBundle), 1);
+
+        final Bundle stringBundle = new Bundle();
+        stringBundle.putString("suffix", "value");
+        assertThrows(IllegalArgumentException.class, () -> calculateScore(
+                AutofillValue.forText("TEST"), "TEST", stringBundle));
+
+    }
+
+    public static void assertFloat(float actualValue, float expectedValue) {
+        assertThat(actualValue).isWithin(0.01F).of(expectedValue);
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
new file mode 100644
index 0000000..fd23f2b
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ext.services.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.testing.TestableContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class AssistantSettingsTest {
+    private static final int USER_ID = 5;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    @Mock Runnable mOnUpdateRunnable;
+
+    private ContentResolver mResolver;
+    private AssistantSettings mAssistantSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mResolver = mContext.getContentResolver();
+        Handler handler = new Handler(Looper.getMainLooper());
+
+        // To bypass real calls to global settings values, set the Settings values here.
+        Settings.Global.putFloat(mResolver,
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
+        Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                "generate_replies=true,generate_actions=true");
+        Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
+
+        mAssistantSettings = AssistantSettings.createForTesting(
+                handler, mResolver, USER_ID, mOnUpdateRunnable);
+    }
+
+    @Test
+    public void testGenerateRepliesDisabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                "generate_replies=false");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+
+        assertFalse(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateRepliesEnabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateActionsDisabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateActionsEnabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testStreakLimit() {
+        verify(mOnUpdateRunnable, never()).run();
+
+        // Update settings value.
+        int newStreakLimit = 4;
+        Settings.Global.putInt(mResolver,
+                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);
+
+        // Notify for the settings value we updated.
+        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
+                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));
+
+        assertEquals(newStreakLimit, mAssistantSettings.mStreakLimit);
+        verify(mOnUpdateRunnable).run();
+    }
+
+    @Test
+    public void testDismissToViewRatioLimit() {
+        verify(mOnUpdateRunnable, never()).run();
+
+        // Update settings value.
+        float newDismissToViewRatioLimit = 3f;
+        Settings.Global.putFloat(mResolver,
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
+                newDismissToViewRatioLimit);
+
+        // Notify for the settings value we updated.
+        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));
+
+        assertEquals(newDismissToViewRatioLimit, mAssistantSettings.mDismissToViewRatioLimit, 1e-6);
+        verify(mOnUpdateRunnable).run();
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
index 2eb005a..0a95b83 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
@@ -33,13 +33,11 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -86,8 +84,7 @@
 
     @Mock INotificationManager mNoMan;
     @Mock AtomicFile mFile;
-    @Mock
-    IPackageManager mPackageManager;
+    @Mock IPackageManager mPackageManager;
 
     Assistant mAssistant;
     Application mApplication;
@@ -108,20 +105,26 @@
                 new Intent("android.service.notification.NotificationAssistantService");
         startIntent.setPackage("android.ext.services");
 
-        // To bypass real calls to global settings values, set the Settings values here.
-        Settings.Global.putFloat(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
         mApplication = (Application) InstrumentationRegistry.getInstrumentation().
                 getTargetContext().getApplicationContext();
         // Force the test to use the correct application instead of trying to use a mock application
         setApplication(mApplication);
-        bindService(startIntent);
+
+        setupService();
         mAssistant = getService();
+
+        // Override the AssistantSettings factory.
+        mAssistant.mSettingsFactory = AssistantSettings::createForTesting;
+
+        bindService(startIntent);
+
+        mAssistant.mSettings.mDismissToViewRatioLimit = 0.8f;
+        mAssistant.mSettings.mStreakLimit = 2;
+        mAssistant.mSettings.mNewInterruptionModel = true;
         mAssistant.setNoMan(mNoMan);
         mAssistant.setFile(mFile);
         mAssistant.setPackageManager(mPackageManager);
+
         ApplicationInfo info = mock(ApplicationInfo.class);
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
                 .thenReturn(info);
@@ -408,6 +411,8 @@
         mAssistant.writeXml(serializer);
 
         Assistant assistant = new Assistant();
+        // onCreate is not invoked, so settings won't be initialised, unless we do it here.
+        assistant.mSettings = mAssistant.mSettings;
         assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));
 
         assertEquals(ci1, assistant.getImpressions(key1));
@@ -417,8 +422,6 @@
 
     @Test
     public void testSettingsProviderUpdate() {
-        ContentResolver resolver = mApplication.getContentResolver();
-
         // Set up channels
         String key = mAssistant.getKey("pkg1", 1, "channel1");
         ChannelImpressions ci = new ChannelImpressions();
@@ -435,19 +438,11 @@
         assertEquals(false, ci.shouldTriggerBlock());
 
         // Update settings values.
-        float newDismissToViewRatioLimit = 0f;
-        int newStreakLimit = 0;
-        Settings.Global.putFloat(resolver,
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                newDismissToViewRatioLimit);
-        Settings.Global.putInt(resolver,
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);
+        mAssistant.mSettings.mDismissToViewRatioLimit = 0f;
+        mAssistant.mSettings.mStreakLimit = 0;
 
         // Notify for the settings values we updated.
-        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));
-        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));
+        mAssistant.mSettings.mOnUpdateRunnable.run();
 
         // With the new threshold, the blocking helper should be triggered.
         assertEquals(true, ci.shouldTriggerBlock());
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index cc17b25..0126e7e5 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -18,6 +18,7 @@
         "SettingsLibSettingsSpinner",
         "SettingsLayoutPreference",
         "ActionButtonsPreference",
+        "SettingsLibEntityHeaderWidgets",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
new file mode 100644
index 0000000..3ca4ecd
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -0,0 +1,14 @@
+android_library {
+    name: "SettingsLibEntityHeaderWidgets",
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+          "androidx.annotation_annotation",
+          "SettingsLibAppPreference"
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
new file mode 100644
index 0000000..4b9f1ab
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
new file mode 100644
index 0000000..9f30eda
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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/app_entities_header"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="24dp"
+    android:paddingEnd="8dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/header_title"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"
+        android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
+
+    <LinearLayout
+        android:id="@+id/all_apps_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:gravity="center">
+
+        <include
+            android:id="@+id/app1_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app2_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app3_view"
+            layout="@layout/app_view"/>
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/header_details"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
new file mode 100644
index 0000000..fcafa31
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:layout_marginEnd="16dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@dimen/secondary_app_icon_size"
+        android:layout_height="@dimen/secondary_app_icon_size"
+        android:layout_marginBottom="12dp"/>
+
+    <TextView
+        android:id="@+id/app_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="2dp"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Title"/>
+
+    <TextView
+        android:id="@+id/app_summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
new file mode 100644
index 0000000..0eefd4b
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES 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="AppEntitiesHeader.Text"
+           parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.HeaderTitle">
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Title">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Summary"
+           parent="@android:style/TextAppearance.Material.Body1">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
new file mode 100644
index 0000000..8ccf89f
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * This is used to initialize view which was inflated
+ * from {@link R.xml.app_entities_header.xml}.
+ *
+ * <p>The view looks like below.
+ *
+ * <pre>
+ * --------------------------------------------------------------
+ * |                     Header title                           |
+ * --------------------------------------------------------------
+ * |    App1 icon       |   App2 icon        |   App3 icon      |
+ * |    App1 title      |   App2 title       |   App3 title     |
+ * |    App1 summary    |   App2 summary     |   App3 summary   |
+ * |-------------------------------------------------------------
+ * |                     Header details                         |
+ * --------------------------------------------------------------
+ * </pre>
+ *
+ * <p>How to use AppEntitiesHeaderController?
+ *
+ * <p>1. Add a {@link LayoutPreference} in layout XML file.
+ * <pre>
+ * &lt;com.android.settingslib.widget.LayoutPreference
+ *        android:key="app_entities_header"
+ *        android:layout="@layout/app_entities_header"/&gt;
+ * </pre>
+ *
+ * <p>2. Use AppEntitiesHeaderController to call below methods, then you can initialize
+ * view of <code>app_entities_header</code>.
+ *
+ * <pre>
+ *
+ * View headerView = ((LayoutPreference) screen.findPreference("app_entities_header"))
+ *         .findViewById(R.id.app_entities_header);
+ *
+ * AppEntitiesHeaderController.newInstance(context, headerView)
+ *         .setHeaderTitleRes(R.string.xxxxx)
+ *         .setHeaderDetailsRes(R.string.xxxxx)
+ *         .setHeaderDetailsClickListener(onClickListener)
+ *         .setAppEntity(0, icon, "app title", "app summary")
+ *         .setAppEntity(1, icon, "app title", "app summary")
+ *         .setAppEntity(2, icon, "app title", "app summary")
+ *         .apply();
+ * </pre>
+ */
+public class AppEntitiesHeaderController {
+
+    private static final String TAG = "AppEntitiesHeaderCtl";
+
+    @VisibleForTesting
+    static final int MAXIMUM_APPS = 3;
+
+    private final Context mContext;
+    private final TextView mHeaderTitleView;
+    private final Button mHeaderDetailsView;
+
+    private final AppEntity[] mAppEntities;
+    private final View[] mAppEntityViews;
+    private final ImageView[] mAppIconViews;
+    private final TextView[] mAppTitleViews;
+    private final TextView[] mAppSummaryViews;
+
+    private int mHeaderTitleRes;
+    private int mHeaderDetailsRes;
+    private View.OnClickListener mDetailsOnClickListener;
+
+    /**
+     * Creates a new instance of the controller.
+     *
+     * @param context the Context the view is running in
+     * @param appEntitiesHeaderView view was inflated from <code>app_entities_header</code>
+     */
+    public static AppEntitiesHeaderController newInstance(@NonNull Context context,
+            @NonNull View appEntitiesHeaderView) {
+        return new AppEntitiesHeaderController(context, appEntitiesHeaderView);
+    }
+
+    private AppEntitiesHeaderController(Context context, View appEntitiesHeaderView) {
+        mContext = context;
+        mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
+        mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+
+        mAppEntities = new AppEntity[MAXIMUM_APPS];
+        mAppIconViews = new ImageView[MAXIMUM_APPS];
+        mAppTitleViews = new TextView[MAXIMUM_APPS];
+        mAppSummaryViews = new TextView[MAXIMUM_APPS];
+
+        mAppEntityViews = new View[]{
+                appEntitiesHeaderView.findViewById(R.id.app1_view),
+                appEntitiesHeaderView.findViewById(R.id.app2_view),
+                appEntitiesHeaderView.findViewById(R.id.app3_view)
+        };
+
+        // Initialize view in advance, so we won't take too much time to do it when controller is
+        // binding view.
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            final View appView = mAppEntityViews[index];
+            mAppIconViews[index] = (ImageView) appView.findViewById(R.id.app_icon);
+            mAppTitleViews[index] = (TextView) appView.findViewById(R.id.app_title);
+            mAppSummaryViews[index] = (TextView) appView.findViewById(R.id.app_summary);
+        }
+    }
+
+    /**
+     * Set the text resource for app entities header title.
+     */
+    public AppEntitiesHeaderController setHeaderTitleRes(@StringRes int titleRes) {
+        mHeaderTitleRes = titleRes;
+        return this;
+    }
+
+    /**
+     * Set the text resource for app entities header details.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsRes(@StringRes int detailsRes) {
+        mHeaderDetailsRes = detailsRes;
+        return this;
+    }
+
+    /**
+     * Register a callback to be invoked when header details view is clicked.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsClickListener(
+            @Nullable View.OnClickListener clickListener) {
+        mDetailsOnClickListener = clickListener;
+        return this;
+    }
+
+    /**
+     * Set an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be inserted
+     * @param icon the icon of app entity
+     * @param titleRes the title of app entity
+     * @param summaryRes the summary of app entity
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController setAppEntity(int index, @NonNull Drawable icon,
+            @Nullable CharSequence titleRes, @Nullable CharSequence summaryRes) {
+        final AppEntity appEntity = new AppEntity(icon, titleRes, summaryRes);
+        mAppEntities[index] = appEntity;
+        return this;
+    }
+
+    /**
+     * Remove an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be removed
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController removeAppEntity(int index) {
+        mAppEntities[index] = null;
+        return this;
+    }
+
+    /**
+     * Clear all app entities in app entities header.
+     *
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController clearAllAppEntities() {
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            removeAppEntity(index);
+        }
+        return this;
+    }
+
+    /**
+     * Done mutating app entities header, rebinds everything.
+     */
+    public void apply() {
+        bindHeaderTitleView();
+        bindHeaderDetailsView();
+
+        // Rebind all apps view
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            bindAppEntityView(index);
+        }
+    }
+
+    private void bindHeaderTitleView() {
+        CharSequence titleText = "";
+        try {
+            titleText = mContext.getText(mHeaderTitleRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header title can't not be found!", e);
+        }
+        mHeaderTitleView.setText(titleText);
+        mHeaderTitleView.setVisibility(
+                TextUtils.isEmpty(titleText) ? View.GONE : View.VISIBLE);
+    }
+
+    private void bindHeaderDetailsView() {
+        CharSequence detailsText = "";
+        try {
+            detailsText = mContext.getText(mHeaderDetailsRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header details can't not be found!", e);
+        }
+        mHeaderDetailsView.setText(detailsText);
+        mHeaderDetailsView.setVisibility(
+                TextUtils.isEmpty(detailsText) ? View.GONE : View.VISIBLE);
+        mHeaderDetailsView.setOnClickListener(mDetailsOnClickListener);
+    }
+
+    private void bindAppEntityView(int index) {
+        final AppEntity appEntity = mAppEntities[index];
+        mAppEntityViews[index].setVisibility(appEntity != null ? View.VISIBLE : View.GONE);
+
+        if (appEntity != null) {
+            mAppIconViews[index].setImageDrawable(appEntity.icon);
+
+            mAppTitleViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.title) ? View.INVISIBLE : View.VISIBLE);
+            mAppTitleViews[index].setText(appEntity.title);
+
+            mAppSummaryViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.summary) ? View.INVISIBLE : View.VISIBLE);
+            mAppSummaryViews[index].setText(appEntity.summary);
+        }
+    }
+
+    private static class AppEntity {
+        public final Drawable icon;
+        public final CharSequence title;
+        public final CharSequence summary;
+
+        AppEntity(Drawable appIcon, CharSequence appTitle, CharSequence appSummary) {
+            icon = appIcon;
+            title = appTitle;
+            summary = appSummary;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
index f2b97b4..2387b01 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
@@ -15,7 +15,6 @@
  */
 package com.android.settingslib.applications;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.permission.RuntimePermissionPresentationInfo;
 import android.permission.RuntimePermissionPresenter;
@@ -31,37 +30,33 @@
             final PermissionsResultCallback callback) {
         final RuntimePermissionPresenter presenter =
                 RuntimePermissionPresenter.getInstance(context);
-        presenter.getAppPermissions(pkg, new RuntimePermissionPresenter.OnResultCallback() {
-            @Override
-            public void onGetAppPermissions(
-                    @NonNull List<RuntimePermissionPresentationInfo> permissions) {
-                final int permissionCount = permissions.size();
+        presenter.getAppPermissions(pkg, permissions -> {
+            final int permissionCount = permissions.size();
 
-                int grantedStandardCount = 0;
-                int grantedAdditionalCount = 0;
-                int requestedCount = 0;
-                List<CharSequence> grantedStandardLabels = new ArrayList<>();
+            int grantedStandardCount = 0;
+            int grantedAdditionalCount = 0;
+            int requestedCount = 0;
+            List<CharSequence> grantedStandardLabels = new ArrayList<>();
 
-                for (int i = 0; i < permissionCount; i++) {
-                    RuntimePermissionPresentationInfo permission = permissions.get(i);
-                    requestedCount++;
-                    if (permission.isGranted()) {
-                        if (permission.isStandard()) {
-                            grantedStandardLabels.add(permission.getLabel());
-                            grantedStandardCount++;
-                        } else {
-                            grantedAdditionalCount++;
-                        }
+            for (int i = 0; i < permissionCount; i++) {
+                RuntimePermissionPresentationInfo permission = permissions.get(i);
+                requestedCount++;
+                if (permission.isGranted()) {
+                    if (permission.isStandard()) {
+                        grantedStandardLabels.add(permission.getLabel());
+                        grantedStandardCount++;
+                    } else {
+                        grantedAdditionalCount++;
                     }
                 }
-
-                Collator collator = Collator.getInstance();
-                collator.setStrength(Collator.PRIMARY);
-                Collections.sort(grantedStandardLabels, collator);
-
-                callback.onPermissionSummaryResult(grantedStandardCount, requestedCount,
-                        grantedAdditionalCount, grantedStandardLabels);
             }
+
+            Collator collator = Collator.getInstance();
+            collator.setStrength(Collator.PRIMARY);
+            Collections.sort(grantedStandardLabels, collator);
+
+            callback.onPermissionSummaryResult(grantedStandardCount, requestedCount,
+                    grantedAdditionalCount, grantedStandardLabels);
         }, null);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
new file mode 100644
index 0000000..c3bc8da
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppEntitiesHeaderControllerTest {
+
+    private static final CharSequence TITLE = "APP_TITLE";
+    private static final CharSequence SUMMARY = "APP_SUMMARY";
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private Drawable mIcon;
+    private View mAppEntitiesHeaderView;
+    private AppEntitiesHeaderController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mAppEntitiesHeaderView = LayoutInflater.from(mContext).inflate(
+                R.layout.app_entities_header, null /* root */);
+        mIcon = mContext.getDrawable(R.drawable.ic_menu);
+        mController = AppEntitiesHeaderController.newInstance(mContext,
+                mAppEntitiesHeaderView);
+    }
+
+    @Test
+    public void assert_amountOfMaximumAppsAreThree() {
+        assertThat(AppEntitiesHeaderController.MAXIMUM_APPS).isEqualTo(3);
+    }
+
+    @Test
+    public void setHeaderTitleRes_setTextRes_shouldSetToTitleView() {
+        mController.setHeaderTitleRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsRes_setTextRes_shouldSetToDetailsView() {
+        mController.setHeaderDetailsRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsClickListener_setClickListener_detailsViewAttachClickListener() {
+        mController.setHeaderDetailsClickListener(v -> {
+        }).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void setAppEntity_indexLessThanZero_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(-1, mIcon, TITLE, SUMMARY);
+    }
+
+    @Test
+    public void asetAppEntity_indexGreaterThanMaximum_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(AppEntitiesHeaderController.MAXIMUM_APPS + 1, mIcon, TITLE,
+                SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex0_shouldShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final ImageView appIconView = app1View.findViewById(R.id.app_icon);
+        final TextView appTitle = app1View.findViewById(R.id.app_title);
+        final TextView appSummary = app1View.findViewById(R.id.app_summary);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex1_shouldShowAppView2() {
+        mController.setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final ImageView appIconView = app2View.findViewById(R.id.app_icon);
+        final TextView appTitle = app2View.findViewById(R.id.app_title);
+        final TextView appSummary = app2View.findViewById(R.id.app_summary);
+
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex2_shouldShowAppView3() {
+        mController.setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+        final ImageView appIconView = app3View.findViewById(R.id.app_icon);
+        final TextView appTitle = app3View.findViewById(R.id.app_title);
+        final TextView appSummary = app3View.findViewById(R.id.app_summary);
+
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void removeAppEntity_removeIndex0_shouldNotShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.removeAppEntity(0).apply();
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void clearAllAppEntities_shouldNotShowAllAppViews() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY)
+                .setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.clearAllAppEntities().apply();
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+    }
+}
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
new file mode 100644
index 0000000..b2307e7
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.systemui.bubbles.BubbleExpandedViewContainer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:id="@+id/bubble_expanded_view">
+
+    <!-- TODO: header -->
+
+    <View
+        android:id="@+id/pointer_view"
+        android:layout_width="@dimen/bubble_pointer_width"
+        android:layout_height="@dimen/bubble_pointer_height"
+    />
+</com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b6c9b8c..3851190 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -361,6 +361,7 @@
     <!-- The height of the qs customize header. Should be (28dp - qs_tile_margin_top_bottom). -->
     <dimen name="qs_customize_header_min_height">40dp</dimen>
     <dimen name="qs_tile_margin_top">18dp</dimen>
+    <dimen name="qs_tile_background_size">40dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <!-- Maximum width of quick quick settings panel. Defaults to MATCH_PARENT-->
     <dimen name="qs_quick_layout_width">-1px</dimen>
@@ -993,4 +994,10 @@
     <dimen name="bubble_icon_size">24dp</dimen>
     <!-- Default height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">400dp</dimen>
+    <!-- Height of the triangle that points to the expanded bubble -->
+    <dimen name="bubble_pointer_height">4dp</dimen>
+    <!-- Width of the triangle that points to the expanded bubble -->
+    <dimen name="bubble_pointer_width">6dp</dimen>
+    <!-- Extra padding around the dismiss target for bubbles -->
+    <dimen name="bubble_dismiss_slop">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
new file mode 100644
index 0000000..e28d96b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.TriangleShape;
+
+/**
+ * Container for the expanded bubble view, handles rendering the caret and header of the view.
+ */
+public class BubbleExpandedViewContainer extends LinearLayout {
+
+    // The triangle pointing to the expanded view
+    private View mPointerView;
+    // The view that is being displayed for the expanded state
+    private View mExpandedView;
+
+    public BubbleExpandedViewContainer(Context context) {
+        this(context, null);
+    }
+
+    public BubbleExpandedViewContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setOrientation(VERTICAL);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        Resources res = getResources();
+        mPointerView = findViewById(R.id.pointer_view);
+        int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
+        int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
+        ShapeDrawable triangleDrawable = new ShapeDrawable(
+                TriangleShape.create(width, height, true /* pointUp */));
+        triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
+        mPointerView.setBackground(triangleDrawable);
+    }
+
+    /**
+     * Set the x position that the tip of the triangle should point to.
+     */
+    public void setPointerPosition(int x) {
+        // Adjust for the pointer size
+        x -= (mPointerView.getWidth() / 2);
+        mPointerView.setTranslationX(x);
+    }
+
+    /**
+     * Set the view to display for the expanded state. Passing null will clear the view.
+     */
+    public void setExpandedView(View view) {
+        if (mExpandedView != null) {
+            removeView(mExpandedView);
+        }
+        mExpandedView = view;
+        if (mExpandedView != null) {
+            addView(mExpandedView);
+        }
+    }
+
+    /**
+     * @return the view containing the expanded content, can be null.
+     */
+    @Nullable
+    public View getExpandedView() {
+        return mExpandedView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index e395c4c..dfd18b2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -52,7 +53,7 @@
     private Point mDisplaySize;
 
     private FrameLayout mBubbleContainer;
-    private FrameLayout mExpandedViewContainer;
+    private BubbleExpandedViewContainer mExpandedViewContainer;
 
     private int mBubbleSize;
     private int mBubblePadding;
@@ -111,7 +112,9 @@
 
         int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
-        mExpandedViewContainer = new FrameLayout(context);
+        mExpandedViewContainer = (BubbleExpandedViewContainer)
+                LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view,
+                        this /* parent */, false /* attachToRoot */);
         mExpandedViewContainer.setElevation(elevation);
         mExpandedViewContainer.setPadding(padding, padding, padding, padding);
         mExpandedViewContainer.setClipChildren(false);
@@ -390,16 +393,19 @@
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
             if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
                 // Different expanded view than what we have
-                mExpandedViewContainer.removeAllViews();
+                mExpandedViewContainer.setExpandedView(null);
             }
-            mExpandedViewContainer.addView(row);
+            int pointerPosition = mExpandedBubble.getPosition().x
+                    + (mExpandedBubble.getWidth() / 2);
+            mExpandedViewContainer.setPointerPosition(pointerPosition);
+            mExpandedViewContainer.setExpandedView(row);
         }
     }
 
     private void applyCurrentState() {
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (!mIsExpanded) {
-            mExpandedViewContainer.removeAllViews();
+            mExpandedViewContainer.setExpandedView(null);
         } else {
             mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 8262b54..8224365 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -51,6 +51,8 @@
 
     // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
     private View mTargetView;
+    private int mTargetSlop;
+    private Point mWindowSize;
     private int[] mLoc = new int[2];
     private boolean mIntersecting;
     private Vibrator mVibe;
@@ -69,12 +71,14 @@
             // Determine sizes for the view
             final Rect stableInsets = new Rect();
             WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
-            final Point windowSize = new Point();
-            mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+            mWindowSize = new Point();
+            mWindowManager.getDefaultDisplay().getRealSize(mWindowSize);
             final int gradientHeight = mContext.getResources().getDimensionPixelSize(
                     R.dimen.pip_dismiss_gradient_height);
             final int bottomMargin = mContext.getResources().getDimensionPixelSize(
                     R.dimen.pip_dismiss_text_bottom_margin);
+            mTargetSlop = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.bubble_dismiss_slop);
 
             // Create a new view for the dismiss target
             LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -96,7 +100,7 @@
             // Add the target to the window
             LayoutParams lp =  new LayoutParams(
                     LayoutParams.MATCH_PARENT, gradientHeight,
-                    0, windowSize.y - gradientHeight,
+                    0, mWindowSize.y - gradientHeight,
                     LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                     LayoutParams.FLAG_LAYOUT_IN_SCREEN
                             | LayoutParams.FLAG_NOT_TOUCHABLE
@@ -112,7 +116,7 @@
 
 
     /**
-     * Updates the dismiss target based on location of the view.
+     * Updates the dismiss target based on location of the view, only used for bubbles not for PIP.
      *
      * @return whether the view is within the dismiss target.
      */
@@ -127,11 +131,13 @@
             mTargetView.getLocationOnScreen(mLoc);
             Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
                     mLoc[1] + mTargetView.getHeight());
+            expandRect(targetRect, mTargetSlop);
             boolean intersecting = targetRect.intersect(viewRect);
-            if (intersecting && !mIntersecting) {
+            if (intersecting != mIntersecting) {
                 // TODO: is this the right effect?
-                mVibe.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-                mIntersecting = true;
+                mVibe.vibrate(VibrationEffect.get(intersecting
+                        ? VibrationEffect.EFFECT_CLICK
+                        : VibrationEffect.EFFECT_TICK));
             }
             mIntersecting = intersecting;
             return intersecting;
@@ -139,7 +145,6 @@
         return false;
     }
 
-
     /**
      * Shows the dismiss target.
      */
@@ -172,4 +177,11 @@
                     .start();
         }
     }
+
+    private void expandRect(Rect outRect, int expandAmount) {
+        outRect.left = Math.max(0, outRect.left - expandAmount);
+        outRect.top = Math.max(0, outRect.top - expandAmount);
+        outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount);
+        outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 3a96595d..32fd2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -19,14 +19,19 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.PathParser;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -46,6 +51,7 @@
 public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
 
     private static final String TAG = "QSTileBaseView";
+    private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask;
     private final H mHandler = new H();
     private final int[] mLocInScreen = new int[2];
     private final FrameLayout mIconFrame;
@@ -62,6 +68,7 @@
     private final int mColorInactive;
     private final int mColorDisabled;
     private int mCircleColor;
+    private int mBgSize;
 
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
@@ -71,15 +78,23 @@
         super(context);
         // Default to Quick Tile padding, and QSTileView will specify its own padding.
         int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
-
         mIconFrame = new FrameLayout(context);
         mIconFrame.setForegroundGravity(Gravity.CENTER);
         int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
         addView(mIconFrame, new LayoutParams(size, size));
         mBg = new ImageView(getContext());
+        Path path = new Path(PathParser.createPathFromPathData(
+                context.getResources().getString(ICON_MASK_ID)));
+        float pathSize = AdaptiveIconDrawable.MASK_SIZE;
+        PathShape p = new PathShape(path, pathSize, pathSize);
+        ShapeDrawable d = new ShapeDrawable(p);
+        int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
+        d.setIntrinsicHeight(bgSize);
+        d.setIntrinsicWidth(bgSize);
         mBg.setScaleType(ScaleType.FIT_CENTER);
-        mBg.setImageResource(R.drawable.ic_qs_circle);
-        mIconFrame.addView(mBg);
+        mBg.setImageDrawable(d);
+        mIconFrame.addView(mBg, ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
         mIcon = icon;
         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -107,7 +122,7 @@
         setFocusable(true);
     }
 
-    public View getBgCicle() {
+    public View getBgCircle() {
         return mBg;
     }
 
@@ -303,6 +318,7 @@
 
     private class H extends Handler {
         private static final int STATE_CHANGED = 1;
+
         public H() {
             super(Looper.getMainLooper());
         }
@@ -314,4 +330,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 20c4816..b1fa6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -21,7 +21,6 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -60,10 +59,11 @@
     private ViewGroup mTransientContainer;
     private boolean mInShelf;
     private boolean mTransformingInShelf;
-    @Nullable private ExpandableViewState mViewState;
+    private final ExpandableViewState mViewState;
 
     public ExpandableView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mViewState = createExpandableViewState();
     }
 
     @Override
@@ -536,11 +536,6 @@
 
     /** Sets {@link ExpandableViewState} to default state. */
     public ExpandableViewState resetViewState() {
-        // TODO(http://b/119762423): Move the null check to getViewState().
-        if (mViewState == null) {
-            mViewState = createExpandableViewState();
-        }
-
         // initialize with the default values of the view
         mViewState.height = getIntrinsicHeight();
         mViewState.gone = getVisibility() == View.GONE;
@@ -573,9 +568,7 @@
 
     /** Applies internal {@link ExpandableViewState} to this view. */
     public void applyViewState() {
-        if (mViewState == null) {
-            Log.wtf(TAG, "No child state was found when applying this state to the hostView");
-        } else if (!mViewState.gone) {
+        if (!mViewState.gone) {
             mViewState.applyToView(this);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 9eb5737a..0cec637 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -296,13 +296,15 @@
                     } else if (exceededSwipeHorizontalTouchSlop) {
                         if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) {
                             // Swiping left (rtl) gesture
-                            int index = isEdgeSwipeAlongNavBar(touchDownH, !mDragHPositive)
+                            int index = mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] != null
+                                        && isEdgeSwipeAlongNavBar(touchDownH, !mDragHPositive)
                                     ? ACTION_SWIPE_LEFT_FROM_EDGE_INDEX : ACTION_SWIPE_LEFT_INDEX;
                             tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */,
                                     event);
                         } else {
                             // Swiping right (ltr) gesture
-                            int index = isEdgeSwipeAlongNavBar(touchDownH, mDragHPositive)
+                            int index = mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] != null
+                                        && isEdgeSwipeAlongNavBar(touchDownH, mDragHPositive)
                                     ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX : ACTION_SWIPE_RIGHT_INDEX;
                             tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */,
                                     event);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
index abb8c79..2805908 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
@@ -601,6 +601,25 @@
         assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT);
     }
 
+    @Test
+    public void testNoEdgeActionsTriggerNormalActions() {
+        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
+        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
+
+        NavigationGestureAction swipeUp = mockAction(true);
+        NavigationGestureAction swipeDown = mockAction(true);
+        NavigationGestureAction swipeLeft = mockAction(true);
+        NavigationGestureAction swipeRight = mockAction(true);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* swipeLeftFromEdgeAction */,
+                null /* swipeLeftFromEdgeAction */);
+
+        // Swipe Left from Edge
+        assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH, 1, 5, 1);
+        // Swipe Right from Edge
+        assertGestureTriggersAction(swipeRight, 0, 1, NAVBAR_WIDTH, 5);
+    }
+
     private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL,
             int navPos) {
         mController.setBarState(isRTL, navPos);
@@ -665,7 +684,6 @@
         reset(buttonView);
     }
 
-
     private MotionEvent event(int action, float x, float y) {
         final MotionEvent event = mock(MotionEvent.class);
         doReturn(x).when(event).getX();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index e8887e7..612c929 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -484,15 +484,15 @@
     }
 
     // Called by Shell command.
-    void getScore(@Nullable String algorithmName, @NonNull String value1,
+    void calculateScore(@Nullable String algorithmName, @NonNull String value1,
             @NonNull String value2, @NonNull RemoteCallback callback) {
         enforceCallingPermissionForManagement();
 
         final FieldClassificationStrategy strategy =
                 new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT);
 
-        strategy.getScores(callback, algorithmName, null,
-                Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 });
+        strategy.calculateScores(callback, Arrays.asList(AutofillValue.forText(value1)),
+                new String[] { value2 }, null, algorithmName, null, null, null);
     }
 
     // Called by Shell command.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5a0d12c..fa62ef8 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -76,7 +76,6 @@
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractPerUserSystemService;
-import com.android.server.infra.AbstractRemoteService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
@@ -1031,8 +1030,7 @@
                 return null;
             }
             final ComponentName componentName = RemoteAugmentedAutofillService.getComponentName(
-                    getContext(), serviceName, mUserId,
-                    mAugmentedAutofillResolver.isTemporaryLocked());
+                    serviceName, mUserId, mAugmentedAutofillResolver.isTemporaryLocked());
             if (componentName == null) return null;
             if (sVerbose) {
                 Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName);
@@ -1041,8 +1039,7 @@
             mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(),
                     componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
                         @Override
-                        public void onServiceDied(
-                                AbstractRemoteService<? extends AbstractRemoteService<?>> service) {
+                        public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
                             // TODO(b/111330312): properly implement
                             Slog.w(TAG, "remote augmented autofill service died");
                         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 35c5102..c562fb1 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -233,7 +233,7 @@
         final String value2 = getNextArgRequired();
 
         final CountDownLatch latch = new CountDownLatch(1);
-        mService.getScore(algorithm, value1, value2, new RemoteCallback((result) -> {
+        mService.calculateScore(algorithm, value1, value2, new RemoteCallback((result) -> {
             final Scores scores = result.getParcelable(EXTRA_SCORES);
             if (scores == null) {
                 pw.println("no score");
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
index 293f908e..9db6254 100644
--- a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
@@ -15,11 +15,12 @@
  */
 package com.android.server.autofill;
 
-import static com.android.server.autofill.Helper.sDebug;
-import static com.android.server.autofill.Helper.sVerbose;
 import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
 import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM;
 
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
 import android.Manifest;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -40,6 +41,7 @@
 import android.os.UserHandle;
 import android.service.autofill.AutofillFieldClassificationService;
 import android.service.autofill.IAutofillFieldClassificationService;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.view.autofill.AutofillValue;
@@ -257,13 +259,13 @@
         return parser.get(res, resourceId);
     }
 
-    //TODO(b/70291841): rename this method (and all others in the chain) to something like
-    // calculateScores() ?
-    void getScores(RemoteCallback callback, @Nullable String algorithmName,
-            @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
-            @NonNull String[] userDataValues) {
-        connectAndRun((service) -> service.getScores(callback, algorithmName,
-                algorithmArgs, actualValues, userDataValues));
+    void calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues,
+            @NonNull String[] userDataValues, @NonNull String[] categoryIds,
+            @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
+            @Nullable ArrayMap<String, String> algorithms,
+            @Nullable ArrayMap<String, Bundle> args) {
+        connectAndRun((service) -> service.calculateScores(callback, actualValues,
+                userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args));
     }
 
     void dump(String prefix, PrintWriter pw) {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 222888c..fc7265d 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -26,7 +26,6 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.IInterface;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.augmented.AugmentedAutofillService;
@@ -43,7 +42,8 @@
 import com.android.server.infra.AbstractSinglePendingRequestRemoteService;
 
 final class RemoteAugmentedAutofillService
-        extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService> {
+        extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService,
+            IAugmentedAutofillService> {
 
     private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName();
 
@@ -51,20 +51,16 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
-    private final RemoteAugmentedAutofillServiceCallbacks mCallbacks;
-    private IAugmentedAutofillService mService;
-
     RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
             int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
             boolean bindInstantServiceAllowed, boolean verbose) {
         super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks,
                 bindInstantServiceAllowed, verbose);
-        mCallbacks = callbacks;
     }
 
     @Nullable
-    public static ComponentName getComponentName(@NonNull Context context,
-            @NonNull String componentName, @UserIdInt int userId, boolean isTemporary) {
+    public static ComponentName getComponentName(@NonNull String componentName,
+            @UserIdInt int userId, boolean isTemporary) {
         int flags = PackageManager.GET_META_DATA;
         if (!isTemporary) {
             flags |= PackageManager.MATCH_SYSTEM_ONLY;
@@ -88,9 +84,8 @@
     }
 
     @Override // from AbstractRemoteService
-    protected IInterface getServiceInterface(IBinder service) {
-        mService = IAugmentedAutofillService.Stub.asInterface(service);
-        return mService;
+    protected IAugmentedAutofillService getServiceInterface(IBinder service) {
+        return IAugmentedAutofillService.Stub.asInterface(service);
     }
 
     @Override // from AbstractRemoteService
@@ -109,7 +104,6 @@
     public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
             int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
             @Nullable AutofillValue focusedValue) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId,
                 activityComponent, focusedId, focusedValue));
     }
@@ -118,12 +112,12 @@
      * Called by {@link Session} when it's time to destroy all augmented autofill requests.
      */
     public void onDestroyAutofillWindowsRequest(int sessionId) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingDestroyAutofillWindowsRequest(this, sessionId));
+        scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId));
     }
 
+    // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
     private abstract static class MyPendingRequest
-            extends PendingRequest<RemoteAugmentedAutofillService> {
+            extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
         protected final int mSessionId;
 
         private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) {
@@ -196,38 +190,8 @@
 
     }
 
-    private static final class PendingDestroyAutofillWindowsRequest extends MyPendingRequest {
-
-        protected PendingDestroyAutofillWindowsRequest(
-                @NonNull RemoteAugmentedAutofillService service, @NonNull int sessionId) {
-            super(service, sessionId);
-        }
-
-        @Override
-        public void run() {
-            final RemoteAugmentedAutofillService remoteService = getService();
-            if (remoteService == null) return;
-
-            try {
-                remoteService.mService.onDestroyFillWindowRequest(mSessionId);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "exception handling onDestroyAutofillWindowsRequest() for "
-                        + mSessionId + ": " + e);
-            } finally {
-                // Service is not calling back, so we finish right away.
-                finish();
-            }
-        }
-
-        @Override
-        protected void onTimeout(RemoteAugmentedAutofillService remoteService) {
-            // Should not happen because we called finish() on run(), although currently it might
-            // be called if the service is destroyed while showing it.
-            Slog.e(TAG, "timed out: " + this);
-        }
-    }
-
-    public interface RemoteAugmentedAutofillServiceCallbacks extends VultureCallback {
+    public interface RemoteAugmentedAutofillServiceCallbacks
+            extends VultureCallback<RemoteAugmentedAutofillService> {
         // NOTE: so far we don't need to notify the callback implementation (an inner class on
         // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
         // callback interface is empty.
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4b7d290..417ea9c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -28,7 +28,6 @@
 import android.content.IntentSender;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
-import android.os.IInterface;
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
 import android.service.autofill.FillRequest;
@@ -42,15 +41,15 @@
 
 import com.android.server.infra.AbstractSinglePendingRequestRemoteService;
 
-final class RemoteFillService extends AbstractSinglePendingRequestRemoteService<RemoteFillService> {
+final class RemoteFillService
+        extends AbstractSinglePendingRequestRemoteService<RemoteFillService, IAutoFillService> {
 
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
     private final FillServiceCallbacks mCallbacks;
-    private IAutoFillService mAutoFillService;
 
-    public interface FillServiceCallbacks extends VultureCallback {
+    public interface FillServiceCallbacks extends VultureCallback<RemoteFillService> {
         void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
                 @NonNull String servicePackageName, int requestFlags);
         void onFillRequestFailure(int requestId, @Nullable CharSequence message);
@@ -71,21 +70,20 @@
 
     @Override // from AbstractRemoteService
     protected void handleOnConnectedStateChanged(boolean state) {
-        if (mAutoFillService == null) {
+        if (mService == null) {
             Slog.w(mTag, "onConnectedStateChanged(): null service");
             return;
         }
         try {
-            mAutoFillService.onConnectedStateChanged(state);
+            mService.onConnectedStateChanged(state);
         } catch (Exception e) {
             Slog.w(mTag, "Exception calling onConnectedStateChanged(): " + e);
         }
     }
 
     @Override // from AbstractRemoteService
-    protected IInterface getServiceInterface(IBinder service) {
-        mAutoFillService = IAutoFillService.Stub.asInterface(service);
-        return mAutoFillService;
+    protected IAutoFillService getServiceInterface(IBinder service) {
+        return IAutoFillService.Stub.asInterface(service);
     }
 
     @Override // from AbstractRemoteService
@@ -127,17 +125,15 @@
     }
 
     public void onFillRequest(@NonNull FillRequest request) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingFillRequest(request, this));
     }
 
     public void onSaveRequest(@NonNull SaveRequest request) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingSaveRequest(request, this));
     }
 
     private boolean handleResponseCallbackCommon(
-            @NonNull PendingRequest<RemoteFillService> pendingRequest) {
+            @NonNull PendingRequest<RemoteFillService, IAutoFillService> pendingRequest) {
         if (isDestroyed()) return false;
 
         if (mPendingRequest == pendingRequest) {
@@ -204,7 +200,8 @@
         });
     }
 
-    private static final class PendingFillRequest extends PendingRequest<RemoteFillService> {
+    private static final class PendingFillRequest
+            extends PendingRequest<RemoteFillService, IAutoFillService> {
         private final FillRequest mRequest;
         private final IFillCallback mCallback;
         private ICancellationSignal mCancellation;
@@ -282,7 +279,7 @@
             if (remoteService != null) {
                 if (sVerbose) Slog.v(mTag, "calling onFillRequest() for id=" + mRequest.getId());
                 try {
-                    remoteService.mAutoFillService.onFillRequest(mRequest, mCallback);
+                    remoteService.mService.onFillRequest(mRequest, mCallback);
                 } catch (RemoteException e) {
                     Slog.e(mTag, "Error calling on fill request", e);
 
@@ -310,7 +307,8 @@
         }
     }
 
-    private static final class PendingSaveRequest extends PendingRequest<RemoteFillService> {
+    private static final class PendingSaveRequest
+            extends PendingRequest<RemoteFillService, IAutoFillService> {
         private final SaveRequest mRequest;
         private final ISaveCallback mCallback;
 
@@ -355,7 +353,7 @@
             if (remoteService != null) {
                 if (sVerbose) Slog.v(mTag, "calling onSaveRequest()");
                 try {
-                    remoteService.mAutoFillService.onSaveRequest(mRequest, mCallback);
+                    remoteService.mService.onSaveRequest(mRequest, mCallback);
                 } catch (RemoteException e) {
                     Slog.e(mTag, "Error calling on save request", e);
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index aef16b1..0e9b407 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -98,7 +98,6 @@
 import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
-import com.android.server.infra.AbstractRemoteService;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -909,7 +908,7 @@
 
     // VultureCallback
     @Override
-    public void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service) {
+    public void onServiceDied(@NonNull RemoteFillService service) {
         Slog.w(TAG, "removing session because service died");
         forceRemoveSelfLocked();
     }
@@ -1402,6 +1401,12 @@
         final String[] userValues = userData.getValues();
         final String[] categoryIds = userData.getCategoryIds();
 
+        final String defaultAlgorithm = userData.getFieldClassificationAlgorithm();
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+
+        final ArrayMap<String, String> algorithms = userData.getFieldClassificationAlgorithms();
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+
         // Sanity check
         if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
             final int valuesLength = userValues == null ? -1 : userValues.length;
@@ -1417,8 +1422,6 @@
         final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
                 maxFieldsSize);
 
-        final String algorithm = userData.getFieldClassificationAlgorithm();
-        final Bundle algorithmArgs = userData.getAlgorithmArgs();
         final int viewsSize = viewStates.size();
 
         // First, we get all scores.
@@ -1505,7 +1508,8 @@
                     mComponentName, mCompatMode);
         });
 
-        fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
+        fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
+                defaultAlgorithm, defaultArgs, algorithms, args);
     }
 
     /**
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3acdc8e3..a917ced 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.backup;
 
+import android.Manifest;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -30,6 +32,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -79,6 +82,7 @@
         return sInstance;
     }
 
+    private final Context mContext;
     private UserBackupManagerService mUserBackupManagerService;
 
     /** Instantiate a new instance of {@link BackupManagerService}. */
@@ -91,11 +95,26 @@
             transportWhitelist = Collections.emptySet();
         }
 
+        mContext = context;
         mUserBackupManagerService =
                 UserBackupManagerService.createAndInitializeService(
                         context, trampoline, backupThread, transportWhitelist);
     }
 
+    /**
+     * If {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId User id on which the backup operation is being requested.
+     * @param message A message to include in the exception if it is thrown.
+     */
+    private void enforceCallingPermissionOnUserId(int userId, String message) {
+        if (Binder.getCallingUserHandle().getIdentifier() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+        }
+    }
+
     // TODO(b/118520567): Remove when tests are modified to use per-user instance.
     @VisibleForTesting
     void setUserBackupManagerService(UserBackupManagerService userBackupManagerService) {
@@ -314,7 +333,8 @@
     // ---------------------------------------------
 
     /** Enable/disable the backup service. This is user-configurable via backup settings. */
-    public void setBackupEnabled(boolean enable) {
+    public void setBackupEnabled(@UserIdInt int userId, boolean enable) {
+        enforceCallingPermissionOnUserId(userId, "setBackupEnabled");
         mUserBackupManagerService.setBackupEnabled(enable);
     }
 
@@ -331,7 +351,8 @@
     /**
      * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}.
      */
-    public boolean isBackupEnabled() {
+    public boolean isBackupEnabled(@UserIdInt int userId) {
+        enforceCallingPermissionOnUserId(userId, "isBackupEnabled");
         return mUserBackupManagerService.isBackupEnabled();
     }
 
@@ -355,7 +376,8 @@
      * Run a backup pass immediately for any key-value backup applications that have declared that
      * they have pending updates.
      */
-    public void backupNow() {
+    public void backupNow(@UserIdInt int userId) {
+        enforceCallingPermissionOnUserId(userId, "backupNow");
         mUserBackupManagerService.backupNow();
     }
 
@@ -364,12 +386,18 @@
      * IBackupManagerMonitor} for receiving events during the operation.
      */
     public int requestBackup(
-            String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags) {
+            @UserIdInt int userId,
+            String[] packages,
+            IBackupObserver observer,
+            IBackupManagerMonitor monitor,
+            int flags) {
+        enforceCallingPermissionOnUserId(userId, "requestBackup");
         return mUserBackupManagerService.requestBackup(packages, observer, monitor, flags);
     }
 
     /** Cancel all running backup operations. */
-    public void cancelBackups() {
+    public void cancelBackups(@UserIdInt int userId) {
+        enforceCallingPermissionOnUserId(userId, "cancelBackups");
         mUserBackupManagerService.cancelBackups();
     }
 
@@ -533,8 +561,10 @@
             stage.renameTo(enableFile);
             // will be synced immediately by the try-with-resources call to close()
         } catch (IOException | RuntimeException e) {
-            Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
-                    + e.getMessage());
+            Slog.e(
+                    TAG,
+                    "Unable to record backup enable state; reverting to disabled: "
+                            + e.getMessage());
             enableFile.delete();
             stage.delete();
         }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 108f50d..ed6ff9b 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -19,6 +19,7 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
 import android.app.backup.IBackupManager;
@@ -123,6 +124,10 @@
                 == MULTI_USER_ENABLED;
     }
 
+    protected int binderGetCallingUserId() {
+        return Binder.getCallingUserHandle().getIdentifier();
+    }
+
     protected int binderGetCallingUid() {
         return Binder.getCallingUid();
     }
@@ -319,14 +324,20 @@
     }
 
     @Override
-    public void setBackupEnabled(boolean isEnabled) throws RemoteException {
+    public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
+            throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.setBackupEnabled(isEnabled);
+            svc.setBackupEnabled(userId, isEnabled);
         }
     }
 
     @Override
+    public void setBackupEnabled(boolean isEnabled) throws RemoteException {
+        setBackupEnabledForUser(binderGetCallingUserId(), isEnabled);
+    }
+
+    @Override
     public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
@@ -343,9 +354,14 @@
     }
 
     @Override
-    public boolean isBackupEnabled() throws RemoteException {
+    public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
         BackupManagerService svc = mService;
-        return (svc != null) ? svc.isBackupEnabled() : false;
+        return (svc != null) ? svc.isBackupEnabled(userId) : false;
+    }
+
+    @Override
+    public boolean isBackupEnabled() throws RemoteException {
+        return isBackupEnabledForUser(binderGetCallingUserId());
     }
 
     @Override
@@ -361,14 +377,19 @@
     }
 
     @Override
-    public void backupNow() throws RemoteException {
+    public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.backupNow();
+            svc.backupNow(userId);
         }
     }
 
     @Override
+    public void backupNow() throws RemoteException {
+        backupNowForUser(binderGetCallingUserId());
+    }
+
+    @Override
     public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps,
             boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
@@ -543,21 +564,33 @@
     }
 
     @Override
-    public int requestBackup(String[] packages, IBackupObserver observer,
-            IBackupManagerMonitor monitor, int flags) throws RemoteException {
+    public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
+            observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc == null) {
             return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
         }
-        return svc.requestBackup(packages, observer, monitor, flags);
+        return svc.requestBackup(userId, packages, observer, monitor, flags);
+    }
+
+    @Override
+    public int requestBackup(String[] packages, IBackupObserver observer,
+            IBackupManagerMonitor monitor, int flags) throws RemoteException {
+        return requestBackupForUser(binderGetCallingUserId(), packages,
+                observer, monitor, flags);
+    }
+
+    @Override
+    public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
+        BackupManagerService svc = mService;
+        if (svc != null) {
+            svc.cancelBackups(userId);
+        }
     }
 
     @Override
     public void cancelBackups() throws RemoteException {
-        BackupManagerService svc = mService;
-        if (svc != null) {
-            svc.cancelBackups();
-        }
+        cancelBackupsForUser(binderGetCallingUserId());
     }
 
     @Override
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
index 2302b7d..a4012d5 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
@@ -29,7 +29,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
-import com.android.server.infra.AbstractRemoteService;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -124,8 +123,8 @@
         }
     }
 
-    @Override // from RemoteScreenObservationServiceCallbacks
-    public void onServiceDied(AbstractRemoteService<?> service) {
+    @Override // from RemoteContentCaptureServiceCallbacks
+    public void onServiceDied(@NonNull RemoteContentCaptureService service) {
         // TODO(b/111276913): implement (remove session from PerUserSession?)
         if (mService.isDebug()) {
             Slog.d(TAG, "onServiceDied() for " + mId);
@@ -135,17 +134,6 @@
         }
     }
 
-    @Override // from RemoteScreenObservationServiceCallbacks
-    public void onFailureOrTimeout(boolean timedOut) {
-        // TODO(b/111276913): log metrics on whether timed out or not
-        if (mService.isDebug()) {
-            Slog.d(TAG, "onFailureOrTimeout(" + mId + "): timed out=" + timedOut);
-        }
-        synchronized (mLock) {
-            removeSelfLocked(/* notifyRemoteService= */ false);
-        }
-    }
-
     @GuardedBy("mLock")
     public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
         pw.print(prefix); pw.print("id: ");  pw.print(mId); pw.println();
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 6a111f2..33b6c8d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -20,14 +20,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.IBinder;
-import android.os.IInterface;
-import android.os.RemoteException;
 import android.service.contentcapture.ContentCaptureEventsRequest;
 import android.service.contentcapture.IContentCaptureService;
 import android.service.contentcapture.InteractionContext;
 import android.service.contentcapture.SnapshotData;
 import android.text.format.DateUtils;
-import android.util.Slog;
 import android.view.contentcapture.ContentCaptureEvent;
 
 import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService;
@@ -35,30 +32,24 @@
 import java.util.List;
 
 final class RemoteContentCaptureService
-        extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService> {
-
-    private static final String TAG = RemoteContentCaptureService.class.getSimpleName();
+        extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService,
+        IContentCaptureService> {
 
     // TODO(b/117779333): changed it so it's permanentely bound
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
-    private final ContentCaptureServiceCallbacks mCallbacks;
-    private IContentCaptureService mService;
-
     RemoteContentCaptureService(Context context, String serviceInterface,
             ComponentName componentName, int userId,
             ContentCaptureServiceCallbacks callbacks, boolean bindInstantServiceAllowed,
             boolean verbose) {
         super(context, serviceInterface, componentName, userId, callbacks,
                 bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2);
-        mCallbacks = callbacks;
     }
 
     @Override // from RemoteService
-    protected IInterface getServiceInterface(@NonNull IBinder service) {
-        mService = IContentCaptureService.Stub.asInterface(service);
-        return mService;
+    protected IContentCaptureService getServiceInterface(@NonNull IBinder service) {
+        return IContentCaptureService.Stub.asInterface(service);
     }
 
     // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative
@@ -81,8 +72,7 @@
      */
     public void onSessionLifecycleRequest(@Nullable InteractionContext context,
             @NonNull String sessionId) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingSessionLifecycleRequest(this, context, sessionId));
+        scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId));
     }
 
     /**
@@ -90,8 +80,8 @@
      */
     public void onContentCaptureEventsRequest(@NonNull String sessionId,
             @NonNull List<ContentCaptureEvent> events) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingOnContentCaptureEventsRequest(this, sessionId, events));
+        scheduleAsyncRequest((s) -> s.onContentCaptureEventsRequest(sessionId,
+                new ContentCaptureEventsRequest(events)));
     }
 
     /**
@@ -99,103 +89,13 @@
      */
     public void onActivitySnapshotRequest(@NonNull String sessionId,
             @NonNull SnapshotData snapshotData) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingOnActivitySnapshotRequest(this, sessionId, snapshotData));
+        scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
     }
 
-    private abstract static class MyPendingRequest
-            extends PendingRequest<RemoteContentCaptureService> {
-        protected final String mSessionId;
-
-        private MyPendingRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId) {
-            super(service);
-            mSessionId = sessionId;
-        }
-
-        @Override // from PendingRequest
-        protected final void onTimeout(RemoteContentCaptureService remoteService) {
-            Slog.w(TAG, "timed out handling " + getClass().getSimpleName() + " for "
-                    + mSessionId);
-            remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ true);
-        }
-
-        @Override // from PendingRequest
-        public final void run() {
-            final RemoteContentCaptureService remoteService = getService();
-            if (remoteService != null) {
-                try {
-                    // We don't expect the service to call us back, so we finish right away.
-                    myRun(remoteService);
-                    // TODO(b/111330312): not true anymore!!
-                    finish();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "exception handling " + getClass().getSimpleName() + " for "
-                            + mSessionId + ": " + e);
-                    remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ false);
-                }
-            }
-        }
-
-        protected abstract void myRun(@NonNull RemoteContentCaptureService service)
-                throws RemoteException;
-
-    }
-
-    private static final class PendingSessionLifecycleRequest extends MyPendingRequest {
-
-        private final InteractionContext mContext;
-
-        protected PendingSessionLifecycleRequest(@NonNull RemoteContentCaptureService service,
-                @Nullable InteractionContext context, @NonNull String sessionId) {
-            super(service, sessionId);
-            mContext = context;
-        }
-
-        @Override // from MyPendingRequest
-        public void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onSessionLifecycle(mContext, mSessionId);
-        }
-    }
-
-    private static final class PendingOnContentCaptureEventsRequest extends MyPendingRequest {
-
-        private final List<ContentCaptureEvent> mEvents;
-
-        protected PendingOnContentCaptureEventsRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId, @NonNull List<ContentCaptureEvent> events) {
-            super(service, sessionId);
-            mEvents = events;
-        }
-
-        @Override // from MyPendingRequest
-        public void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onContentCaptureEventsRequest(mSessionId,
-                    new ContentCaptureEventsRequest(mEvents));
-        }
-    }
-
-    private static final class PendingOnActivitySnapshotRequest extends MyPendingRequest {
-
-        private final SnapshotData mSnapshotData;
-
-        protected PendingOnActivitySnapshotRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId, @NonNull SnapshotData snapshotData) {
-            super(service, sessionId);
-            mSnapshotData = snapshotData;
-        }
-
-        @Override // from MyPendingRequest
-        protected void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onActivitySnapshot(mSessionId, mSnapshotData);
-        }
-    }
-
-    public interface ContentCaptureServiceCallbacks extends VultureCallback {
-        // To keep it simple, we use the same callback for all failures / timeouts.
-        void onFailureOrTimeout(boolean timedOut);
+    public interface ContentCaptureServiceCallbacks
+            extends VultureCallback<RemoteContentCaptureService> {
+        // NOTE: so far we don't need to notify the callback implementation (an inner class on
+        // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
+        // callback interface is empty.
     }
 }
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 11a2fc9..8b7f321 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -19,7 +19,6 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -28,9 +27,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.os.ThreadLocalWorkSource;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -38,19 +35,16 @@
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AppIdToPackageMap;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
-import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.os.CachedDeviceState;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 public class BinderCallsStatsService extends Binder {
 
@@ -60,10 +54,10 @@
             = "persist.sys.binder_calls_detailed_tracking";
 
     /** Resolves the work source of an incoming binder transaction. */
-    static class WorkSourceProvider {
+    static class AuthorizedWorkSourceProvider implements BinderInternal.WorkSourceProvider {
         private ArraySet<Integer> mAppIdWhitelist;
 
-        WorkSourceProvider() {
+        AuthorizedWorkSourceProvider() {
             mAppIdWhitelist = new ArraySet<>();
         }
 
@@ -82,11 +76,11 @@
             mAppIdWhitelist = createAppidWhitelist(context);
         }
 
-        public void dump(PrintWriter pw, Map<Integer, String> appIdToPackageName) {
+        public void dump(PrintWriter pw, AppIdToPackageMap packageMap) {
             pw.println("AppIds of apps that can set the work source:");
             final ArraySet<Integer> whitelist = mAppIdWhitelist;
             for (Integer appId : whitelist) {
-                pw.println("\t- " + appIdToPackageName.getOrDefault(appId, String.valueOf(appId)));
+                pw.println("\t- " + packageMap.mapAppId(appId));
             }
         }
 
@@ -103,7 +97,7 @@
             final ArraySet<Integer> whitelist = new ArraySet<>();
 
             // We trust our own process.
-            whitelist.add(Process.myUid());
+            whitelist.add(UserHandle.getAppId(Process.myUid()));
             // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission.
             final PackageManager pm = context.getPackageManager();
             final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS };
@@ -125,41 +119,6 @@
         }
     }
 
-    /** Observer for all system server incoming binder transactions. */
-    @VisibleForTesting
-    static class BinderCallsObserver implements BinderInternal.Observer {
-        private final BinderInternal.Observer mBinderCallsStats;
-        private final WorkSourceProvider mWorkSourceProvider;
-
-        BinderCallsObserver(BinderInternal.Observer callsStats,
-                WorkSourceProvider workSourceProvider) {
-            mBinderCallsStats = callsStats;
-            mWorkSourceProvider = workSourceProvider;
-        }
-
-        @Override
-        public CallSession callStarted(Binder binder, int code) {
-            // We depend on the code in Binder#execTransact to reset the state of
-            // ThreadLocalWorkSource
-            setThreadLocalWorkSourceUid(mWorkSourceProvider.resolveWorkSourceUid());
-            return mBinderCallsStats.callStarted(binder, code);
-        }
-
-        @Override
-        public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
-            mBinderCallsStats.callEnded(s, parcelRequestSize, parcelReplySize);
-        }
-
-        @Override
-        public void callThrewException(CallSession s, Exception exception) {
-            mBinderCallsStats.callThrewException(s, exception);
-        }
-
-        protected void setThreadLocalWorkSourceUid(int uid) {
-            ThreadLocalWorkSource.setUid(uid);
-        }
-    }
-
     /** Listens for flag changes. */
     private static class SettingsObserver extends ContentObserver {
         private static final String SETTINGS_ENABLED_KEY = "enabled";
@@ -173,16 +132,16 @@
         private final Context mContext;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
         private final BinderCallsStats mBinderCallsStats;
-        private final BinderCallsObserver mBinderCallsObserver;
+        private final AuthorizedWorkSourceProvider mWorkSourceProvider;
 
         SettingsObserver(Context context, BinderCallsStats binderCallsStats,
-                BinderCallsObserver observer) {
+                AuthorizedWorkSourceProvider workSourceProvider) {
             super(BackgroundThread.getHandler());
             mContext = context;
             context.getContentResolver().registerContentObserver(mUri, false, this,
                     UserHandle.USER_SYSTEM);
             mBinderCallsStats = binderCallsStats;
-            mBinderCallsObserver = observer;
+            mWorkSourceProvider = workSourceProvider;
             // Always kick once to ensure that we match current state
             onChange();
         }
@@ -220,12 +179,14 @@
                     mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
             if (mEnabled != enabled) {
                 if (enabled) {
-                    Binder.setObserver(mBinderCallsObserver);
+                    Binder.setObserver(mBinderCallsStats);
                     Binder.setProxyTransactListener(
                             new Binder.PropagateWorkSourceTransactListener());
+                    Binder.setWorkSourceProvider(mWorkSourceProvider);
                 } else {
                     Binder.setObserver(null);
                     Binder.setProxyTransactListener(null);
+                    Binder.setWorkSourceProvider(Binder::getCallingUid);
                 }
                 mEnabled = enabled;
                 mBinderCallsStats.reset();
@@ -268,7 +229,7 @@
     public static class LifeCycle extends SystemService {
         private BinderCallsStatsService mService;
         private BinderCallsStats mBinderCallsStats;
-        private WorkSourceProvider mWorkSourceProvider;
+        private AuthorizedWorkSourceProvider mWorkSourceProvider;
 
         public LifeCycle(Context context) {
             super(context);
@@ -277,11 +238,9 @@
         @Override
         public void onStart() {
             mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
-            mWorkSourceProvider = new WorkSourceProvider();
-            BinderCallsObserver binderCallsObserver =
-                    new BinderCallsObserver(mBinderCallsStats, mWorkSourceProvider);
+            mWorkSourceProvider = new AuthorizedWorkSourceProvider();
             mService = new BinderCallsStatsService(
-                    mBinderCallsStats, binderCallsObserver, mWorkSourceProvider);
+                    mBinderCallsStats, mWorkSourceProvider);
             publishLocalService(Internal.class, new Internal(mBinderCallsStats));
             publishBinderService("binder_calls_stats", mService);
             boolean detailedTrackingEnabled = SystemProperties.getBoolean(
@@ -311,18 +270,16 @@
 
     private SettingsObserver mSettingsObserver;
     private final BinderCallsStats mBinderCallsStats;
-    private final BinderCallsObserver mBinderCallsObserver;
-    private final WorkSourceProvider mWorkSourceProvider;
+    private final AuthorizedWorkSourceProvider mWorkSourceProvider;
 
-    BinderCallsStatsService(BinderCallsStats binderCallsStats, BinderCallsObserver observer,
-            WorkSourceProvider workSourceProvider) {
+    BinderCallsStatsService(BinderCallsStats binderCallsStats,
+            AuthorizedWorkSourceProvider workSourceProvider) {
         mBinderCallsStats = binderCallsStats;
-        mBinderCallsObserver = observer;
         mWorkSourceProvider = workSourceProvider;
     }
 
     public void systemReady(Context context) {
-        mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mBinderCallsObserver);
+        mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mWorkSourceProvider);
     }
 
     public void reset() {
@@ -342,7 +299,7 @@
                     pw.println("binder_calls_stats reset.");
                     return;
                 } else if ("--enable".equals(arg)) {
-                    Binder.setObserver(mBinderCallsObserver);
+                    Binder.setObserver(mBinderCallsStats);
                     return;
                 } else if ("--disable".equals(arg)) {
                     Binder.setObserver(null);
@@ -361,7 +318,7 @@
                     pw.println("Detailed tracking disabled");
                     return;
                 } else if ("--dump-worksource-provider".equals(arg)) {
-                    mWorkSourceProvider.dump(pw, getAppIdToPackagesMap());
+                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
                     return;
                 } else if ("-h".equals(arg)) {
                     pw.println("binder_calls_stats commands:");
@@ -377,28 +334,6 @@
                 }
             }
         }
-        mBinderCallsStats.dump(pw, getAppIdToPackagesMap(), verbose);
-    }
-
-    private Map<Integer, String> getAppIdToPackagesMap() {
-        List<PackageInfo> packages;
-        try {
-            packages = AppGlobals.getPackageManager()
-                    .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            UserHandle.USER_SYSTEM).getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        Map<Integer,String> map = new HashMap<>();
-        for (PackageInfo pkg : packages) {
-            String name = pkg.packageName;
-            int uid = pkg.applicationInfo.uid;
-            // Use sharedUserId string as package name if there are collisions
-            if (pkg.sharedUserId != null && map.containsKey(uid)) {
-                name = "shared:" + pkg.sharedUserId;
-            }
-            map.put(uid, name);
-        }
-        return map;
+        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose);
     }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 14503f9..eda9fe1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -902,6 +902,7 @@
         // Listen to package add and removal events for all users.
         intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
         mContext.registerReceiverAsUser(
@@ -4203,12 +4204,46 @@
         mPermissionMonitor.onPackageAdded(packageName, uid);
     }
 
-    private void onPackageRemoved(String packageName, int uid) {
+    private void onPackageReplaced(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Slog.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+                Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.startAlwaysOnVpn();
+            }
+        }
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
         if (TextUtils.isEmpty(packageName) || uid < 0) {
             Slog.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
             return;
         }
         mPermissionMonitor.onPackageRemoved(uid);
+
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+                Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.setAlwaysOnPackage(null, false);
+            }
+        }
     }
 
     private void onUserUnlocked(int userId) {
@@ -4245,8 +4280,12 @@
                 onUserUnlocked(userId);
             } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                 onPackageAdded(packageName, uid);
+            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                onPackageReplaced(packageName, uid);
             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                onPackageRemoved(packageName, uid);
+                final boolean isReplacing = intent.getBooleanExtra(
+                        Intent.EXTRA_REPLACING, false);
+                onPackageRemoved(packageName, uid, isReplacing);
             }
         }
     };
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index fa3baba..cee98c1 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -31,6 +31,7 @@
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
+import com.android.internal.os.AppIdToPackageMap;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.CachedDeviceState;
 import com.android.internal.os.LooperStats;
@@ -92,6 +93,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot();
         pw.print("Start time: ");
         pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis()));
         pw.print("On battery time (ms): ");
@@ -121,7 +123,7 @@
         pw.println(header);
         for (LooperStats.ExportedEntry entry : entries) {
             pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
-                    entry.workSourceUid,
+                    packageMap.mapUid(entry.workSourceUid),
                     entry.threadName,
                     entry.handlerClassName,
                     entry.messageName,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 40da881..c9cab98 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17532,8 +17532,9 @@
         // how many slots we have for background processes; we may want
         // to put multiple processes in a slot of there are enough of
         // them.
-        int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
-                - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;
+        final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
+                - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
+                / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
         int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
         if (numEmptyProcs > cachedProcessLimit) {
             // If there are more empty processes than our limit on cached
@@ -17544,17 +17545,19 @@
             // instead of a gazillion empty processes.
             numEmptyProcs = cachedProcessLimit;
         }
-        int emptyFactor = numEmptyProcs/numSlots;
+        int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
         if (emptyFactor < 1) emptyFactor = 1;
-        int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;
+        int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
+                / numSlots;
         if (cachedFactor < 1) cachedFactor = 1;
-        int stepCached = 0;
-        int stepEmpty = 0;
+        int stepCached = -1;
+        int stepEmpty = -1;
         int numCached = 0;
         int numCachedExtraGroup = 0;
         int numEmpty = 0;
         int numTrimming = 0;
         int lastCachedGroup = 0;
+        int lastCachedGroupImportance = 0;
         int lastCachedGroupUid = 0;
 
         mNumNonCachedProcs = 0;
@@ -17563,9 +17566,10 @@
         // First update the OOM adjustment for each of the
         // application processes based on their current state.
         int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
-        int nextCachedAdj = curCachedAdj+1;
-        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
-        int nextEmptyAdj = curEmptyAdj+2;
+        int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+        int curCachedImpAdj = 0;
+        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
 
         boolean retryCycles = false;
 
@@ -17590,27 +17594,61 @@
                         case PROCESS_STATE_CACHED_ACTIVITY:
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                         case ActivityManager.PROCESS_STATE_CACHED_RECENT:
-                            // This process is a cached process holding activities...
-                            // assign it the next cached value for that type, and then
-                            // step that cached level.
-                            app.setCurRawAdj(curCachedAdj);
-                            app.curAdj = app.modifyRawOomAdj(curCachedAdj);
-                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
-                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
-                                    + ")");
-                            if (curCachedAdj != nextCachedAdj) {
+                            // Figure out the next cached level, taking into account groups.
+                            boolean inGroup = false;
+                            if (app.connectionGroup != 0) {
+                                if (lastCachedGroupUid == app.uid
+                                        && lastCachedGroup == app.connectionGroup) {
+                                    // This is in the same group as the last process, just tweak
+                                    // adjustment by importance.
+                                    if (app.connectionImportance > lastCachedGroupImportance) {
+                                        lastCachedGroupImportance = app.connectionImportance;
+                                        if (curCachedAdj < nextCachedAdj
+                                                && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
+                                            curCachedImpAdj++;
+                                        }
+                                    }
+                                    inGroup = true;
+                                } else {
+                                    lastCachedGroupUid = app.uid;
+                                    lastCachedGroup = app.connectionGroup;
+                                    lastCachedGroupImportance = app.connectionImportance;
+                                }
+                            }
+                            if (!inGroup && curCachedAdj != nextCachedAdj) {
                                 stepCached++;
+                                curCachedImpAdj = 0;
                                 if (stepCached >= cachedFactor) {
                                     stepCached = 0;
                                     curCachedAdj = nextCachedAdj;
-                                    nextCachedAdj += 2;
+                                    nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
                                     if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
                                         nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
                                     }
                                 }
                             }
+                            // This process is a cached process holding activities...
+                            // assign it the next cached value for that type, and then
+                            // step that cached level.
+                            app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+                            app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+                                    + " curCachedImpAdj=" + curCachedImpAdj + ")");
                             break;
                         default:
+                            // Figure out the next cached level.
+                            if (curEmptyAdj != nextEmptyAdj) {
+                                stepEmpty++;
+                                if (stepEmpty >= emptyFactor) {
+                                    stepEmpty = 0;
+                                    curEmptyAdj = nextEmptyAdj;
+                                    nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                    }
+                                }
+                            }
                             // For everything else, assign next empty cached process
                             // level and bump that up.  Note that this means that
                             // long-running services that have dropped down to the
@@ -17621,22 +17659,9 @@
                             if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
                                     + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
                                     + ")");
-                            if (curEmptyAdj != nextEmptyAdj) {
-                                stepEmpty++;
-                                if (stepEmpty >= emptyFactor) {
-                                    stepEmpty = 0;
-                                    curEmptyAdj = nextEmptyAdj;
-                                    nextEmptyAdj += 2;
-                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
-                                    }
-                                }
-                            }
                             break;
                     }
                 }
-
-
             }
         }
 
@@ -17668,6 +17693,8 @@
             }
         }
 
+        lastCachedGroup = lastCachedGroupUid = 0;
+
         for (int i=N-1; i>=0; i--) {
             ProcessRecord app = mProcessList.mLruProcesses.get(i);
             if (!app.killedByAm && app.thread != null) {
@@ -18935,7 +18962,7 @@
                     ProcessMemoryState processMemoryState =
                             new ProcessMemoryState(uid,
                                     r.processName,
-                                    r.maxAdj,
+                                    r.curAdj,
                                     memoryStat.pgfault,
                                     memoryStat.pgmajfault,
                                     memoryStat.rssInBytes,
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 8c39d75..3a0899d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1337,7 +1337,7 @@
         // Broadcast is being executed, its package can't be stopped.
         try {
             AppGlobals.getPackageManager().setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, UserHandle.getUserId(r.callingUid));
+                    r.curComponent.getPackageName(), false, r.userId);
         } catch (RemoteException e) {
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 8cf9f1e..117984e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -137,9 +137,13 @@
 
     // This is a process only hosting activities that are not visible,
     // so it can be killed without any disruption.
-    static final int CACHED_APP_MAX_ADJ = 906;
+    static final int CACHED_APP_MAX_ADJ = 999;
     static final int CACHED_APP_MIN_ADJ = 900;
 
+    // Number of levels we have available for different service connection group importance
+    // levels.
+    static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+
     // The B list of SERVICE_ADJ -- these are the old and decrepit
     // services that aren't as shiny and interesting as the ones in the A list.
     static final int SERVICE_B_ADJ = 800;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 3c14393..d75601b 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -944,10 +944,11 @@
     public boolean hasTetherableConfiguration() {
         final TetheringConfiguration cfg = mConfig;
         final boolean hasDownstreamConfiguration =
-                (cfg.tetherableUsbRegexs.length != 0) ||
-                (cfg.tetherableWifiRegexs.length != 0) ||
-                (cfg.tetherableBluetoothRegexs.length != 0);
-        final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty();
+                (cfg.tetherableUsbRegexs.length != 0)
+                || (cfg.tetherableWifiRegexs.length != 0)
+                || (cfg.tetherableBluetoothRegexs.length != 0);
+        final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty()
+                || cfg.chooseUpstreamAutomatically;
 
         return hasDownstreamConfiguration && hasUpstreamConfiguration;
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b7ed2f9..602aedb 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -206,45 +206,6 @@
     // Handle of the user initiating VPN.
     private final int mUserHandle;
 
-    // Listen to package removal and change events (update/uninstall) for this user
-    private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final Uri data = intent.getData();
-            final String packageName = data == null ? null : data.getSchemeSpecificPart();
-            if (packageName == null) {
-                return;
-            }
-
-            synchronized (Vpn.this) {
-                // Avoid race where always-on package has been unset
-                if (!packageName.equals(getAlwaysOnPackage())) {
-                    return;
-                }
-
-                final String action = intent.getAction();
-                Log.i(TAG, "Received broadcast " + action + " for always-on VPN package "
-                        + packageName + " in user " + mUserHandle);
-
-                switch(action) {
-                    case Intent.ACTION_PACKAGE_REPLACED:
-                        // Start vpn after app upgrade
-                        startAlwaysOnVpn();
-                        break;
-                    case Intent.ACTION_PACKAGE_REMOVED:
-                        final boolean isPackageRemoved = !intent.getBooleanExtra(
-                                Intent.EXTRA_REPLACING, false);
-                        if (isPackageRemoved) {
-                            setAlwaysOnPackage(null, false);
-                        }
-                        break;
-                }
-            }
-        }
-    };
-
-    private boolean mIsPackageIntentReceiverRegistered = false;
-
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle) {
         this(looper, context, netService, userHandle, new SystemServices(context));
@@ -500,7 +461,6 @@
             // Prepare this app. The notification will update as a side-effect of updateState().
             prepareInternal(packageName);
         }
-        maybeRegisterPackageChangeReceiverLocked(packageName);
         setVpnForcedLocked(mLockdown);
         return true;
     }
@@ -509,31 +469,6 @@
         return packageName == null || VpnConfig.LEGACY_VPN.equals(packageName);
     }
 
-    private void unregisterPackageChangeReceiverLocked() {
-        if (mIsPackageIntentReceiverRegistered) {
-            mContext.unregisterReceiver(mPackageIntentReceiver);
-            mIsPackageIntentReceiverRegistered = false;
-        }
-    }
-
-    private void maybeRegisterPackageChangeReceiverLocked(String packageName) {
-        // Unregister IntentFilter listening for previous always-on package change
-        unregisterPackageChangeReceiverLocked();
-
-        if (!isNullOrLegacyVpn(packageName)) {
-            mIsPackageIntentReceiverRegistered = true;
-
-            IntentFilter intentFilter = new IntentFilter();
-            // Protected intent can only be sent by system. No permission required in register.
-            intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            intentFilter.addDataScheme("package");
-            intentFilter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
-            mContext.registerReceiverAsUser(
-                    mPackageIntentReceiver, UserHandle.of(mUserHandle), intentFilter, null, null);
-        }
-    }
-
     /**
      * @return the package name of the VPN controller responsible for always-on VPN,
      *         or {@code null} if none is set or always-on VPN is controlled through
@@ -1302,7 +1237,6 @@
         setLockdown(false);
         mAlwaysOn = false;
 
-        unregisterPackageChangeReceiverLocked();
         // Quit any active connections
         agentDisconnect();
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index de0f298..25ca278 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1082,13 +1082,14 @@
         assertRunOnServiceThread();
 
         if (!canStartArcUpdateAction(message.getSource(), true)) {
-            if (getAvrDeviceInfo() == null) {
+            HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
+            if (avrDeviceInfo == null) {
                 // AVR may not have been discovered yet. Delay the message processing.
                 mDelayedMessageBuffer.add(message);
                 return true;
             }
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            if (!isConnectedToArcPort(message.getSource())) {
+            if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
             }
             return true;
diff --git a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
index 513a6a3..aaea45e 100644
--- a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.IInterface;
 import android.util.Slog;
 
 import java.io.PrintWriter;
@@ -29,21 +30,21 @@
  * bound.
  *
  * @param <S> the concrete remote service class
- *
+ * @param <I> the interface of the binder service
  * @hide
  */
-public abstract class AbstractMultiplePendingRequestsRemoteService<
-        S extends AbstractMultiplePendingRequestsRemoteService<S>>
-        extends AbstractRemoteService<S> {
+public abstract class AbstractMultiplePendingRequestsRemoteService<S
+        extends AbstractMultiplePendingRequestsRemoteService<S, I>, I extends IInterface>
+        extends AbstractRemoteService<S, I> {
 
     private final int mInitialCapacity;
 
-    protected ArrayList<PendingRequest<S>> mPendingRequests;
+    protected ArrayList<PendingRequest<S, I>> mPendingRequests;
 
     public AbstractMultiplePendingRequestsRemoteService(@NonNull Context context,
             @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId,
-            @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, boolean verbose,
-            int initialCapacity) {
+            @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed,
+            boolean verbose, int initialCapacity) {
         super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
                 verbose);
         mInitialCapacity = initialCapacity;
@@ -84,7 +85,7 @@
     }
 
     @Override // from AbstractRemoteService
-    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) {
+    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) {
         if (mPendingRequests == null) {
             mPendingRequests = new ArrayList<>(mInitialCapacity);
         }
diff --git a/services/core/java/com/android/server/infra/AbstractRemoteService.java b/services/core/java/com/android/server/infra/AbstractRemoteService.java
index 67b3ecf..41dcf89 100644
--- a/services/core/java/com/android/server/infra/AbstractRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractRemoteService.java
@@ -54,13 +54,13 @@
  * (no pun intended) example of how to use it.
  *
  * @param <S> the concrete remote service class
+ * @param <I> the interface of the binder service
  *
  * @hide
  */
 //TODO(b/117779333): improve javadoc above instead of using Autofill as an example
-public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>>
-        implements DeathRecipient {
-
+public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>,
+        I extends IInterface> implements DeathRecipient {
     private static final int MSG_UNBIND = 1;
 
     protected static final int LAST_PRIVATE_MSG = MSG_UNBIND;
@@ -74,11 +74,11 @@
 
     private final Context mContext;
     private final Intent mIntent;
-    private final VultureCallback mVultureCallback;
+    private final VultureCallback<S> mVultureCallback;
     private final int mUserId;
     private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
     private final boolean mBindInstantServiceAllowed;
-    private IInterface mServiceInterface;
+    protected I mService;
 
     private boolean mBinding;
     private boolean mDestroyed;
@@ -87,19 +87,21 @@
 
     /**
      * Callback called when the service dies.
+     *
+     * @param <T> service class
      */
-    public interface VultureCallback {
+    public interface VultureCallback<T> {
         /**
          * Called when the service dies.
          *
          * @param service service that died!
          */
-        void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service);
+        void onServiceDied(T service);
     }
 
     // NOTE: must be package-protected so this class is not extend outside
     AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface,
-            @NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback,
+            @NonNull ComponentName componentName, int userId, @NonNull VultureCallback<S> callback,
             boolean bindInstantServiceAllowed, boolean verbose) {
         mContext = context;
         mVultureCallback = callback;
@@ -150,7 +152,7 @@
      * Gets the base Binder interface from the service.
      */
     @NonNull
-    protected abstract IInterface getServiceInterface(@NonNull IBinder service);
+    protected abstract I getServiceInterface(@NonNull IBinder service);
 
     /**
      * Defines How long after the last interaction with the service we would unbind.
@@ -183,12 +185,14 @@
 
     private void handleBinderDied() {
         if (checkIfDestroyed()) return;
-        if (mServiceInterface != null) {
-            mServiceInterface.asBinder().unlinkToDeath(this, 0);
+        if (mService != null) {
+            mService.asBinder().unlinkToDeath(this, 0);
         }
-        mServiceInterface = null;
+        mService = null;
         mServiceDied = true;
-        mVultureCallback.onServiceDied(this);
+        @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
+        final S castService = (S) this;
+        mVultureCallback.onServiceDied(castService);
     }
 
     // Note: we are dumping without a lock held so this is a bit racy but
@@ -216,12 +220,35 @@
         pw.println();
     }
 
-    protected void scheduleRequest(@NonNull PendingRequest<S> pendingRequest) {
+    /**
+     * Schedules a "sync" request.
+     *
+     * <p>This request must be responded by the service somehow (typically using a callback),
+     * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the
+     * service doesn't respond.
+     */
+    protected void scheduleRequest(@NonNull PendingRequest<S, I> pendingRequest) {
+        cancelScheduledUnbind();
         mHandler.sendMessage(obtainMessage(
                 AbstractRemoteService::handlePendingRequest, this, pendingRequest));
     }
 
-    protected void cancelScheduledUnbind() {
+    /**
+     * Schedules an async request.
+     *
+     * <p>This request is not expecting a callback from the service, hence it's represented by
+     * a simple {@link Runnable}.
+     */
+    protected void scheduleAsyncRequest(@NonNull AsyncRequest<I> request) {
+        cancelScheduledUnbind();
+        // TODO(b/117779333): fix generics below
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        final MyAsyncPendingRequest<S, I> asyncRequest = new MyAsyncPendingRequest(this, request);
+        mHandler.sendMessage(
+                obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest));
+    }
+
+    private void cancelScheduledUnbind() {
         mHandler.removeMessages(MSG_UNBIND);
     }
 
@@ -244,7 +271,7 @@
      * Handles a request, either processing it right now when bound, or saving it to be handled when
      * bound.
      */
-    protected final void handlePendingRequest(@NonNull PendingRequest<S> pendingRequest) {
+    protected final void handlePendingRequest(@NonNull PendingRequest<S, I> pendingRequest) {
         if (checkIfDestroyed() || mCompleted) return;
 
         if (!handleIsBound()) {
@@ -263,10 +290,10 @@
     /**
      * Defines what to do with a request that arrives while not bound to the service.
      */
-    abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest);
+    abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest);
 
     private boolean handleIsBound() {
-        return mServiceInterface != null;
+        return mService != null;
     }
 
     private void handleEnsureBound() {
@@ -300,9 +327,9 @@
         mBinding = false;
         if (handleIsBound()) {
             handleOnConnectedStateChangedInternal(false);
-            if (mServiceInterface != null) {
-                mServiceInterface.asBinder().unlinkToDeath(this, 0);
-                mServiceInterface = null;
+            if (mService != null) {
+                mService.asBinder().unlinkToDeath(this, 0);
+                mService = null;
             }
         }
         mContext.unbindService(mServiceConnection);
@@ -318,7 +345,7 @@
                 return;
             }
             mBinding = false;
-            mServiceInterface = getServiceInterface(service);
+            mService = getServiceInterface(service);
             try {
                 service.linkToDeath(AbstractRemoteService.this, 0);
             } catch (RemoteException re) {
@@ -332,7 +359,7 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             mBinding = true;
-            mServiceInterface = null;
+            mService = null;
         }
     }
 
@@ -349,10 +376,15 @@
     /**
      * Base class for the requests serviced by the remote service.
      *
+     * <p><b>NOTE: </b> this class is typically used when the service needs to use a callback to
+     * communicate back with the system server. For cases where that's not needed, you should use
+     * {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} instead.
+     *
      * @param <S> the remote service class
+     * @param <I> the interface of the binder service
      */
-    public abstract static class PendingRequest<S extends AbstractRemoteService<S>>
-            implements Runnable {
+    public abstract static class PendingRequest<S extends AbstractRemoteService<S, I>,
+            I extends IInterface> implements Runnable {
         protected final String mTag = getClass().getSimpleName();
         protected final Object mLock = new Object();
 
@@ -366,7 +398,7 @@
         @GuardedBy("mLock")
         private boolean mCompleted;
 
-        protected PendingRequest(S service) {
+        protected PendingRequest(@NonNull S service) {
             mWeakService = new WeakReference<>(service);
             mServiceHandler = service.mHandler;
             mTimeoutTrigger = () -> {
@@ -452,4 +484,50 @@
             return false;
         }
     }
+
+    /**
+     * Represents a request that does not expect a callback from the remote service.
+     *
+     * @param <I> the interface of the binder service
+     */
+    public interface AsyncRequest<I extends IInterface> {
+
+        /**
+         * Run Forrest, run!
+         */
+        void run(@NonNull I binder) throws RemoteException;
+    }
+
+    private static final class MyAsyncPendingRequest<S extends AbstractRemoteService<S, I>,
+            I extends IInterface> extends PendingRequest<S, I> {
+        private static final String TAG = MyAsyncPendingRequest.class.getSimpleName();
+
+        private final AsyncRequest<I> mRequest;
+
+        protected MyAsyncPendingRequest(@NonNull S service, @NonNull AsyncRequest<I> request) {
+            super(service);
+
+            mRequest = request;
+        }
+
+        @Override
+        public void run() {
+            final S remoteService = getService();
+            if (remoteService == null) return;
+            try {
+                mRequest.run(remoteService.mService);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "exception handling async request (" + this + "): " + e);
+            } finally {
+                finish();
+            }
+        }
+
+        @Override
+        protected void onTimeout(S remoteService) {
+            // TODO(b/117779333): should not happen because we called finish() on run(), although
+            // currently it might be called if the service is destroyed while showing it.
+            Slog.w(TAG, "AsyncPending requested timed out");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
index 37a1f54..d32f13b 100644
--- a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.IInterface;
 import android.util.Slog;
 
 import java.io.PrintWriter;
@@ -29,17 +30,19 @@
  * <p>If another request is received while not bound, the previous one will be canceled.
  *
  * @param <S> the concrete remote service class
+ * @param <I> the interface of the binder service
  *
  * @hide
  */
-public abstract class AbstractSinglePendingRequestRemoteService<
-        S extends AbstractSinglePendingRequestRemoteService<S>> extends AbstractRemoteService<S> {
+public abstract class AbstractSinglePendingRequestRemoteService<S
+        extends AbstractSinglePendingRequestRemoteService<S, I>, I extends IInterface>
+        extends AbstractRemoteService<S, I> {
 
-    protected PendingRequest<S> mPendingRequest;
+    protected PendingRequest<S, I> mPendingRequest;
 
     public AbstractSinglePendingRequestRemoteService(@NonNull Context context,
             @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId,
-            @NonNull VultureCallback callback, boolean bindInstantServiceAllowed,
+            @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed,
             boolean verbose) {
         super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
                 verbose);
@@ -48,7 +51,7 @@
     @Override // from AbstractRemoteService
     void handlePendingRequests() {
         if (mPendingRequest != null) {
-            final PendingRequest<S> pendingRequest = mPendingRequest;
+            final PendingRequest<S, I> pendingRequest = mPendingRequest;
             mPendingRequest = null;
             handlePendingRequest(pendingRequest);
         }
@@ -70,7 +73,7 @@
     }
 
     @Override // from AbstractRemoteService
-    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) {
+    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) {
         if (mPendingRequest != null) {
             if (mVerbose) {
                 Slog.v(mTag, "handlePendingRequestWhileUnBound(): cancelling " + mPendingRequest
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d96b6cb..e7c3c7b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1951,6 +1951,11 @@
     }
 
     // Native callback.
+    private int getPointerDisplayId() {
+        return mWindowManagerCallbacks.getPointerDisplayId();
+    }
+
+    // Native callback.
     private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         if (!mSystemReady) {
             return null;
@@ -2017,6 +2022,8 @@
                 KeyEvent event, int policyFlags);
 
         public int getPointerLayer();
+
+        public int getPointerDisplayId();
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
index 6e08949..26e8270 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -16,13 +16,17 @@
 
 package com.android.server.locksettings.recoverablekeystore.certificate;
 
-import static javax.xml.xpath.XPathConstants.NODESET;
-
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -40,7 +44,6 @@
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CollectionCertStoreParameters;
@@ -58,15 +61,6 @@
 
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
 
 /** Utility functions related to parsing and validating public-key certificates. */
 public final class CertUtils {
@@ -167,50 +161,63 @@
     static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode,
             String... nodeTags)
             throws CertParsingException {
-        String expression = String.join("/", nodeTags);
-
-        XPath xPath = XPathFactory.newInstance().newXPath();
-        NodeList nodeList;
-        try {
-            nodeList = (NodeList) xPath.compile(expression).evaluate(rootNode, NODESET);
-        } catch (XPathExpressionException e) {
-            throw new CertParsingException(e);
+        if (nodeTags.length == 0) {
+            throw new CertParsingException("The tag list must not be empty");
         }
 
-        switch (mustExist) {
-            case MUST_EXIST_UNENFORCED:
-                break;
-
-            case MUST_EXIST_EXACTLY_ONE:
-                if (nodeList.getLength() != 1) {
-                    throw new CertParsingException(
-                            "The XML file must contain exactly one node with the path "
-                                    + expression);
-                }
-                break;
-
-            case MUST_EXIST_AT_LEAST_ONE:
-                if (nodeList.getLength() == 0) {
-                    throw new CertParsingException(
-                            "The XML file must contain at least one node with the path "
-                                    + expression);
-                }
-                break;
-
-            default:
-                throw new UnsupportedOperationException(
-                        "This value of MustExist is not supported: " + mustExist);
+        // Go down through all the intermediate node tags (except the last tag for the leaf nodes).
+        // Note that this implementation requires that at most one path exists for the given
+        // intermediate node tags.
+        Element parent = rootNode;
+        for (int i = 0; i < nodeTags.length - 1; i++) {
+            String tag = nodeTags[i];
+            List<Element> children = getXmlDirectChildren(parent, tag);
+            if ((children.size() == 0 && mustExist != MUST_EXIST_UNENFORCED)
+                    || children.size() > 1) {
+                throw new CertParsingException(
+                        "The XML file must contain exactly one path with the tag " + tag);
+            }
+            if (children.size() == 0) {
+                return new ArrayList<>();
+            }
+            parent = children.get(0);
         }
 
+        // Then collect the contents of the leaf nodes.
+        List<Element> leafs = getXmlDirectChildren(parent, nodeTags[nodeTags.length - 1]);
+        if (mustExist == MUST_EXIST_EXACTLY_ONE && leafs.size() != 1) {
+            throw new CertParsingException(
+                    "The XML file must contain exactly one node with the path "
+                            + String.join("/", nodeTags));
+        }
+        if (mustExist == MUST_EXIST_AT_LEAST_ONE && leafs.size() == 0) {
+            throw new CertParsingException(
+                    "The XML file must contain at least one node with the path "
+                            + String.join("/", nodeTags));
+        }
         List<String> result = new ArrayList<>();
-        for (int i = 0; i < nodeList.getLength(); i++) {
-            Node node = nodeList.item(i);
+        for (Element leaf : leafs) {
             // Remove whitespaces and newlines.
-            result.add(node.getTextContent().replaceAll("\\s", ""));
+            result.add(leaf.getTextContent().replaceAll("\\s", ""));
         }
         return result;
     }
 
+    /** Get the direct child nodes with a given tag. */
+    private static List<Element> getXmlDirectChildren(Element parent, String tag) {
+        // Cannot use Element.getElementsByTagName because it will return all descendant elements
+        // with the tag name, i.e. not only the direct child nodes.
+        List<Element> children = new ArrayList<>();
+        NodeList childNodes = parent.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node node = childNodes.item(i);
+            if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(tag)) {
+                children.add((Element) node);
+            }
+        }
+        return children;
+    }
+
     /**
      * Decodes a base64-encoded string.
      *
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f19ecf3..2d2f9e3 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -207,6 +207,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.DeviceIdleController;
 import com.android.server.EventLogTags;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.lights.Light;
@@ -265,7 +266,7 @@
 
     // message codes
     static final int MESSAGE_DURATION_REACHED = 2;
-    static final int MESSAGE_SAVE_POLICY_FILE = 3;
+    // 3: removed to a different handler
     static final int MESSAGE_SEND_RANKING_UPDATE = 4;
     static final int MESSAGE_LISTENER_HINTS_CHANGED = 5;
     static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6;
@@ -573,7 +574,7 @@
             mListeners.migrateToXml();
             mAssistants.migrateToXml();
             mConditionProviders.migrateToXml();
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         mAssistants.ensureAssistant();
@@ -603,34 +604,29 @@
         }
     }
 
-    /**
-     * Saves notification policy
-     */
-    public void savePolicyFile() {
-        mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE);
-        mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE);
-    }
+    @VisibleForTesting
+    protected void handleSavePolicyFile() {
+        IoThread.getHandler().post(() -> {
+            if (DBG) Slog.d(TAG, "handleSavePolicyFile");
+            synchronized (mPolicyFile) {
+                final FileOutputStream stream;
+                try {
+                    stream = mPolicyFile.startWrite();
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to save policy file", e);
+                    return;
+                }
 
-    private void handleSavePolicyFile() {
-        if (DBG) Slog.d(TAG, "handleSavePolicyFile");
-        synchronized (mPolicyFile) {
-            final FileOutputStream stream;
-            try {
-                stream = mPolicyFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to save policy file", e);
-                return;
+                try {
+                    writePolicyXml(stream, false /*forBackup*/);
+                    mPolicyFile.finishWrite(stream);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to save policy file, restoring backup", e);
+                    mPolicyFile.failWrite(stream);
+                }
             }
-
-            try {
-                writePolicyXml(stream, false /*forBackup*/);
-                mPolicyFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to save policy file, restoring backup", e);
-                mPolicyFile.failWrite(stream);
-            }
-        }
-        BackupManager.dataChanged(getContext().getPackageName());
+            BackupManager.dataChanged(getContext().getPackageName());
+        });
     }
 
     private void writePolicyXml(OutputStream stream, boolean forBackup) throws IOException {
@@ -1138,8 +1134,9 @@
 
                     }
                 }
+
                 mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList);
-                savePolicyFile();
+                handleSavePolicyFile();
             }
         }
     };
@@ -1206,7 +1203,7 @@
                 mListeners.onUserRemoved(userId);
                 mConditionProviders.onUserRemoved(userId);
                 mAssistants.onUserRemoved(userId);
-                savePolicyFile();
+                handleSavePolicyFile();
             } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mUserProfiles.updateCache(context);
@@ -1462,7 +1459,7 @@
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
-                savePolicyFile();
+                handleSavePolicyFile();
             }
 
             @Override
@@ -1764,7 +1761,7 @@
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
         }
 
-        savePolicyFile();
+        handleSavePolicyFile();
     }
 
     private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
@@ -2232,7 +2229,7 @@
                 Slog.w(TAG, "Can't notify app about app block change", e);
             }
 
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         /**
@@ -2289,7 +2286,7 @@
         public void setShowBadge(String pkg, int uid, boolean showBadge) {
             checkCallerIsSystem();
             mPreferencesHelper.setShowBadge(pkg, uid, showBadge);
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -2305,7 +2302,7 @@
                 if (info != null) {
                     mPreferencesHelper.setNotificationDelegate(
                             callingPkg, callingUid, delegate, info.uid);
-                    savePolicyFile();
+                    handleSavePolicyFile();
                 }
             } catch (RemoteException e) {
                 // :(
@@ -2316,7 +2313,7 @@
         public void revokeNotificationDelegate(String callingPkg) {
             checkCallerIsSameApp(callingPkg);
             mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid());
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -2351,7 +2348,7 @@
                 NotificationChannelGroup group) throws RemoteException {
             enforceSystemOrSystemUI("Caller not system or systemui");
             createNotificationChannelGroup(pkg, uid, group, false, false);
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -2364,7 +2361,7 @@
                 final NotificationChannelGroup group = groups.get(i);
                 createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false);
             }
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         private void createNotificationChannelsImpl(String pkg, int uid,
@@ -2382,7 +2379,7 @@
                         mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
                         NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
             }
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -2427,7 +2424,7 @@
                     UserHandle.getUserHandleForUid(callingUid),
                     mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true),
                     NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -2469,7 +2466,7 @@
                 mListeners.notifyNotificationChannelGroupChanged(
                         pkg, UserHandle.getUserHandleForUid(callingUid), groupToDelete,
                         NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
-                savePolicyFile();
+                handleSavePolicyFile();
             }
         }
 
@@ -2602,7 +2599,7 @@
                         true, UserHandle.getCallingUserId(), packages, uids);
             }
 
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
 
@@ -3390,7 +3387,7 @@
                 final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
                 try {
                     readPolicyXml(bais, true /*forRestore*/);
-                    savePolicyFile();
+                    handleSavePolicyFile();
                 } catch (NumberFormatException | XmlPullParserException | IOException e) {
                     Slog.w(TAG, "applyRestore: error reading payload", e);
                 }
@@ -3431,7 +3428,7 @@
                                     .setPackage(pkg)
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
                             UserHandle.of(userId), null);
-                    savePolicyFile();
+                    handleSavePolicyFile();
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3575,7 +3572,7 @@
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
                             UserHandle.of(userId), null);
 
-                    savePolicyFile();
+                    handleSavePolicyFile();
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3601,7 +3598,7 @@
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
                             UserHandle.of(userId), null);
 
-                    savePolicyFile();
+                    handleSavePolicyFile();
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3684,7 +3681,7 @@
             verifyPrivilegedListener(token, user, false);
             createNotificationChannelGroup(
                     pkg, getUidForPackageAndUser(pkg, user), group, false, true);
-            savePolicyFile();
+            handleSavePolicyFile();
         }
 
         @Override
@@ -3733,7 +3730,7 @@
             }
             if (allow != mLockScreenAllowSecureNotifications) {
                 mLockScreenAllowSecureNotifications = allow;
-                savePolicyFile();
+                handleSavePolicyFile();
             }
         }
 
@@ -4492,7 +4489,7 @@
                 Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
             }
             mSnoozeHelper.update(userId, r);
-            savePolicyFile();
+            handleSavePolicyFile();
             return false;
         }
 
@@ -4623,7 +4620,7 @@
                 mSnoozeHelper.snooze(r, mDuration);
             }
             r.recordSnoozed();
-            savePolicyFile();
+            handleSavePolicyFile();
         }
     }
 
@@ -4701,7 +4698,7 @@
                     if (mReason != REASON_SNOOZED) {
                         final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId);
                         if (wasSnoozed) {
-                            savePolicyFile();
+                            handleSavePolicyFile();
                         }
                     }
                 }
@@ -5750,9 +5747,6 @@
                 case MESSAGE_FINISH_TOKEN_TIMEOUT:
                     handleKillTokenTimeout((ToastRecord) msg.obj);
                     break;
-                case MESSAGE_SAVE_POLICY_FILE:
-                    handleSavePolicyFile();
-                    break;
                 case MESSAGE_SEND_RANKING_UPDATE:
                     handleSendRankingUpdate();
                     break;
@@ -6270,7 +6264,7 @@
             Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName));
         }
         mSnoozeHelper.repost(key);
-        savePolicyFile();
+        handleSavePolicyFile();
     }
 
     @GuardedBy("mNotificationLock")
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 65ccecd..7ae2271 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
@@ -77,18 +78,20 @@
             IApplicationThread caller,
             String callingPackage,
             ComponentName component,
-            UserHandle user) throws RemoteException {
+            @UserIdInt int userId,
+            boolean launchMainActivity) throws RemoteException {
         Preconditions.checkNotNull(callingPackage);
         Preconditions.checkNotNull(component);
-        Preconditions.checkNotNull(user);
 
         verifyCallingPackage(callingPackage);
 
+        final int callerUserId = mInjector.getCallingUserId();
+        final int callingUid = mInjector.getCallingUid();
+
         List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
-                callingPackage, mInjector.getCallingUserId());
-        if (!allowedTargetUsers.contains(user)) {
-            throw new SecurityException(
-                    callingPackage + " cannot access unrelated user " + user.getIdentifier());
+                callingPackage, callerUserId);
+        if (!allowedTargetUsers.contains(UserHandle.of(userId))) {
+            throw new SecurityException(callingPackage + " cannot access unrelated user " + userId);
         }
 
         // Verify that caller package is starting activity in its own package.
@@ -98,25 +101,43 @@
                             + component.getPackageName());
         }
 
-        final int callingUid = mInjector.getCallingUid();
-
-        // Verify that target activity does handle the intent with ACTION_MAIN and
-        // 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.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
-        // category are ignored if component name is present while we are resolving intent.
-        launchIntent.setPackage(component.getPackageName());
-        verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user);
+        // Verify that target activity does handle the intent correctly.
+        final Intent launchIntent = new Intent();
+        if (launchMainActivity) {
+            launchIntent.setAction(Intent.ACTION_MAIN);
+            launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            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 category are ignored if component name is present while we are resolving intent.
+            launchIntent.setPackage(component.getPackageName());
+        } else {
+            // If the main activity is not being launched and the users are different, the caller
+            // must have the required permission and the users must be in the same profile group
+            // in order to launch any of its own activities.
+            if (callerUserId != userId) {
+                final int permissionFlag = ActivityManager.checkComponentPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid,
+                        -1, true);
+                if (permissionFlag != PackageManager.PERMISSION_GRANTED
+                        || !mInjector.getUserManager().isSameProfileGroup(callerUserId, userId)) {
+                    throw new SecurityException("Attempt to launch activity without required "
+                            + android.Manifest.permission.INTERACT_ACROSS_PROFILES + " permission"
+                            + " or target user is not in the same profile group.");
+                }
+            }
+            launchIntent.setComponent(component);
+        }
+        verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, userId);
 
         launchIntent.setPackage(null);
         launchIntent.setComponent(component);
         mInjector.getActivityTaskManagerInternal().startActivityAsUser(
                 caller, callingPackage, launchIntent,
-                ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
-                user.getIdentifier());
+                launchMainActivity
+                        ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()
+                        : null,
+                userId);
     }
 
     private List<UserHandle> getTargetUserProfilesUnchecked(
@@ -163,7 +184,7 @@
      * activity is exported.
      */
     private void verifyActivityCanHandleIntentAndExported(
-            Intent launchIntent, ComponentName component, int callingUid, UserHandle user) {
+            Intent launchIntent, ComponentName component, int callingUid, @UserIdInt int userId) {
         final long ident = mInjector.clearCallingIdentity();
         try {
             final List<ResolveInfo> apps =
@@ -171,7 +192,7 @@
                             launchIntent,
                             MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
                             callingUid,
-                            user.getIdentifier());
+                            userId);
             final int size = apps.size();
             for (int i = 0; i < size; ++i) {
                 final ActivityInfo activityInfo = apps.get(i).activityInfo;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 888675c..edab94c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -86,11 +86,6 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.PackageParser.isApkFile;
-import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_BASE;
-import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_MANAGER;
-import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE;
-import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK;
-import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
@@ -242,6 +237,7 @@
 import android.os.storage.StorageManagerInternal;
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
+import android.provider.MediaStore;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.security.KeyStore;
@@ -1302,6 +1298,7 @@
     final @Nullable String mStorageManagerPackage;
     final @Nullable String mSystemTextClassifierPackage;
     final @Nullable String mWellbeingPackage;
+    final @Nullable String mDocumenterPackage;
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
@@ -2094,28 +2091,6 @@
         }
     }
 
-    @GuardedBy("mPackages")
-    private void setupBuiltinSharedLibraryDependenciesLocked() {
-        // Builtin libraries don't have versions.
-        long version = SharedLibraryInfo.VERSION_UNDEFINED;
-
-        SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr(ANDROID_HIDL_MANAGER, version);
-        if (libraryInfo != null) {
-            libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_HIDL_BASE, version));
-        }
-
-        libraryInfo = getSharedLibraryInfoLPr(ANDROID_TEST_RUNNER, version);
-        if (libraryInfo != null) {
-            libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_MOCK, version));
-            libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_BASE, version));
-        }
-
-        libraryInfo = getSharedLibraryInfoLPr(ANDROID_TEST_MOCK, version);
-        if (libraryInfo != null) {
-            libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_BASE, version));
-        }
-    }
-
     public PackageManagerService(Context context, Installer installer,
             boolean factoryTest, boolean onlyCore) {
         LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
@@ -2223,17 +2198,32 @@
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
             mInstantAppRegistry = new InstantAppRegistry(this);
 
-            ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
+            ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
+                    = systemConfig.getSharedLibraries();
             final int builtInLibCount = libConfig.size();
             for (int i = 0; i < builtInLibCount; i++) {
                 String name = libConfig.keyAt(i);
-                String path = libConfig.valueAt(i);
-                addSharedLibraryLPw(path, null, null, name, SharedLibraryInfo.VERSION_UNDEFINED,
-                        SharedLibraryInfo.TYPE_BUILTIN, PLATFORM_PACKAGE_NAME, 0);
+                SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
+                addSharedLibraryLPw(entry.filename, null, null, name,
+                        SharedLibraryInfo.VERSION_UNDEFINED, SharedLibraryInfo.TYPE_BUILTIN,
+                        PLATFORM_PACKAGE_NAME, 0);
             }
-            // Builtin libraries cannot encode their dependency where they are
-            // defined, so fix that now.
-            setupBuiltinSharedLibraryDependenciesLocked();
+
+            // Now that we have added all the libraries, iterate again to add dependency
+            // information IFF their dependencies are added.
+            long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED;
+            for (int i = 0; i < builtInLibCount; i++) {
+                String name = libConfig.keyAt(i);
+                SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
+                final int dependencyCount = entry.dependencies.length;
+                for (int j = 0; j < dependencyCount; j++) {
+                    final SharedLibraryInfo dependency =
+                        getSharedLibraryInfoLPr(entry.dependencies[j], undefinedVersion);
+                    if (dependency != null) {
+                        getSharedLibraryInfoLPr(name, undefinedVersion).addDependency(dependency);
+                    }
+                }
+            }
 
             SELinuxMMAC.readInstallPolicy();
 
@@ -2792,6 +2782,7 @@
             mSystemTextClassifierPackage = getSystemTextClassifierPackageName();
 
             mWellbeingPackage = getWellbeingPackageName();
+            mDocumenterPackage = getDocumenterPackageName();
 
             // Now that we know all of the shared libraries, update all clients to have
             // the correct library paths.
@@ -17936,7 +17927,7 @@
                 final int removedUserId = (user != null) ? user.getIdentifier()
                         : UserHandle.USER_ALL;
 
-                clearPackageStateForUserLIF(ps, removedUserId, outInfo);
+                clearPackageStateForUserLIF(ps, removedUserId, outInfo, flags);
                 markPackageUninstalledForUserLPw(ps, user);
                 scheduleWritePackageRestrictionsLocked(user);
                 return;
@@ -17966,7 +17957,7 @@
                     // we need to do is clear this user's data and save that
                     // it is uninstalled.
                     if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
-                    clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo);
+                    clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
                     scheduleWritePackageRestrictionsLocked(user);
                     return;
                 } else {
@@ -17982,7 +17973,7 @@
                 // we need to do is clear this user's data and save that
                 // it is uninstalled.
                 if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
-                clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo);
+                clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
                 scheduleWritePackageRestrictionsLocked(user);
                 return;
             }
@@ -18099,7 +18090,7 @@
     }
 
     private void clearPackageStateForUserLIF(PackageSetting ps, int userId,
-            PackageRemovedInfo outInfo) {
+            PackageRemovedInfo outInfo, int flags) {
         final PackageParser.Package pkg;
         synchronized (mPackages) {
             pkg = mPackages.get(ps.name);
@@ -18124,6 +18115,14 @@
                 }
                 resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, nextUserId);
             }
+            // Also delete contributed media, when requested
+            if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
+                try {
+                    MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
+                }
+            }
         }
 
         if (outInfo != null) {
@@ -19570,6 +19569,22 @@
         return mContext.getString(R.string.config_defaultTextClassifierPackage);
     }
 
+    private @Nullable String getDocumenterPackageName() {
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+        final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
+                        | MATCH_DISABLED_COMPONENTS,
+                UserHandle.myUserId());
+        if (matches.size() == 1) {
+            return matches.get(0).getComponentInfo().packageName;
+        } else {
+            Slog.e(TAG, "There should probably be exactly one documenter; found "
+                    + matches.size() + ": matches=" + matches);
+            return null;
+        }
+    }
+
     @Override
     public String getWellbeingPackageName() {
         return mContext.getString(R.string.config_defaultWellbeingPackage);
@@ -22733,6 +22748,8 @@
                     return mRequiredPermissionControllerPackage;
                 case PackageManagerInternal.PACKAGE_WELLBEING:
                     return mWellbeingPackage;
+                case PackageManagerInternal.PACKAGE_DOCUMENTER:
+                    return mDocumenterPackage;
             }
             return null;
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c18ca25..d1d5818 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -31,6 +31,7 @@
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyEventLogger;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -73,6 +74,7 @@
 import android.os.storage.StorageManager;
 import android.security.GateKeeper;
 import android.service.gatekeeper.IGateKeeperService;
+import android.stats.devicepolicy.DevicePolicyEnums;
 import android.util.AtomicFile;
 import android.util.IntArray;
 import android.util.Log;
@@ -100,6 +102,8 @@
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -121,8 +125,6 @@
 import java.util.List;
 import java.util.Objects;
 
-import libcore.io.IoUtils;
-
 /**
  * Service for {@link UserManager}.
  *
@@ -173,6 +175,8 @@
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
+    private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL =
+            "lastRequestQuietModeEnabledCall";
     private static final String ATTR_KEY = "key";
     private static final String ATTR_VALUE_TYPE = "type";
     private static final String ATTR_MULTIPLE = "m";
@@ -268,6 +272,16 @@
         /** Elapsed realtime since boot when the user was unlocked. */
         long unlockRealtime;
 
+        private long mLastRequestQuietModeEnabledMillis;
+
+        void setLastRequestQuietModeEnabledMillis(long millis) {
+            mLastRequestQuietModeEnabledMillis = millis;
+        }
+
+        long getLastRequestQuietModeEnabledMillis() {
+            return mLastRequestQuietModeEnabledMillis;
+        }
+
         void clearSeedAccountData() {
             seedAccountName = null;
             seedAccountType = null;
@@ -389,8 +403,8 @@
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT);
             final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
             // Call setQuietModeEnabled on bg thread to avoid ANR
-            BackgroundThread.getHandler()
-                    .post(() -> setQuietModeEnabled(userHandle, false, target));
+            BackgroundThread.getHandler().post(() ->
+                    setQuietModeEnabled(userHandle, false, target, /* callingPackage */ null));
         }
     };
 
@@ -834,21 +848,24 @@
         ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), target != null);
         final long identity = Binder.clearCallingIdentity();
         try {
+            boolean result = false;
             if (enableQuietMode) {
-                setQuietModeEnabled(userHandle, true /* enableQuietMode */, target);
-                return true;
+                setQuietModeEnabled(
+                        userHandle, true /* enableQuietMode */, target, callingPackage);
+                result = true;
             } else {
                 boolean needToShowConfirmCredential =
                         mLockPatternUtils.isSecure(userHandle)
                                 && !StorageManager.isUserKeyUnlocked(userHandle);
                 if (needToShowConfirmCredential) {
                     showConfirmCredentialToDisableQuietMode(userHandle, target);
-                    return false;
                 } else {
-                    setQuietModeEnabled(userHandle, false /* enableQuietMode */, target);
-                    return true;
+                    setQuietModeEnabled(
+                            userHandle, false /* enableQuietMode */, target, callingPackage);
+                    result = true;
                 }
             }
+            return result;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -894,8 +911,8 @@
                 + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission");
     }
 
-    private void setQuietModeEnabled(
-            int userHandle, boolean enableQuietMode, IntentSender target) {
+    private void setQuietModeEnabled(int userHandle, boolean enableQuietMode,
+            IntentSender target, @Nullable String callingPackage) {
         final UserInfo profile, parent;
         final UserData profileUserData;
         synchronized (mUsersLock) {
@@ -927,6 +944,7 @@
                 ActivityManager.getService().startUserInBackgroundWithListener(
                         userHandle, callback);
             }
+            logQuietModeEnabled(userHandle, enableQuietMode, callingPackage);
         } catch (RemoteException e) {
             // Should not happen, same process.
             e.rethrowAsRuntimeException();
@@ -935,6 +953,28 @@
                 enableQuietMode);
     }
 
+    private void logQuietModeEnabled(int userHandle, boolean enableQuietMode,
+            @Nullable String callingPackage) {
+        UserData userData;
+        synchronized (mUsersLock) {
+            userData = getUserDataLU(userHandle);
+        }
+        if (userData == null) {
+            return;
+        }
+        final long now = System.currentTimeMillis();
+        final long period = (userData.getLastRequestQuietModeEnabledMillis() != 0L
+                ? now - userData.getLastRequestQuietModeEnabledMillis()
+                : now - userData.info.creationTime);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.REQUEST_QUIET_MODE_ENABLED)
+                .setStrings(callingPackage)
+                .setBoolean(enableQuietMode)
+                .setTimePeriod(period)
+                .write();
+        userData.setLastRequestQuietModeEnabledMillis(now);
+    }
+
     @Override
     public boolean isQuietModeEnabled(int userHandle) {
         synchronized (mPackagesLock) {
@@ -2314,6 +2354,12 @@
             serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
         }
 
+        if (userData.getLastRequestQuietModeEnabledMillis() != 0L) {
+            serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
+            serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis()));
+            serializer.endTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
+        }
+
         serializer.endTag(null, TAG_USER);
 
         serializer.endDocument();
@@ -2408,6 +2454,7 @@
         String iconPath = null;
         long creationTime = 0L;
         long lastLoggedInTime = 0L;
+        long lastRequestQuietModeEnabledTimestamp = 0L;
         String lastLoggedInFingerprint = null;
         int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
         int profileBadge = 0;
@@ -2494,6 +2541,11 @@
                 } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
                     seedAccountOptions = PersistableBundle.restoreFromXml(parser);
                     persistSeedData = true;
+                } else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) {
+                    type = parser.next();
+                    if (type == XmlPullParser.TEXT) {
+                        lastRequestQuietModeEnabledTimestamp = Long.parseLong(parser.getText());
+                    }
                 }
             }
         }
@@ -2518,6 +2570,7 @@
         userData.seedAccountType = seedAccountType;
         userData.persistSeedData = persistSeedData;
         userData.seedAccountOptions = seedAccountOptions;
+        userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
 
         synchronized (mRestrictionsLock) {
             if (baseRestrictions != null) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 3a74ab5..36b7269 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,6 +16,11 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -26,9 +31,9 @@
 import android.os.Build;
 import android.os.FileUtils;
 import android.os.RemoteException;
-import android.os.storage.StorageManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.Slog;
@@ -48,18 +53,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
-
 /**
  * This class keeps track of how dex files are used.
  * Every time it gets a notification about a dex file being loaded it tracks
@@ -89,6 +90,12 @@
     // encode and save the dex usage data.
     private final PackageDexUsage mPackageDexUsage;
 
+    // PackageDynamicCodeLoading handles recording of dynamic code loading -
+    // which is similar to PackageDexUsage but records a different aspect of the data.
+    // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
+    // record class loaders or ISAs.)
+    private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+
     private final IPackageManager mPackageManager;
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final Object mInstallLock;
@@ -126,14 +133,15 @@
 
     public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
             Installer installer, Object installLock, Listener listener) {
-      mContext = context;
-      mPackageCodeLocationsCache = new HashMap<>();
-      mPackageDexUsage = new PackageDexUsage();
-      mPackageManager = pms;
-      mPackageDexOptimizer = pdo;
-      mInstaller = installer;
-      mInstallLock = installLock;
-      mListener = listener;
+        mContext = context;
+        mPackageCodeLocationsCache = new HashMap<>();
+        mPackageDexUsage = new PackageDexUsage();
+        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading();
+        mPackageManager = pms;
+        mPackageDexOptimizer = pdo;
+        mInstaller = installer;
+        mInstallLock = installLock;
+        mListener = listener;
     }
 
     public void systemReady() {
@@ -207,7 +215,6 @@
                 Slog.i(TAG, loadingAppInfo.packageName +
                         " uses unsupported class loader in " + classLoaderNames);
             }
-            return;
         }
 
         int dexPathIndex = 0;
@@ -236,15 +243,24 @@
                     continue;
                 }
 
-                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
-                // or UsedByOtherApps), record will return true and we trigger an async write
-                // to disk to make sure we don't loose the data in case of a reboot.
+                if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath,
+                        PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
+                        loadingAppInfo.packageName)) {
+                    mPackageDynamicCodeLoading.maybeWriteAsync();
+                }
 
-                String classLoaderContext = classLoaderContexts[dexPathIndex];
-                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
-                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
-                        loadingAppInfo.packageName, classLoaderContext)) {
-                    mPackageDexUsage.maybeWriteAsync();
+                if (classLoaderContexts != null) {
+
+                    // Record dex file usage. If the current usage is a new pattern (e.g. new
+                    // secondary, or UsedByOtherApps), record will return true and we trigger an
+                    // async write to disk to make sure we don't loose the data in case of a reboot.
+
+                    String classLoaderContext = classLoaderContexts[dexPathIndex];
+                    if (mPackageDexUsage.record(searchResult.mOwningPackageName,
+                            dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
+                            loadingAppInfo.packageName, classLoaderContext)) {
+                        mPackageDexUsage.maybeWriteAsync();
+                    }
                 }
             } else {
                 // If we can't find the owner of the dex we simply do not track it. The impact is
@@ -268,8 +284,8 @@
             loadInternal(existingPackages);
         } catch (Exception e) {
             mPackageDexUsage.clear();
-            Slog.w(TAG, "Exception while loading package dex usage. " +
-                    "Starting with a fresh state.", e);
+            mPackageDynamicCodeLoading.clear();
+            Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
         }
     }
 
@@ -311,15 +327,24 @@
      * all usage information for the package will be removed.
      */
     public void notifyPackageDataDestroyed(String packageName, int userId) {
-        boolean updated = userId == UserHandle.USER_ALL
-            ? mPackageDexUsage.removePackage(packageName)
-            : mPackageDexUsage.removeUserPackage(packageName, userId);
         // In case there was an update, write the package use info to disk async.
-        // Note that we do the writing here and not in PackageDexUsage in order to be
+        // Note that we do the writing here and not in the lower level classes in order to be
         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
         // multiple updates in PackageDexUsage before writing it).
-        if (updated) {
-            mPackageDexUsage.maybeWriteAsync();
+        if (userId == UserHandle.USER_ALL) {
+            if (mPackageDexUsage.removePackage(packageName)) {
+                mPackageDexUsage.maybeWriteAsync();
+            }
+            if (mPackageDynamicCodeLoading.removePackage(packageName)) {
+                mPackageDynamicCodeLoading.maybeWriteAsync();
+            }
+        } else {
+            if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
+                mPackageDexUsage.maybeWriteAsync();
+            }
+            if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
+                mPackageDynamicCodeLoading.maybeWriteAsync();
+            }
         }
     }
 
@@ -388,8 +413,23 @@
             }
         }
 
-        mPackageDexUsage.read();
-        mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
+        try {
+            mPackageDexUsage.read();
+            mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
+        } catch (Exception e) {
+            mPackageDexUsage.clear();
+            Slog.w(TAG, "Exception while loading package dex usage. "
+                    + "Starting with a fresh state.", e);
+        }
+
+        try {
+            mPackageDynamicCodeLoading.read();
+            mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+        } catch (Exception e) {
+            mPackageDynamicCodeLoading.clear();
+            Slog.w(TAG, "Exception while loading package dynamic code usage. "
+                    + "Starting with a fresh state.", e);
+        }
     }
 
     /**
@@ -415,10 +455,16 @@
      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
      * to access the package use.
      */
+    @VisibleForTesting
     /*package*/ boolean hasInfoOnPackage(String packageName) {
         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
     }
 
+    @VisibleForTesting
+    /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
+        return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
+    }
+
     /**
      * Perform dexopt on with the given {@code options} on the secondary dex files.
      * @return true if all secondary dex files were processed successfully (compiled or skipped
@@ -652,7 +698,7 @@
             // to load dex files through it.
             try {
                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
-                if (dexPathReal != dexPath) {
+                if (!dexPath.equals(dexPathReal)) {
                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
                             dexPath + " dexPathReal=" + dexPathReal);
                 }
@@ -675,6 +721,7 @@
      */
     public void writePackageDexUsageNow() {
         mPackageDexUsage.writeNow();
+        mPackageDynamicCodeLoading.writeNow();
     }
 
     private void registerSettingObserver() {
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
new file mode 100644
index 0000000..f74aa1d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.dex;
+
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+import com.android.server.pm.AbstractStatsBase;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Stats file which stores information about secondary code files that are dynamically loaded.
+ */
+class PackageDynamicCodeLoading extends AbstractStatsBase<Void> {
+    // Type code to indicate a secondary file containing DEX code. (The char value is how it
+    // is represented in the text file format.)
+    static final int FILE_TYPE_DEX = 'D';
+
+    private static final String TAG = "PackageDynamicCodeLoading";
+
+    private static final String FILE_VERSION_HEADER = "DCL1";
+    private static final String PACKAGE_PREFIX = "P:";
+
+    private static final char FIELD_SEPARATOR = ':';
+    private static final String PACKAGE_SEPARATOR = ",";
+
+    /**
+     * Regular expression to match the expected format of an input line describing one file.
+     * <p>Example: {@code D:10:package.name1,package.name2:/escaped/path}
+     * <p>The capturing groups are the file type, user ID, loading packages and escaped file path
+     * (in that order).
+     * <p>See {@link #write(OutputStream, Map)} below for more details of the format.
+     */
+    private static final Pattern PACKAGE_LINE_PATTERN =
+            Pattern.compile("([A-Z]):([0-9]+):([^:]*):(.*)");
+
+    private final Object mLock = new Object();
+
+    // Map from package name to data about loading of dynamic code files owned by that package.
+    // (Apps may load code files owned by other packages, subject to various access
+    // constraints.)
+    // Any PackageDynamicCode in this map will be non-empty.
+    @GuardedBy("mLock")
+    private Map<String, PackageDynamicCode> mPackageMap = new HashMap<>();
+
+    PackageDynamicCodeLoading() {
+        super("package-dcl.list", "PackageDynamicCodeLoading_DiskWriter", false);
+    }
+
+    /**
+     * Record dynamic code loading from a file.
+     *
+     * Note this is called when an app loads dex files and as such it should return
+     * as fast as possible.
+     *
+     * @param owningPackageName the package owning the file path
+     * @param filePath the path of the dex files being loaded
+     * @param fileType the type of code loading
+     * @param ownerUserId the user id which runs the code loading the file
+     * @param loadingPackageName the package performing the load
+     * @return whether new information has been recorded
+     * @throws IllegalArgumentException if clearly invalid information is detected
+     */
+    boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
+            String loadingPackageName) {
+        if (fileType != FILE_TYPE_DEX) {
+            throw new IllegalArgumentException("Bad file type: " + fileType);
+        }
+        synchronized (mLock) {
+            PackageDynamicCode packageInfo = mPackageMap.get(owningPackageName);
+            if (packageInfo == null) {
+                packageInfo = new PackageDynamicCode();
+                mPackageMap.put(owningPackageName, packageInfo);
+            }
+            return packageInfo.add(filePath, (char) fileType, ownerUserId, loadingPackageName);
+        }
+    }
+
+    /**
+     * Return all packages that contain records of secondary dex files. (Note that data updates
+     * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed
+     * one of these package names.)
+     */
+    Set<String> getAllPackagesWithDynamicCodeLoading() {
+        synchronized (mLock) {
+            return new HashSet<>(mPackageMap.keySet());
+        }
+    }
+
+    /**
+     * Return information about the dynamic code file usage of the specified package,
+     * or null if there is currently no usage information. The object returned is a copy of the
+     * live information that is not updated.
+     */
+    PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
+        synchronized (mLock) {
+            PackageDynamicCode info = mPackageMap.get(packageName);
+            return info == null ? null : new PackageDynamicCode(info);
+        }
+    }
+
+    /**
+     * Remove all information about all packages.
+     */
+    void clear() {
+        synchronized (mLock) {
+            mPackageMap.clear();
+        }
+    }
+
+    /**
+     * Remove the data associated with package {@code packageName}. Affects all users.
+     * @return true if the package usage was found and removed successfully
+     */
+    boolean removePackage(String packageName) {
+        synchronized (mLock) {
+            return mPackageMap.remove(packageName) != null;
+        }
+    }
+
+    /**
+     * Remove all the records about package {@code packageName} belonging to user {@code userId}.
+     * @return whether any data was actually removed
+     */
+    boolean removeUserPackage(String packageName, int userId) {
+        synchronized (mLock) {
+            PackageDynamicCode packageDynamicCode = mPackageMap.get(packageName);
+            if (packageDynamicCode == null) {
+                return false;
+            }
+            if (packageDynamicCode.removeUser(userId)) {
+                if (packageDynamicCode.mFileUsageMap.isEmpty()) {
+                    mPackageMap.remove(packageName);
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove the specified dynamic code file record belonging to the package {@code packageName}
+     * and user {@code userId}.
+     * @return whether data was actually removed
+     */
+    boolean removeFile(String packageName, String filePath, int userId) {
+        synchronized (mLock) {
+            PackageDynamicCode packageDynamicCode = mPackageMap.get(packageName);
+            if (packageDynamicCode == null) {
+                return false;
+            }
+            if (packageDynamicCode.removeFile(filePath, userId)) {
+                if (packageDynamicCode.mFileUsageMap.isEmpty()) {
+                    mPackageMap.remove(packageName);
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Syncs data with the set of installed packages. Data about packages that are no longer
+     * installed is removed.
+     * @param packageToUsersMap a map from all existing package names to the users who have the
+     *                          package installed
+     */
+    void syncData(Map<String, Set<Integer>> packageToUsersMap) {
+        synchronized (mLock) {
+            Iterator<Entry<String, PackageDynamicCode>> it = mPackageMap.entrySet().iterator();
+            while (it.hasNext()) {
+                Entry<String, PackageDynamicCode> entry = it.next();
+                Set<Integer> packageUsers = packageToUsersMap.get(entry.getKey());
+                if (packageUsers == null) {
+                    it.remove();
+                } else {
+                    PackageDynamicCode packageDynamicCode = entry.getValue();
+                    packageDynamicCode.syncData(packageToUsersMap, packageUsers);
+                    if (packageDynamicCode.mFileUsageMap.isEmpty()) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Request that data be written to persistent file at the next time allowed by write-limiting.
+     */
+    void maybeWriteAsync() {
+        super.maybeWriteAsync(null);
+    }
+
+    /**
+     * Writes data to persistent file immediately.
+     */
+    void writeNow() {
+        super.writeNow(null);
+    }
+
+    @Override
+    protected final void writeInternal(Void data) {
+        AtomicFile file = getFile();
+        FileOutputStream output = null;
+        try {
+            output = file.startWrite();
+            write(output);
+            file.finishWrite(output);
+        } catch (IOException e) {
+            file.failWrite(output);
+            Slog.e(TAG, "Failed to write dynamic usage for secondary code files.", e);
+        }
+    }
+
+    @VisibleForTesting
+    void write(OutputStream output) throws IOException {
+        // Make a deep copy to avoid holding the lock while writing to disk.
+        Map<String, PackageDynamicCode> copiedMap;
+        synchronized (mLock) {
+            copiedMap = new HashMap<>(mPackageMap.size());
+            for (Entry<String, PackageDynamicCode> entry : mPackageMap.entrySet()) {
+                PackageDynamicCode copiedValue = new PackageDynamicCode(entry.getValue());
+                copiedMap.put(entry.getKey(), copiedValue);
+            }
+        }
+
+        write(output, copiedMap);
+    }
+
+    /**
+     * Write the dynamic code loading data as a text file to {@code output}. The file format begins
+     * with a line indicating the file type and version - {@link #FILE_VERSION_HEADER}.
+     * <p>There is then one section for each owning package, introduced by a line beginning "P:".
+     * This is followed by a line for each file owned by the package this is dynamically loaded,
+     * containing the file type, user ID, loading package names and full path (with newlines and
+     * backslashes escaped - see {@link #escape}).
+     * <p>For example:
+     * <pre>{@code
+     * DCL1
+     * P:first.owning.package
+     * D:0:loading.package_1,loading.package_2:/path/to/file
+     * D:10:loading.package_1:/another/file
+     * P:second.owning.package
+     * D:0:loading.package:/third/file
+     * }</pre>
+     */
+    private static void write(OutputStream output, Map<String, PackageDynamicCode> packageMap)
+            throws IOException {
+        PrintWriter writer = new FastPrintWriter(output);
+
+        writer.println(FILE_VERSION_HEADER);
+        for (Entry<String, PackageDynamicCode> packageEntry : packageMap.entrySet()) {
+            writer.print(PACKAGE_PREFIX);
+            writer.println(packageEntry.getKey());
+
+            Map<String, DynamicCodeFile> mFileUsageMap = packageEntry.getValue().mFileUsageMap;
+            for (Entry<String, DynamicCodeFile> fileEntry : mFileUsageMap.entrySet()) {
+                String path = fileEntry.getKey();
+                DynamicCodeFile dynamicCodeFile = fileEntry.getValue();
+
+                writer.print(dynamicCodeFile.mFileType);
+                writer.print(FIELD_SEPARATOR);
+                writer.print(dynamicCodeFile.mUserId);
+                writer.print(FIELD_SEPARATOR);
+
+                String prefix = "";
+                for (String packageName : dynamicCodeFile.mLoadingPackages) {
+                    writer.print(prefix);
+                    writer.print(packageName);
+                    prefix = PACKAGE_SEPARATOR;
+                }
+
+                writer.print(FIELD_SEPARATOR);
+                writer.println(escape(path));
+            }
+        }
+
+        writer.flush();
+        if (writer.checkError()) {
+            throw new IOException("Writer failed");
+        }
+    }
+
+    /**
+     * Read data from the persistent file. Replaces existing data completely if successful.
+     */
+    void read() {
+        super.read(null);
+    }
+
+    @Override
+    protected final void readInternal(Void data) {
+        AtomicFile file = getFile();
+
+        FileInputStream stream = null;
+        try {
+            stream = file.openRead();
+            read(stream);
+        } catch (FileNotFoundException expected) {
+            // The file may not be there. E.g. When we first take the OTA with this feature.
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to parse dynamic usage for secondary code files.", e);
+        } finally {
+            IoUtils.closeQuietly(stream);
+        }
+    }
+
+    @VisibleForTesting
+    void read(InputStream stream) throws IOException {
+        Map<String, PackageDynamicCode> newPackageMap = new HashMap<>();
+        read(stream, newPackageMap);
+        synchronized (mLock) {
+            mPackageMap = newPackageMap;
+        }
+    }
+
+    private static void read(InputStream stream, Map<String, PackageDynamicCode> packageMap)
+            throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
+        String versionLine = reader.readLine();
+        if (!FILE_VERSION_HEADER.equals(versionLine)) {
+            throw new IOException("Incorrect version line: " + versionLine);
+        }
+
+        String line = reader.readLine();
+        if (line != null && !line.startsWith(PACKAGE_PREFIX)) {
+            throw new IOException("Malformed line: " + line);
+        }
+
+        while (line != null) {
+            String packageName = line.substring(PACKAGE_PREFIX.length());
+
+            PackageDynamicCode packageInfo = new PackageDynamicCode();
+            while (true) {
+                line = reader.readLine();
+                if (line == null || line.startsWith(PACKAGE_PREFIX)) {
+                    break;
+                }
+                readFileInfo(line, packageInfo);
+            }
+
+            if (!packageInfo.mFileUsageMap.isEmpty()) {
+                packageMap.put(packageName, packageInfo);
+            }
+        }
+    }
+
+    private static void readFileInfo(String line, PackageDynamicCode output) throws IOException {
+        try {
+            Matcher matcher = PACKAGE_LINE_PATTERN.matcher(line);
+            if (!matcher.matches()) {
+                throw new IOException("Malformed line: " + line);
+            }
+
+            char type = matcher.group(1).charAt(0);
+            int user = Integer.parseInt(matcher.group(2));
+            String[] packages = matcher.group(3).split(PACKAGE_SEPARATOR);
+            String path = unescape(matcher.group(4));
+
+            if (packages.length == 0) {
+                throw new IOException("Malformed line: " + line);
+            }
+            if (type != FILE_TYPE_DEX) {
+                throw new IOException("Unknown file type: " + line);
+            }
+
+            output.mFileUsageMap.put(path, new DynamicCodeFile(type, user, packages));
+        } catch (RuntimeException e) {
+            // Just in case we get NumberFormatException, or various
+            // impossible out of bounds errors happen.
+            throw new IOException("Unable to parse line: " + line, e);
+        }
+    }
+
+    /**
+     * Escape any newline and backslash characters in path. A newline in a path is legal if unusual,
+     * and it would break our line-based file parsing.
+     */
+    @VisibleForTesting
+    static String escape(String path) {
+        if (path.indexOf('\\') == -1 && path.indexOf('\n') == -1 && path.indexOf('\r') == -1) {
+            return path;
+        }
+
+        StringBuilder result = new StringBuilder(path.length() + 10);
+        for (int i = 0; i < path.length(); i++) {
+            // Surrogates will never match the characters we care about, so it's ok to use chars
+            // not code points here.
+            char c = path.charAt(i);
+            switch (c) {
+                case '\\':
+                    result.append("\\\\");
+                    break;
+                case '\n':
+                    result.append("\\n");
+                    break;
+                case '\r':
+                    result.append("\\r");
+                    break;
+                default:
+                    result.append(c);
+                    break;
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Reverse the effect of {@link #escape}.
+     * @throws IOException if the input string is malformed
+     */
+    @VisibleForTesting
+    static String unescape(String escaped) throws IOException {
+        // As we move through the input string, start is the position of the first character
+        // after the previous escape sequence and finish is the position of the following backslash.
+        int start = 0;
+        int finish = escaped.indexOf('\\');
+        if (finish == -1) {
+            return escaped;
+        }
+
+        StringBuilder result = new StringBuilder(escaped.length());
+        while (true) {
+            if (finish >= escaped.length() - 1) {
+                // Backslash mustn't be the last character
+                throw new IOException("Unexpected \\ in: " + escaped);
+            }
+            result.append(escaped, start, finish);
+            switch (escaped.charAt(finish + 1)) {
+                case '\\':
+                    result.append('\\');
+                    break;
+                case 'r':
+                    result.append('\r');
+                    break;
+                case 'n':
+                    result.append('\n');
+                    break;
+                default:
+                    throw new IOException("Bad escape in: " + escaped);
+            }
+
+            start = finish + 2;
+            finish = escaped.indexOf('\\', start);
+            if (finish == -1) {
+                result.append(escaped, start, escaped.length());
+                break;
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Represents the dynamic code usage of a single package.
+     */
+    static class PackageDynamicCode {
+        /**
+         * Map from secondary code file path to information about which packages dynamically load
+         * that file.
+         */
+        final Map<String, DynamicCodeFile> mFileUsageMap;
+
+        private PackageDynamicCode() {
+            mFileUsageMap = new HashMap<>();
+        }
+
+        private PackageDynamicCode(PackageDynamicCode original) {
+            mFileUsageMap = new HashMap<>(original.mFileUsageMap.size());
+            for (Entry<String, DynamicCodeFile> entry : original.mFileUsageMap.entrySet()) {
+                DynamicCodeFile newValue = new DynamicCodeFile(entry.getValue());
+                mFileUsageMap.put(entry.getKey(), newValue);
+            }
+        }
+
+        private boolean add(String path, char fileType, int userId, String loadingPackage) {
+            DynamicCodeFile fileInfo = mFileUsageMap.get(path);
+            if (fileInfo == null) {
+                fileInfo = new DynamicCodeFile(fileType, userId, loadingPackage);
+                mFileUsageMap.put(path, fileInfo);
+                return true;
+            } else {
+                if (fileInfo.mUserId != userId) {
+                    // This should be impossible: private app files are always user-specific and
+                    // can't be accessed from different users.
+                    throw new IllegalArgumentException("Cannot change userId for '" + path
+                            + "' from " + fileInfo.mUserId + " to " + userId);
+                }
+                // Changing file type (i.e. loading the same file in different ways is possible if
+                // unlikely. We allow it but ignore it.
+                return fileInfo.mLoadingPackages.add(loadingPackage);
+            }
+        }
+
+        private boolean removeUser(int userId) {
+            boolean updated = false;
+            Iterator<DynamicCodeFile> it = mFileUsageMap.values().iterator();
+            while (it.hasNext()) {
+                DynamicCodeFile fileInfo = it.next();
+                if (fileInfo.mUserId == userId) {
+                    it.remove();
+                    updated = true;
+                }
+            }
+            return updated;
+        }
+
+        private boolean removeFile(String filePath, int userId) {
+            DynamicCodeFile fileInfo = mFileUsageMap.get(filePath);
+            if (fileInfo == null || fileInfo.mUserId != userId) {
+                return false;
+            } else {
+                mFileUsageMap.remove(filePath);
+                return true;
+            }
+        }
+
+        private void syncData(Map<String, Set<Integer>> packageToUsersMap,
+                Set<Integer> owningPackageUsers) {
+            Iterator<DynamicCodeFile> fileIt = mFileUsageMap.values().iterator();
+            while (fileIt.hasNext()) {
+                DynamicCodeFile fileInfo = fileIt.next();
+                int fileUserId = fileInfo.mUserId;
+                if (!owningPackageUsers.contains(fileUserId)) {
+                    fileIt.remove();
+                } else {
+                    // Also remove information about any loading packages that are no longer
+                    // installed for this user.
+                    Iterator<String> loaderIt = fileInfo.mLoadingPackages.iterator();
+                    while (loaderIt.hasNext()) {
+                        String loader = loaderIt.next();
+                        Set<Integer> loadingPackageUsers = packageToUsersMap.get(loader);
+                        if (loadingPackageUsers == null
+                                || !loadingPackageUsers.contains(fileUserId)) {
+                            loaderIt.remove();
+                        }
+                    }
+                    if (fileInfo.mLoadingPackages.isEmpty()) {
+                        fileIt.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Represents a single dynamic code file loaded by one or more packages. Note that it is
+     * possible for one app to dynamically load code from a different app's home dir, if the
+     * owning app:
+     * <ul>
+     *     <li>Targets API 27 or lower and has shared its home dir.
+     *     <li>Is a system app.
+     *     <li>Has a shared UID with the loading app.
+     * </ul>
+     */
+    static class DynamicCodeFile {
+        final char mFileType;
+        final int mUserId;
+        final Set<String> mLoadingPackages;
+
+        private DynamicCodeFile(char type, int user, String... packages) {
+            mFileType = type;
+            mUserId = user;
+            mLoadingPackages = new HashSet<>(Arrays.asList(packages));
+        }
+
+        private DynamicCodeFile(DynamicCodeFile original) {
+            mFileType = original.mFileType;
+            mUserId = original.mUserId;
+            mLoadingPackages = new HashSet<>(original.mLoadingPackages);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 996f42b..3a49412 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -191,7 +191,8 @@
     }
 
     public boolean isRemoved() {
-        return perm.info != null && (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0;
+        return perm != null && perm.info != null
+                && (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0;
     }
 
     public boolean isSignature() {
@@ -243,6 +244,9 @@
     public boolean isWellbeing() {
         return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0;
     }
+    public boolean isDocumenter() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0;
+    }
 
     public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
         if (!origPackageName.equals(sourcePackageName)) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index bc3c18d..31f5ce4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1647,6 +1647,13 @@
                 // Special permission granted only to the OEM specified wellbeing app
                 allowed = true;
             }
+            if (!allowed && bp.isDocumenter()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the documenter and
+                // this app is the documenter, then it gets the permission.
+                allowed = true;
+            }
         }
         return allowed;
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 29d6237..565bb706 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3221,6 +3221,20 @@
         mNativeWrapper.nativeSendPowerHint(hintId, data);
     }
 
+    @VisibleForTesting
+    boolean wasDeviceIdleForInternal(long ms) {
+        synchronized (mLock) {
+            return mLastUserActivityTime + ms < SystemClock.uptimeMillis();
+        }
+    }
+
+    @VisibleForTesting
+    void onUserActivity() {
+        synchronized (mLock) {
+            mLastUserActivityTime = SystemClock.uptimeMillis();
+        }
+    }
+
     /**
      * Low-level function turn the device off immediately, without trying
      * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
@@ -4874,5 +4888,10 @@
         public void powerHint(int hintId, int data) {
             powerHintInternal(hintId, data);
         }
+
+        @Override
+        public boolean wasDeviceIdleFor(long ms) {
+            return wasDeviceIdleForInternal(ms);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 07bebad..02689a9 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -250,14 +250,29 @@
         }
     }
 
+    private void shutdownIfNeededLocked(Temperature temperature) {
+        if (temperature.getStatus() != Temperature.THROTTLING_SHUTDOWN) {
+            return;
+        }
+        switch (temperature.getType()) {
+            case Temperature.TYPE_CPU:
+                // Fall through
+            case Temperature.TYPE_GPU:
+                // Fall through
+            case Temperature.TYPE_NPU:
+                // Fall through
+            case Temperature.TYPE_SKIN:
+                mPowerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
+                break;
+            case Temperature.TYPE_BATTERY:
+                mPowerManager.shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
+                break;
+        }
+    }
+
     private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
         synchronized (mLock) {
-            // Thermal Shutdown for Skin temperature
-            if (temperature.getStatus() == Temperature.THROTTLING_SHUTDOWN
-                    && temperature.getType() == Temperature.TYPE_SKIN) {
-                mPowerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
-            }
-
+            shutdownIfNeededLocked(temperature);
             Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
             if (old != null) {
                 if (old.getStatus() != temperature.getStatus()) {
@@ -300,6 +315,8 @@
     final IThermalService.Stub mService = new IThermalService.Stub() {
         @Override
         public boolean registerThermalEventListener(IThermalEventListener listener) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
             synchronized (mLock) {
                 final long token = Binder.clearCallingIdentity();
                 try {
@@ -320,6 +337,8 @@
         @Override
         public boolean registerThermalEventListenerWithType(IThermalEventListener listener,
                 int type) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
             synchronized (mLock) {
                 final long token = Binder.clearCallingIdentity();
                 try {
@@ -339,6 +358,8 @@
 
         @Override
         public boolean unregisterThermalEventListener(IThermalEventListener listener) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
             synchronized (mLock) {
                 final long token = Binder.clearCallingIdentity();
                 try {
@@ -351,6 +372,8 @@
 
         @Override
         public List<Temperature> getCurrentTemperatures() {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
             final long token = Binder.clearCallingIdentity();
             try {
                 if (!mHalReady) {
@@ -364,6 +387,8 @@
 
         @Override
         public List<Temperature> getCurrentTemperaturesWithType(int type) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
             final long token = Binder.clearCallingIdentity();
             try {
                 if (!mHalReady) {
@@ -428,14 +453,15 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
+                    pw.println("IsStatusOverride: " + mIsStatusOverride);
                     pw.println("ThermalEventListeners:");
                     mThermalEventListeners.dump(pw, "\t");
                     pw.println("ThermalStatusListeners:");
                     mThermalStatusListeners.dump(pw, "\t");
-                    pw.println("Thermal Status: " + Integer.toString(mStatus));
+                    pw.println("Thermal Status: " + mStatus);
                     pw.println("Cached temperatures:");
                     dumpTemperaturesLocked(pw, "\t", mTemperatureMap.values());
-                    pw.println("HAL Ready: " + Boolean.toString(mHalReady));
+                    pw.println("HAL Ready: " + mHalReady);
                     if (mHalReady) {
                         pw.println("HAL connection:");
                         mHalWrapper.dump(pw, "\t");
@@ -507,7 +533,7 @@
                     return -1;
                 }
                 if (!Temperature.isValidStatus(status)) {
-                    pw.println("Invalid status: " + Integer.toString(status));
+                    pw.println("Invalid status: " + status);
                     return -1;
                 }
                 synchronized (mLock) {
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 8711ddf..35013de 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -22,8 +22,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.role.IOnRoleHoldersChangedListener;
 import android.app.role.IRoleManager;
 import android.app.role.IRoleManagerCallback;
 import android.app.role.RoleManager;
@@ -33,6 +35,9 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
@@ -53,6 +58,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -73,7 +80,7 @@
  *
  * @see RoleManager
  */
-public class RoleManagerService extends SystemService {
+public class RoleManagerService extends SystemService implements RoleUserState.Callback {
 
     private static final String LOG_TAG = RoleManagerService.class.getSimpleName();
 
@@ -100,6 +107,17 @@
     private final SparseArray<RemoteRoleControllerService> mControllerServices =
             new SparseArray<>();
 
+    /**
+     * Maps user id to its list of listeners.
+     */
+    @GuardedBy("mLock")
+    @NonNull
+    private final SparseArray<RemoteCallbackList<IOnRoleHoldersChangedListener>> mListeners =
+            new SparseArray<>();
+
+    @NonNull
+    private final Handler mListenerHandler = FgThread.getHandler();
+
     public RoleManagerService(@NonNull Context context) {
         super(context);
 
@@ -188,7 +206,7 @@
     }
 
     @Nullable
-    private String computeComponentStateHash(@UserIdInt int userId) {
+    private static String computeComponentStateHash(@UserIdInt int userId) {
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
         ByteArrayOutputStream out = new ByteArrayOutputStream();
 
@@ -223,7 +241,7 @@
         synchronized (mLock) {
             RoleUserState userState = mUserStates.get(userId);
             if (userState == null) {
-                userState = new RoleUserState(userId);
+                userState = new RoleUserState(userId, this);
                 mUserStates.put(userId, userState);
             }
             return userState;
@@ -242,17 +260,70 @@
         }
     }
 
+    @Nullable
+    private RemoteCallbackList<IOnRoleHoldersChangedListener> getListeners(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mListeners.get(userId);
+        }
+    }
+
+    @NonNull
+    private RemoteCallbackList<IOnRoleHoldersChangedListener> getOrCreateListeners(
+            @UserIdInt int userId) {
+        synchronized (mLock) {
+            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = mListeners.get(userId);
+            if (listeners == null) {
+                listeners = new RemoteCallbackList<>();
+                mListeners.put(userId, listeners);
+            }
+            return listeners;
+        }
+    }
+
     private void onRemoveUser(@UserIdInt int userId) {
+        RemoteCallbackList<IOnRoleHoldersChangedListener> listeners;
         RoleUserState userState;
         synchronized (mLock) {
+            listeners = mListeners.removeReturnOld(userId);
             mControllerServices.remove(userId);
             userState = mUserStates.removeReturnOld(userId);
         }
+        if (listeners != null) {
+            listeners.kill();
+        }
         if (userState != null) {
             userState.destroy();
         }
     }
 
+    @Override
+    public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+        mListenerHandler.sendMessage(PooledLambda.obtainMessage(
+                RoleManagerService::notifyRoleHoldersChanged, this, roleName, userId));
+    }
+
+    @WorkerThread
+    private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+        RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
+        if (listeners == null) {
+            return;
+        }
+
+        int broadcastCount = listeners.beginBroadcast();
+        try {
+            for (int i = 0; i < broadcastCount; i++) {
+                IOnRoleHoldersChangedListener listener = listeners.getBroadcastItem(i);
+                try {
+                    listener.onRoleHoldersChanged(roleName, userId);
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Error calling OnRoleHoldersChangedListener", e);
+                }
+            }
+        } finally {
+            listeners.finishBroadcast();
+        }
+    }
+
     private class Stub extends IRoleManager.Stub {
 
         @Override
@@ -357,6 +428,42 @@
         }
 
         @Override
+        public void addOnRoleHoldersChangedListenerAsUser(
+                @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
+            Preconditions.checkNotNull(listener, "listener cannot be null");
+            if (!mUserManagerInternal.exists(userId)) {
+                Slog.e(LOG_TAG, "user " + userId + " does not exist");
+                return;
+            }
+            userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser");
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
+                    "addOnRoleHoldersChangedListenerAsUser");
+
+            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getOrCreateListeners(
+                    userId);
+            listeners.register(listener);
+        }
+
+        @Override
+        public void removeOnRoleHoldersChangedListenerAsUser(
+                @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
+            Preconditions.checkNotNull(listener, "listener cannot be null");
+            if (!mUserManagerInternal.exists(userId)) {
+                Slog.e(LOG_TAG, "user " + userId + " does not exist");
+                return;
+            }
+            userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser");
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
+                    "removeOnRoleHoldersChangedListenerAsUser");
+
+            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
+            if (listener == null) {
+                return;
+            }
+            listeners.unregister(listener);
+        }
+
+        @Override
         public void setRoleNamesFromController(@NonNull List<String> roleNames) {
             Preconditions.checkNotNull(roleNames, "roleNames cannot be null");
             getContext().enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index ec614a4..d55e261 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -74,6 +74,9 @@
     private final int mUserId;
 
     @NonNull
+    private final Callback mCallback;
+
+    @NonNull
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
@@ -100,12 +103,14 @@
     private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
 
     /**
-     * Create a new instance of user state, and read its state from disk if previously persisted.
+     * Create a new user state, and read its state from disk if previously persisted.
      *
-     * @param userId the user id for the new user state
+     * @param userId the user id for this user state
+     * @param callback the callback for this user state
      */
-    public RoleUserState(@UserIdInt int userId) {
+    public RoleUserState(@UserIdInt int userId, @NonNull Callback callback) {
         mUserId = userId;
+        mCallback = callback;
 
         readFile();
     }
@@ -116,6 +121,7 @@
     public int getVersion() {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             return mVersion;
         }
     }
@@ -128,6 +134,7 @@
     public void setVersion(int version) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             if (mVersion == version) {
                 return;
             }
@@ -156,6 +163,7 @@
     public void setPackagesHash(@Nullable String packagesHash) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             if (Objects.equals(mPackagesHash, packagesHash)) {
                 return;
             }
@@ -174,6 +182,7 @@
     public boolean isRoleAvailable(@NonNull String roleName) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             return mRoles.containsKey(roleName);
         }
     }
@@ -189,6 +198,7 @@
     public ArraySet<String> getRoleHolders(@NonNull String roleName) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             return new ArraySet<>(mRoles.get(roleName));
         }
     }
@@ -201,29 +211,34 @@
     public void setRoleNames(@NonNull List<String> roleNames) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             boolean changed = false;
+
             for (int i = mRoles.size() - 1; i >= 0; i--) {
                 String roleName = mRoles.keyAt(i);
+
                 if (!roleNames.contains(roleName)) {
                     ArraySet<String> packageNames = mRoles.valueAt(i);
                     if (!packageNames.isEmpty()) {
-                        Slog.e(LOG_TAG,
-                                "Holders of a removed role should have been cleaned up, role: "
-                                        + roleName + ", holders: " + packageNames);
+                        Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
+                                + " role: " + roleName + ", holders: " + packageNames);
                     }
                     mRoles.removeAt(i);
                     changed = true;
                 }
             }
+
             int roleNamesSize = roleNames.size();
             for (int i = 0; i < roleNamesSize; i++) {
                 String roleName = roleNames.get(i);
+
                 if (!mRoles.containsKey(roleName)) {
                     mRoles.put(roleName, new ArraySet<>());
                     Slog.i(LOG_TAG, "Added new role: " + roleName);
                     changed = true;
                 }
             }
+
             if (changed) {
                 scheduleWriteFileLocked();
             }
@@ -241,20 +256,27 @@
      */
     @CheckResult
     public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
+        boolean changed;
+
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             ArraySet<String> roleHolders = mRoles.get(roleName);
             if (roleHolders == null) {
                 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
                         + ", package: " + packageName);
                 return false;
             }
-            boolean changed = roleHolders.add(packageName);
+            changed = roleHolders.add(packageName);
             if (changed) {
                 scheduleWriteFileLocked();
             }
-            return true;
         }
+
+        if (changed) {
+            mCallback.onRoleHoldersChanged(roleName, mUserId);
+        }
+        return true;
     }
 
     /**
@@ -268,20 +290,28 @@
      */
     @CheckResult
     public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
+        boolean changed;
+
         synchronized (mLock) {
             throwIfDestroyedLocked();
+
             ArraySet<String> roleHolders = mRoles.get(roleName);
             if (roleHolders == null) {
                 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
                         + ", package: " + packageName);
                 return false;
             }
-            boolean changed = roleHolders.remove(packageName);
+
+            changed = roleHolders.remove(packageName);
             if (changed) {
                 scheduleWriteFileLocked();
             }
-            return true;
         }
+
+        if (changed) {
+            mCallback.onRoleHoldersChanged(roleName, mUserId);
+        }
+        return true;
     }
 
     /**
@@ -520,8 +550,8 @@
     }
 
     /**
-     * Destroy this state and delete the corresponding file. Any pending writes to the file will be
-     * cancelled and any future interaction with this state will throw an exception.
+     * Destroy this user state and delete the corresponding file. Any pending writes to the file
+     * will be cancelled, and any future interaction with this state will throw an exception.
      */
     public void destroy() {
         synchronized (mLock) {
@@ -542,4 +572,18 @@
     private static @NonNull File getFile(@UserIdInt int userId) {
         return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
     }
+
+    /**
+     * Callback for a user state.
+     */
+    public interface Callback {
+
+        /**
+         * Called when the holders of roles are changed.
+         *
+         * @param roleName the name of the role whose holders are changed
+         * @param userId the user id for this role holder change
+         */
+        void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
+    }
 }
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java
index a3f452c..e6bb800 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfig.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java
@@ -48,7 +48,7 @@
     private static final String CONFIG_KEY_VALUE = "value";
 
     /**
-     * Represents config values targetting to an SDK range.
+     * Represents config values targeting an SDK range.
      */
     public static class PerSdkConfig {
         public final int minSdk;
@@ -90,11 +90,24 @@
     /**
      * Parse configuration from an APK.
      *
-     * @param config config as read from the APK metadata.
+     * @param config Config string as read from the APK metadata.
+     * @param allowedKeys Set of allowed keys in the config. Any key/value mapping for a key not in
+     *                    this set will result in an {@link InvalidConfigException} being thrown.
+     * @param keyValueMappers Mappings for values per key. The keys in the top level map should be
+     *                        a subset of {@code allowedKeys}. The keys in the inner map indicate
+     *                        the set of allowed values for that keys value. This map will be
+     *                        applied to the value in the configuration. This is intended to allow
+     *                        enum-like values to be encoded as strings in the configuration, and
+     *                        mapped back to integers when the configuration is parsed.
+     *
+     *                        <p>Any config key with a value that does not appear in the
+     *                        corresponding map will result in an {@link InvalidConfigException}
+     *                        being thrown.
      * @return Parsed configuration.
      * @throws InvalidConfigException If there's a problem parsing the config.
      */
-    public static SignedConfig parse(String config, Set<String> allowedKeys)
+    public static SignedConfig parse(String config, Set<String> allowedKeys,
+            Map<String, Map<String, String>> keyValueMappers)
             throws InvalidConfigException {
         try {
             JSONObject json = new JSONObject(config);
@@ -103,7 +116,8 @@
             JSONArray perSdkConfig = json.getJSONArray(KEY_CONFIG);
             List<PerSdkConfig> parsedConfigs = new ArrayList<>();
             for (int i = 0; i < perSdkConfig.length(); ++i) {
-                parsedConfigs.add(parsePerSdkConfig(perSdkConfig.getJSONObject(i), allowedKeys));
+                parsedConfigs.add(parsePerSdkConfig(perSdkConfig.getJSONObject(i), allowedKeys,
+                        keyValueMappers));
             }
 
             return new SignedConfig(version, parsedConfigs);
@@ -113,8 +127,17 @@
 
     }
 
+    private static CharSequence quoted(Object s) {
+        if (s == null) {
+            return "null";
+        } else {
+            return "\"" + s + "\"";
+        }
+    }
+
     @VisibleForTesting
-    static PerSdkConfig parsePerSdkConfig(JSONObject json, Set<String> allowedKeys)
+    static PerSdkConfig parsePerSdkConfig(JSONObject json, Set<String> allowedKeys,
+            Map<String, Map<String, String>> keyValueMappers)
             throws JSONException, InvalidConfigException {
         int minSdk = json.getInt(CONFIG_KEY_MIN_SDK);
         int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK);
@@ -123,12 +146,23 @@
         for (int i = 0; i < valueArray.length(); ++i) {
             JSONObject keyValuePair = valueArray.getJSONObject(i);
             String key = keyValuePair.getString(CONFIG_KEY_KEY);
-            String value = keyValuePair.has(CONFIG_KEY_VALUE)
-                    ? keyValuePair.getString(CONFIG_KEY_VALUE)
+            Object valueObject = keyValuePair.has(CONFIG_KEY_VALUE)
+                    ? keyValuePair.get(CONFIG_KEY_VALUE)
                     : null;
+            String value = valueObject == JSONObject.NULL || valueObject == null
+                            ? null
+                            : valueObject.toString();
             if (!allowedKeys.contains(key)) {
                 throw new InvalidConfigException("Config key " + key + " is not allowed");
             }
+            if (keyValueMappers.containsKey(key)) {
+                Map<String, String> mapper = keyValueMappers.get(key);
+                if (!mapper.containsKey(value)) {
+                    throw new InvalidConfigException(
+                            "Config key " + key + " contains unsupported value " + quoted(value));
+                }
+                value = mapper.get(value);
+            }
             values.put(key, value);
         }
         return new PerSdkConfig(minSdk, maxSdk, values);
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
index 7ce071f..e4d799a 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
@@ -17,12 +17,112 @@
 package com.android.server.signedconfig;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
 
 class SignedConfigApplicator {
 
-    static void applyConfig(Context context, String config, String signature) {
-        //TODO verify signature
-        //TODO parse & apply config
+    private static final String TAG = "SignedConfig";
+
+    private static final Set<String> ALLOWED_KEYS = Collections.unmodifiableSet(new ArraySet<>(
+            Arrays.asList(
+                    Settings.Global.HIDDEN_API_POLICY,
+                    Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS
+            )));
+
+    private static final Map<String, String> HIDDEN_API_POLICY_KEY_MAP = makeMap(
+            "DEFAULT", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT),
+            "DISABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED),
+            "JUST_WARN", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_JUST_WARN),
+            "ENABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED)
+    );
+
+    private static final Map<String, Map<String, String>> KEY_VALUE_MAPPERS = makeMap(
+            Settings.Global.HIDDEN_API_POLICY, HIDDEN_API_POLICY_KEY_MAP
+    );
+
+    private static <K, V> Map<K, V> makeMap(Object... keyValuePairs) {
+        if (keyValuePairs.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        final int len = keyValuePairs.length / 2;
+        ArrayMap<K, V> m = new ArrayMap<>(len);
+        for (int i = 0; i < len;  ++i) {
+            m.put((K) keyValuePairs[i * 2], (V) keyValuePairs[(i * 2) + 1]);
+        }
+        return Collections.unmodifiableMap(m);
+
     }
 
+    private final Context mContext;
+    private final String mSourcePackage;
+
+    SignedConfigApplicator(Context context, String sourcePackage) {
+        mContext = context;
+        mSourcePackage = sourcePackage;
+    }
+
+    private boolean checkSignature(String data, String signature) {
+        Slog.w(TAG, "SIGNATURE CHECK NOT IMPLEMENTED YET!");
+        return false;
+    }
+
+    private int getCurrentConfigVersion() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SIGNED_CONFIG_VERSION, 0);
+    }
+
+    private void updateCurrentConfig(int version, Map<String, String> values) {
+        for (Map.Entry<String, String> e: values.entrySet()) {
+            Settings.Global.putString(
+                    mContext.getContentResolver(),
+                    e.getKey(),
+                    e.getValue());
+        }
+        Settings.Global.putInt(
+                mContext.getContentResolver(), Settings.Global.SIGNED_CONFIG_VERSION, version);
+    }
+
+
+    void applyConfig(String configStr, String signature) {
+        if (!checkSignature(configStr, signature)) {
+            Slog.e(TAG, "Signature check on signed configuration in package " + mSourcePackage
+                    + " failed; ignoring");
+            return;
+        }
+        SignedConfig config;
+        try {
+            config = SignedConfig.parse(configStr, ALLOWED_KEYS, KEY_VALUE_MAPPERS);
+        } catch (InvalidConfigException e) {
+            Slog.e(TAG, "Failed to parse config from package " + mSourcePackage, e);
+            return;
+        }
+        int currentVersion = getCurrentConfigVersion();
+        if (currentVersion >= config.version) {
+            Slog.i(TAG, "Config from package " + mSourcePackage + " is older than existing: "
+                    + config.version + "<=" + currentVersion);
+            return;
+        }
+        // We have new config!
+        Slog.i(TAG, "Got new signed config from package " + mSourcePackage + ": version "
+                + config.version + " replacing existing version " + currentVersion);
+        SignedConfig.PerSdkConfig matchedConfig =
+                config.getMatchingConfig(Build.VERSION.SDK_INT);
+        if (matchedConfig == null) {
+            Slog.i(TAG, "Config is not applicable to current SDK version; ignoring");
+            return;
+        }
+
+        Slog.i(TAG, "Updating signed config to version " + config.version);
+        updateCurrentConfig(config.version, matchedConfig.values);
+    }
 }
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigService.java b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
index 1485686..be1d41d 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
@@ -85,7 +85,8 @@
                 Slog.d(TAG, "Got signed config: " + config);
                 Slog.d(TAG, "Got config signature: " + signature);
             }
-            SignedConfigApplicator.applyConfig(mContext, config, signature);
+            new SignedConfigApplicator(mContext, packageName).applyConfig(
+                    config, signature);
         } else {
             if (DBG) Slog.d(TAG, "Package has no config/signature.");
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 7683172..aca9702 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1797,7 +1797,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || getDisplay().hasPinnedStack()) {
+                || (getDisplay() != null && getDisplay().hasPinnedStack())) {
             mService.getTaskChangeNotificationController().notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index a335fa2..5b20af3 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -219,7 +219,7 @@
     }
 
     private boolean updateStateLw(final int state) {
-        if (state != mState) {
+        if (mWin != null && state != mState) {
             mState = state;
             if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
             mHandler.post(new Runnable() {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 639ed02..f9c9d33 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -1,5 +1,6 @@
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
@@ -9,7 +10,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
-import android.view.InputApplicationHandle;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
@@ -204,6 +204,37 @@
                 + WindowManagerService.TYPE_LAYER_OFFSET;
     }
 
+    /** Callback to get pointer display id. */
+    @Override
+    public int getPointerDisplayId() {
+        synchronized (mService.mGlobalLock) {
+            // If desktop mode is not enabled, show on the default display.
+            if (!mService.mForceDesktopModeOnExternalDisplays) {
+                return DEFAULT_DISPLAY;
+            }
+
+            // Look for the topmost freeform display.
+            int firstExternalDisplayId = DEFAULT_DISPLAY;
+            for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
+                final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+                // Heuristic solution here. Currently when "Freeform windows" developer option is
+                // enabled we automatically put secondary displays in freeform mode and emulating
+                // "desktop mode". It also makes sense to show the pointer on the same display.
+                if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    return displayContent.getDisplayId();
+                }
+
+                if (firstExternalDisplayId == DEFAULT_DISPLAY
+                        && displayContent.getDisplayId() != DEFAULT_DISPLAY) {
+                    firstExternalDisplayId = displayContent.getDisplayId();
+                }
+            }
+
+            // Look for the topmost non-default display
+            return firstExternalDisplayId;
+        }
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 43d2dcf..bf83ca9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
     jmethodID getLongPressTimeout;
     jmethodID getPointerLayer;
     jmethodID getPointerIcon;
+    jmethodID getPointerDisplayId;
     jmethodID getKeyboardLayoutOverlay;
     jmethodID getDeviceAlias;
     jmethodID getTouchCalibrationForInputDevice;
@@ -174,15 +175,6 @@
     loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
 }
 
-static void updatePointerControllerFromViewport(
-        sp<PointerController> controller, const DisplayViewport* const viewport) {
-    if (controller != nullptr && viewport != nullptr) {
-        const int32_t width = viewport->logicalRight - viewport->logicalLeft;
-        const int32_t height = viewport->logicalBottom - viewport->logicalTop;
-        controller->setDisplayViewport(width, height, viewport->orientation);
-    }
-}
-
 enum {
     WM_ACTION_PASS_TO_USER = 1,
 };
@@ -242,6 +234,7 @@
             jfloatArray matrixArr);
     virtual TouchAffineTransformation getTouchAffineTransformation(
             const std::string& inputDeviceDescriptor, int32_t surfaceRotation);
+    virtual void updatePointerDisplay();
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -314,10 +307,11 @@
 
     std::atomic<bool> mInteractive;
 
-    void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
+    void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
-
+    const DisplayViewport* findDisplayViewportLocked(int32_t displayId);
+    int32_t getPointerDisplayId();
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
     static inline JNIEnv* jniEnv() {
@@ -391,9 +385,10 @@
     return false;
 }
 
-static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) {
-    for (const DisplayViewport& v : viewports) {
-        if (v.type == ViewportType::VIEWPORT_INTERNAL) {
+const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId)
+        REQUIRES(mLock) {
+    for (const DisplayViewport& v : mLocked.viewports) {
+        if (v.displayId == displayId) {
             return &v;
         }
     }
@@ -420,20 +415,10 @@
         }
     }
 
-    const DisplayViewport* newInternalViewport = findInternalViewport(viewports);
-    {
+    { // acquire lock
         AutoMutex _l(mLock);
-        const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports);
-        // Internal viewport has changed if there wasn't one earlier, and there is one now, or,
-        // if they are different.
-        const bool internalViewportChanged = (newInternalViewport != nullptr) &&
-                (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport));
-        if (internalViewportChanged) {
-            sp<PointerController> controller = mLocked.pointerController.promote();
-            updatePointerControllerFromViewport(controller, newInternalViewport);
-        }
         mLocked.viewports = viewports;
-    }
+    } // release lock
 
     mInputManager->getReader()->requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -556,15 +541,43 @@
 
         controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
-
-        const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports);
-        updatePointerControllerFromViewport(controller, internalViewport);
-
-        updateInactivityTimeoutLocked(controller);
+        updateInactivityTimeoutLocked();
     }
+
     return controller;
 }
 
+int32_t NativeInputManager::getPointerDisplayId() {
+    JNIEnv* env = jniEnv();
+    jint pointerDisplayId = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getPointerDisplayId);
+    if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
+        pointerDisplayId = ADISPLAY_ID_DEFAULT;
+    }
+
+    return pointerDisplayId;
+}
+
+void NativeInputManager::updatePointerDisplay() {
+    ATRACE_CALL();
+
+    jint pointerDisplayId = getPointerDisplayId();
+
+    AutoMutex _l(mLock);
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller != nullptr) {
+        const DisplayViewport* viewport = findDisplayViewportLocked(pointerDisplayId);
+        if (viewport == nullptr) {
+            ALOGW("Can't find pointer display viewport, fallback to default display.");
+            viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT);
+        }
+
+        if (viewport != nullptr) {
+            controller->setDisplayViewport(*viewport);
+        }
+    }
+}
+
 void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) {
     if (mLocked.spriteController == nullptr) {
         JNIEnv* env = jniEnv();
@@ -821,16 +834,16 @@
 
     if (mLocked.systemUiVisibility != visibility) {
         mLocked.systemUiVisibility = visibility;
-
-        sp<PointerController> controller = mLocked.pointerController.promote();
-        if (controller != nullptr) {
-            updateInactivityTimeoutLocked(controller);
-        }
+        updateInactivityTimeoutLocked();
     }
 }
 
-void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller)
-        REQUIRES(mLock) {
+void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller == nullptr) {
+        return;
+    }
+
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
     controller->setInactivityTimeout(lightsOut
             ? PointerController::INACTIVITY_TIMEOUT_SHORT
@@ -1824,6 +1837,9 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
             "getPointerIcon", "()Landroid/view/PointerIcon;");
 
+    GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
+            "getPointerDisplayId", "()I");
+
     GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
             "getKeyboardLayoutOverlay",
             "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index c44f306..240b820 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -126,4 +126,14 @@
     public List<String> getCrossProfileCalendarPackagesForUser(int userHandle) {
         return Collections.emptyList();
     }
+
+    @Override
+    public boolean isManagedKiosk() {
+        return false;
+    }
+
+    @Override
+    public boolean isUnattendedManagedKiosk() {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bfbaac9..bc550dc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -448,6 +448,12 @@
     private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = TimeUnit.HOURS.toMillis(1);
 
     /**
+     * The amount of ms that a managed kiosk must go without user interaction to be considered
+     * unattended.
+     */
+    private static final int UNATTENDED_MANAGED_KIOSK_MS = 30000;
+
+    /**
      * Strings logged with {@link
      * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}.
      */
@@ -5140,7 +5146,7 @@
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
-        mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+        getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
                 UserHandle.USER_SYSTEM, timeMs);
     }
 
@@ -5159,7 +5165,7 @@
         }
         policy.mLastMaximumTimeToLock = timeMs;
 
-        mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+        getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
                 userId, policy.mLastMaximumTimeToLock);
     }
 
@@ -13577,4 +13583,79 @@
         }
         return Collections.emptyList();
     }
+
+    @Override
+    public boolean isManagedKiosk() {
+        if (!mHasFeature) {
+            return false;
+        }
+        enforceManageUsers();
+        long id = mInjector.binderClearCallingIdentity();
+        try {
+            return isManagedKioskInternal();
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+    }
+
+    @Override
+    public boolean isUnattendedManagedKiosk() {
+        if (!mHasFeature) {
+            return false;
+        }
+        enforceManageUsers();
+        long id = mInjector.binderClearCallingIdentity();
+        try {
+            return isManagedKioskInternal()
+                    && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS);
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+    }
+
+    /**
+     * Returns whether the device is currently being used as a publicly-accessible dedicated device.
+     * Assumes that feature checks and permission checks have already been performed, and that the
+     * calling identity has been cleared.
+     */
+    private boolean isManagedKioskInternal() throws RemoteException {
+        return mOwners.hasDeviceOwner()
+                && mInjector.getIActivityManager().getLockTaskModeState()
+                        == ActivityManager.LOCK_TASK_MODE_LOCKED
+                && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO)
+                && !deviceHasKeyguard()
+                && !inEphemeralUserSession();
+    }
+
+    private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException {
+        int lockTaskFeatures =
+                getUserData(mInjector.getIActivityManager().getCurrentUser().id).mLockTaskFeatures;
+        return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
+    }
+
+    private boolean deviceHasKeyguard() {
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            if (mLockPatternUtils.isSecure(userInfo.id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean inEphemeralUserSession() {
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            if (mInjector.getUserManager().isUserEphemeral(userInfo.id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private PowerManagerInternal getPowerManagerInternal() {
+        return mInjector.getPowerManagerInternal();
+    }
 }
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index b01adc9..96ef0ce 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -22,7 +22,10 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+import static org.testng.Assert.expectThrows;
 
+import android.annotation.UserIdInt;
 import android.app.Application;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -32,11 +35,14 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import com.android.server.backup.testing.BackupManagerServiceTestUtils;
 import com.android.server.backup.testing.TransportData;
+import com.android.server.testing.shadows.ShadowBinder;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,6 +50,8 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContextWrapper;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -51,14 +59,19 @@
 
 /** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBinder.class})
 @Presubmit
 public class BackupManagerServiceTest {
     private static final String TEST_PACKAGE = "package";
     private static final String TEST_TRANSPORT = "transport";
 
+    private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
+
+    private ShadowContextWrapper mShadowContext;
     @Mock private UserBackupManagerService mUserBackupManagerService;
     private BackupManagerService mBackupManagerService;
     private Context mContext;
+    @UserIdInt private int mUserId;
 
     /** Initialize {@link BackupManagerService}. */
     @Before
@@ -67,6 +80,8 @@
 
         Application application = RuntimeEnvironment.application;
         mContext = application;
+        mShadowContext = shadowOf(application);
+        mUserId = NON_USER_SYSTEM;
         mBackupManagerService =
                 new BackupManagerService(
                         application,
@@ -76,6 +91,15 @@
     }
 
     /**
+     * Clean up and reset state that was created for testing {@link BackupManagerService}
+     * operations.
+     */
+    @After
+    public void tearDown() throws Exception {
+        ShadowBinder.reset();
+    }
+
+    /**
      * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}.
      * This is specifically to prevent overloading the logs in production.
      */
@@ -274,11 +298,41 @@
     // ---------------------------------------------
     // Settings tests
     // ---------------------------------------------
+    /**
+     * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} throws a
+     * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Test
+    public void setBackupEnabled_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.setBackupEnabled(mUserId, true));
+    }
+
+    /**
+     * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} does not
+     * require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is
+     * the same as the target user id.
+     */
+    @Test
+    public void setBackupEnabled_whenCallingUserIsTargetUser_doesntNeedPermission() {
+        ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        mBackupManagerService.setBackupEnabled(mUserId, true);
+
+        verify(mUserBackupManagerService).setBackupEnabled(true);
+    }
+
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
     @Test
     public void setBackupEnabled_callsSetBackupEnabledForUser() throws Exception {
-        mBackupManagerService.setBackupEnabled(true);
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        mBackupManagerService.setBackupEnabled(mUserId, true);
 
         verify(mUserBackupManagerService).setBackupEnabled(true);
     }
@@ -299,10 +353,25 @@
         verify(mUserBackupManagerService).setBackupProvisioned(true);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#isBackupEnabled(int)} throws a
+     * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Test
+    public void testIsBackupEnabled_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.isBackupEnabled(mUserId));
+    }
+
     /** Test that the backup service routes methods correctly to the user that requests it. */
     @Test
     public void testIsBackupEnabled_callsIsBackupEnabledForUser() throws Exception {
-        mBackupManagerService.isBackupEnabled();
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        mBackupManagerService.isBackupEnabled(mUserId);
 
         verify(mUserBackupManagerService).isBackupEnabled();
     }
@@ -330,30 +399,81 @@
         verify(mUserBackupManagerService).filterAppsEligibleForBackup(packages);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#backupNow(int)} throws a
+     * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Test
+    public void testBackupNow_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.backupNow(mUserId));
+    }
+
     /** Test that the backup service routes methods correctly to the user that requests it. */
     @Test
     public void testBackupNow_callsBackupNowForUser() throws Exception {
-        mBackupManagerService.backupNow();
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        mBackupManagerService.backupNow(mUserId);
 
         verify(mUserBackupManagerService).backupNow();
     }
 
-    /** Test that the backup service routes methods correctly to the user that requests it. */
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(int, String[], IBackupObserver,
+     * IBackupManagerMonitor, int)} throws a {@link SecurityException} if the caller does not have
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
     @Test
-    public void testRequestBackup_callsRequestBackupForUser() throws Exception {
+    public void testRequestBackup_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
         String[] packages = {TEST_PACKAGE};
         IBackupObserver observer = mock(IBackupObserver.class);
         IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
 
-        mBackupManagerService.requestBackup(packages, observer, monitor, /* flags */ 0);
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.requestBackup(mUserId, packages, observer, monitor, 0));
+    }
+
+
+    /** Test that the backup service routes methods correctly to the user that requests it. */
+    @Test
+    public void testRequestBackup_callsRequestBackupForUser() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        String[] packages = {TEST_PACKAGE};
+        IBackupObserver observer = mock(IBackupObserver.class);
+        IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
+
+        mBackupManagerService.requestBackup(mUserId, packages, observer, monitor,
+                /* flags */ 0);
 
         verify(mUserBackupManagerService).requestBackup(packages, observer, monitor, /* flags */ 0);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#cancelBackups(int)} throws a
+     * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Test
+    public void testCancelBackups_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.cancelBackups(mUserId));
+    }
+
+
     /** Test that the backup service routes methods correctly to the user that requests it. */
     @Test
     public void testCancelBackups_callsCancelBackupsForUser() throws Exception {
-        mBackupManagerService.cancelBackups();
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        mBackupManagerService.cancelBackups(mUserId);
 
         verify(mUserBackupManagerService).cancelBackups();
     }
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
index 1ece49e..9de9f50 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
@@ -17,9 +17,11 @@
 package com.android.server.testing.shadows;
 
 import android.os.Binder;
+import android.os.UserHandle;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
 
 /**
  * Extends {@link org.robolectric.shadows.ShadowBinder} with {@link Binder#clearCallingIdentity()}
@@ -30,6 +32,7 @@
 public class ShadowBinder extends org.robolectric.shadows.ShadowBinder {
     public static final Integer LOCAL_UID = 1000;
     private static Integer originalCallingUid;
+    private static UserHandle sCallingUserHandle;
 
     @Implementation
     protected static long clearCallingIdentity() {
@@ -42,4 +45,30 @@
     protected static void restoreCallingIdentity(long token) {
         setCallingUid(originalCallingUid);
     }
+
+    public static void setCallingUserHandle(UserHandle userHandle) {
+        sCallingUserHandle = userHandle;
+    }
+
+    /**
+     * Shadows {@link Binder#getCallingUserHandle()}. If {@link ShadowBinder#sCallingUserHandle}
+     * is set, return that; otherwise mimic the default implementation.
+     */
+    @Implementation
+    public static UserHandle getCallingUserHandle() {
+        if (sCallingUserHandle != null) {
+            return sCallingUserHandle;
+        } else {
+            return UserHandle.of(UserHandle.getUserId(getCallingUid()));
+        }
+    }
+
+    /**
+     * Clean up and reset state that was created for testing.
+     */
+    @Resetter
+    public static void reset() {
+        sCallingUserHandle = null;
+        org.robolectric.shadows.ShadowBinder.reset();
+    }
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d49e78a..d7b1cb4 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -88,7 +88,7 @@
         "utils/**/*.java",
     ],
     static_libs: [
-        "android-support-test",
+        "junit",
         "mockito-target-minus-junit4",
     ],
     libs: [
diff --git a/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java
index f70efdf..f75617e 100644
--- a/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.os.Binder;
 import android.os.Process;
 import android.platform.test.annotations.Presubmit;
 
@@ -26,9 +25,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.BinderInternal.CallSession;
-import com.android.server.BinderCallsStatsService.WorkSourceProvider;
+import com.android.server.BinderCallsStatsService.AuthorizedWorkSourceProvider;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,7 +36,7 @@
 public class BinderCallsStatsServiceTest {
     @Test
     public void weTrustOurselves() {
-        WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+        AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() {
             protected int getCallingUid() {
                 return Process.myUid();
             }
@@ -55,7 +52,7 @@
 
     @Test
     public void workSourceSetIfCallerHasPermission() {
-        WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+        AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() {
             protected int getCallingUid() {
                 // System process uid which as UPDATE_DEVICE_STATS.
                 return 1001;
@@ -72,7 +69,7 @@
 
     @Test
     public void workSourceResolvedToCallingUid() {
-        WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+        AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() {
             protected int getCallingUid() {
                 // UID without permissions.
                 return Integer.MAX_VALUE;
@@ -86,40 +83,4 @@
 
         assertEquals(Integer.MAX_VALUE, workSourceProvider.resolveWorkSourceUid());
     }
-
-    @Test
-    public void workSourceSet() {
-        TestObserver observer = new TestObserver();
-        observer.callStarted(new Binder(), 0);
-        assertEquals(true, observer.workSourceSet);
-    }
-
-    static class TestObserver extends BinderCallsStatsService.BinderCallsObserver {
-        public boolean workSourceSet = false;
-
-        TestObserver() {
-            super(new NoopObserver(), new WorkSourceProvider());
-        }
-
-        @Override
-        protected void setThreadLocalWorkSourceUid(int uid) {
-            workSourceSet = true;
-        }
-    }
-
-
-    static class NoopObserver implements BinderInternal.Observer {
-        @Override
-        public CallSession callStarted(Binder binder, int code) {
-            return null;
-        }
-
-        @Override
-        public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
-        }
-
-        @Override
-        public void callThrewException(CallSession s, Exception exception) {
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index fd010f1..751ed9b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -86,8 +87,9 @@
             new ComponentName("package1", "class1"),
             new ComponentName("package2", "class2")
     };
-    private final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
+    private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
 
+    @UserIdInt private int mUserId;
     @Mock private BackupManagerService mBackupManagerServiceMock;
     @Mock private Context mContextMock;
     @Mock private File mSuppressFileMock;
@@ -110,11 +112,13 @@
 
         TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
         TrampolineTestable.sSuppressFile = mSuppressFileMock;
+        TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
         TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
         TrampolineTestable.sBackupDisabled = false;
 
         when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
 
+        mUserId = NON_USER_SYSTEM;
         mTrampoline = new TrampolineTestable(mContextMock);
 
         mContentResolver = new MockContentResolver();
@@ -361,10 +365,22 @@
     }
 
     @Test
-    public void setBackupEnabled_forwarded() throws RemoteException {
+    public void setBackupEnabledForUser_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
+        mTrampoline.setBackupEnabledForUser(mUserId, true);
+
+        verify(mBackupManagerServiceMock).setBackupEnabled(mUserId, true);
+    }
+
+    @Test
+    public void setBackupEnabled_forwardedToCallingUserId() throws RemoteException {
+        TrampolineTestable.sCallingUserId = mUserId;
+        mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
         mTrampoline.setBackupEnabled(true);
-        verify(mBackupManagerServiceMock).setBackupEnabled(true);
+
+        verify(mBackupManagerServiceMock).setBackupEnabled(mUserId, true);
     }
 
     @Test
@@ -400,10 +416,22 @@
     }
 
     @Test
-    public void isBackupEnabled_forwarded() throws RemoteException {
+    public void isBackupEnabledForUser_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
+        mTrampoline.isBackupEnabledForUser(mUserId);
+
+        verify(mBackupManagerServiceMock).isBackupEnabled(mUserId);
+    }
+
+    @Test
+    public void isBackupEnabled_forwardedToCallingUserId() throws RemoteException {
+        TrampolineTestable.sCallingUserId = mUserId;
+        mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
         mTrampoline.isBackupEnabled();
-        verify(mBackupManagerServiceMock).isBackupEnabled();
+
+        verify(mBackupManagerServiceMock).isBackupEnabled(mUserId);
     }
 
     @Test
@@ -439,10 +467,22 @@
     }
 
     @Test
-    public void backupNow_forwarded() throws RemoteException {
+    public void backupNowForUser_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
+        mTrampoline.backupNowForUser(mUserId);
+
+        verify(mBackupManagerServiceMock).backupNow(mUserId);
+    }
+
+    @Test
+    public void backupNow_forwardedToCallingUserId() throws RemoteException {
+        TrampolineTestable.sCallingUserId = mUserId;
+        mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
         mTrampoline.backupNow();
-        verify(mBackupManagerServiceMock).backupNow();
+
+        verify(mBackupManagerServiceMock).backupNow(mUserId);
     }
 
     @Test
@@ -798,15 +838,28 @@
     }
 
     @Test
-    public void requestBackup_forwarded() throws RemoteException {
-        when(mBackupManagerServiceMock.requestBackup(PACKAGE_NAMES, mBackupObserverMock,
-                mBackupManagerMonitorMock, 123)).thenReturn(456);
-
+    public void requestBackupForUser_forwarded() throws RemoteException {
+        when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
-        assertEquals(456, mTrampoline.requestBackup(PACKAGE_NAMES, mBackupObserverMock,
-                mBackupManagerMonitorMock, 123));
-        verify(mBackupManagerServiceMock).requestBackup(PACKAGE_NAMES, mBackupObserverMock,
-                mBackupManagerMonitorMock, 123);
+
+        assertEquals(456, mTrampoline.requestBackupForUser(mUserId, PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123));
+        verify(mBackupManagerServiceMock).requestBackup(mUserId, PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123);
+    }
+
+    @Test
+    public void requestBackup_forwardedToCallingUserId() throws RemoteException {
+        TrampolineTestable.sCallingUserId = mUserId;
+        when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
+        mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
+        assertEquals(456, mTrampoline.requestBackup(PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123));
+        verify(mBackupManagerServiceMock).requestBackup(mUserId, PACKAGE_NAMES,
+                mBackupObserverMock, mBackupManagerMonitorMock, 123);
     }
 
     @Test
@@ -816,10 +869,22 @@
     }
 
     @Test
-    public void cancelBackups_forwarded() throws RemoteException {
+    public void cancelBackupsForUser_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
+        mTrampoline.cancelBackupsForUser(mUserId);
+
+        verify(mBackupManagerServiceMock).cancelBackups(mUserId);
+    }
+
+    @Test
+    public void cancelBackups_forwardedToCallingUserId() throws RemoteException {
+        TrampolineTestable.sCallingUserId = mUserId;
+        mTrampoline.initializeService(UserHandle.USER_SYSTEM);
+
         mTrampoline.cancelBackups();
-        verify(mBackupManagerServiceMock).cancelBackups();
+
+        verify(mBackupManagerServiceMock).cancelBackups(mUserId);
     }
 
     @Test
@@ -891,6 +956,7 @@
     private static class TrampolineTestable extends Trampoline {
         static boolean sBackupDisabled = false;
         static File sSuppressFile = null;
+        static int sCallingUserId = -1;
         static int sCallingUid = -1;
         static BackupManagerService sBackupManagerServiceMock = null;
         private int mCreateServiceCallsCount = 0;
@@ -909,6 +975,10 @@
             return sSuppressFile;
         }
 
+        protected int binderGetCallingUserId() {
+            return sCallingUserId;
+        }
+
         @Override
         protected int binderGetCallingUid() {
             return sCallingUid;
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c4c2ad9..839b25f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -212,7 +212,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PRIMARY_USER)));
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_currentUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -234,7 +256,31 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notInstalled() {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -254,7 +300,29 @@
                                 mIApplicationThread,
                                 PACKAGE_TWO,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_fakeCaller() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -276,7 +344,31 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notExported() {
+        mActivityInfo.exported = false;
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -296,7 +388,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 new ComponentName(PACKAGE_TWO, "test"),
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_anotherPackage() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -316,7 +430,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(SECONDARY_USER)));
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_secondaryUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -335,7 +471,8 @@
                 mIApplicationThread,
                 PACKAGE_ONE,
                 ACTIVITY_COMPONENT,
-                UserHandle.of(PRIMARY_USER));
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true);
 
         verify(mActivityTaskManagerInternal)
                 .startActivityAsUser(
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index dad7b93..fd07cb0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -18,10 +18,14 @@
 
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -41,6 +45,10 @@
 
 import com.android.server.pm.Installer;
 
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.PathClassLoader;
+import dalvik.system.VMRuntime;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -50,10 +58,6 @@
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
 
-import dalvik.system.DelegateLastClassLoader;
-import dalvik.system.PathClassLoader;
-import dalvik.system.VMRuntime;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -129,6 +133,9 @@
 
         // Package is not used by others, so we should get nothing back.
         assertNoUseInfo(mFooUser0);
+
+        // A package loading its own code is not stored as DCL.
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -140,6 +147,8 @@
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
         assertIsUsedByOtherApps(mBarUser0, pui, true);
         assertTrue(pui.getDexUseInfoMap().isEmpty());
+
+        assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths());
     }
 
     @Test
@@ -152,6 +161,8 @@
         assertIsUsedByOtherApps(mFooUser0, pui, false);
         assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
     }
 
     @Test
@@ -164,6 +175,8 @@
         assertIsUsedByOtherApps(mBarUser0, pui, false);
         assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);
+
+        assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries);
     }
 
     @Test
@@ -200,9 +213,10 @@
     }
 
     @Test
-    public void testPackageUseInfoNotFound() {
+    public void testNoNotify() {
         // Assert we don't get back data we did not previously record.
         assertNoUseInfo(mFooUser0);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -210,6 +224,7 @@
         // Notifying with an invalid ISA should be ignored.
         notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0);
         assertNoUseInfo(mInvalidIsa);
+        assertNoDclInfo(mInvalidIsa);
     }
 
     @Test
@@ -218,6 +233,7 @@
         // register in DexManager#load should be ignored.
         notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0);
         assertNoUseInfo(mDoesNotExist);
+        assertNoDclInfo(mDoesNotExist);
     }
 
     @Test
@@ -226,6 +242,8 @@
         // Request should be ignored.
         notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1);
         assertNoUseInfo(mBarUser1);
+
+        assertNoDclInfo(mBarUser1);
     }
 
     @Test
@@ -235,6 +253,10 @@
         // still check that nothing goes unexpected in DexManager.
         notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1);
         assertNoUseInfo(mBarUser1);
+        assertNoUseInfo(mFooUser0);
+
+        assertNoDclInfo(mBarUser1);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -247,6 +269,7 @@
         // is trying to load something from it we should not find it.
         notifyDexLoad(mFooUser0, newSecondaries, mUser0);
         assertNoUseInfo(newPackage);
+        assertNoDclInfo(newPackage);
 
         // Notify about newPackage install and let mFoo load its dexes.
         mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
@@ -257,6 +280,7 @@
         assertIsUsedByOtherApps(newPackage, pui, false);
         assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
+        assertHasDclInfo(newPackage, mFooUser0, newSecondaries);
     }
 
     @Test
@@ -273,6 +297,7 @@
         assertIsUsedByOtherApps(newPackage, pui, false);
         assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(newPackage, newPackage, newSecondaries);
     }
 
     @Test
@@ -305,6 +330,7 @@
         // We shouldn't find yet the new split as we didn't notify the package update.
         notifyDexLoad(mFooUser0, newSplits, mUser0);
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
 
         // Notify that bar is updated. splitSourceDirs will contain the updated path.
         mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(),
@@ -314,8 +340,8 @@
         // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers.
         notifyDexLoad(mFooUser0, newSplits, mUser0);
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
-        assertNotNull(pui);
         assertIsUsedByOtherApps(newSplits, pui, true);
+        assertHasDclInfo(mBarUser0, mFooUser0, newSplits);
     }
 
     @Test
@@ -326,11 +352,15 @@
 
         mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0);
 
-        // Bar should not be around since it was removed for all users.
+        // Data for user 1 should still be present
         PackageUseInfo pui = getPackageUseInfo(mBarUser1);
-        assertNotNull(pui);
         assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(),
                 /*isUsedByOtherApps*/false, mUser1);
+        assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths());
+
+        // But not user 0
+        assertNoUseInfo(mBarUser0, mUser0);
+        assertNoDclInfo(mBarUser0, mUser0);
     }
 
     @Test
@@ -349,6 +379,8 @@
         PackageUseInfo pui = getPackageUseInfo(mFooUser0);
         assertIsUsedByOtherApps(mFooUser0, pui, true);
         assertTrue(pui.getDexUseInfoMap().isEmpty());
+
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -362,6 +394,7 @@
         // Foo should not be around since all its secondary dex info were deleted
         // and it is not used by other apps.
         assertNoUseInfo(mFooUser0);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -374,6 +407,7 @@
 
         // Bar should not be around since it was removed for all users.
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
     }
 
     @Test
@@ -383,6 +417,7 @@
         notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0);
         // The dex file should not be recognized as a package.
         assertFalse(mDexManager.hasInfoOnPackage(frameworkDex));
+        assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex));
     }
 
     @Test
@@ -395,6 +430,8 @@
         assertIsUsedByOtherApps(mFooUser0, pui, false);
         assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
     }
 
     @Test
@@ -402,7 +439,12 @@
         List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths();
         notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
 
+        // We don't record the dex usage
         assertNoUseInfo(mBarUser0UnsupportedClassLoader);
+
+        // But we do record this as an intance of dynamic code loading
+        assertHasDclInfo(
+                mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries);
     }
 
     @Test
@@ -414,6 +456,8 @@
         notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0);
 
         assertNoUseInfo(mBarUser0);
+
+        assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths());
     }
 
     @Test
@@ -421,6 +465,7 @@
         notifyDexLoad(mBarUser0, null, mUser0);
 
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
     }
 
     @Test
@@ -455,12 +500,14 @@
         notifyDexLoad(mBarUser0, secondaries, mUser0);
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
         assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
 
         // Record bar secondaries again with an unsupported class loader. This should not change the
         // context.
         notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
         pui = getPackageUseInfo(mBarUser0);
         assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
     }
 
     @Test
@@ -533,13 +580,53 @@
 
     private PackageUseInfo getPackageUseInfo(TestData testData) {
         assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
-        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
+        PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
+        assertNotNull(pui);
+        return pui;
     }
 
     private void assertNoUseInfo(TestData testData) {
         assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
     }
 
+    private void assertNoUseInfo(TestData testData, int userId) {
+        if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) {
+            return;
+        }
+        PackageUseInfo pui = getPackageUseInfo(testData);
+        for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) {
+            assertNotEquals(userId, dexUseInfo.getOwnerUserId());
+        }
+    }
+
+    private void assertNoDclInfo(TestData testData) {
+        assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()));
+    }
+
+    private void assertNoDclInfo(TestData testData, int userId) {
+        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName());
+        if (info == null) {
+            return;
+        }
+
+        for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) {
+            assertNotEquals(userId, fileInfo.mUserId);
+        }
+    }
+
+    private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) {
+        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName());
+        assertNotNull("No DCL data for owner " + owner.getPackageName(), info);
+        for (String path : paths) {
+            DynamicCodeFile fileInfo = info.mFileUsageMap.get(path);
+            assertNotNull("No DCL data for path " + path, fileInfo);
+            assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType);
+            assertEquals(owner.mUserId, fileInfo.mUserId);
+            assertTrue("No DCL data for loader " + loader.getPackageName(),
+                    fileInfo.mLoadingPackages.contains(loader.getPackageName()));
+        }
+    }
+
     private static PackageInfo getMockPackageInfo(String packageName, int userId) {
         PackageInfo pi = new PackageInfo();
         pi.packageName = packageName;
@@ -563,11 +650,13 @@
         private final PackageInfo mPackageInfo;
         private final String mLoaderIsa;
         private final String mClassLoader;
+        private final int mUserId;
 
         private TestData(String packageName, String loaderIsa, int userId, String classLoader) {
             mPackageInfo = getMockPackageInfo(packageName, userId);
             mLoaderIsa = loaderIsa;
             mClassLoader = classLoader;
+            mUserId = userId;
         }
 
         private TestData(String packageName, String loaderIsa, int userId) {
@@ -603,9 +692,7 @@
         List<String> getBaseAndSplitDexPaths() {
             List<String> paths = new ArrayList<>();
             paths.add(mPackageInfo.applicationInfo.sourceDir);
-            for (String split : mPackageInfo.applicationInfo.splitSourceDirs) {
-                paths.add(split);
-            }
+            Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs);
             return paths;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
new file mode 100644
index 0000000..eb4cc4e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.dex;
+
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.escape;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.unescape;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
+import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageDynamicCodeLoadingTests {
+
+    // Deliberately making a copy here since we're testing identity and
+    // string literals have a tendency to be identical.
+    private static final String TRIVIAL_STRING = new String("hello/world");
+    private static final Entry[] NO_ENTRIES = {};
+    private static final String[] NO_PACKAGES = {};
+
+    @Test
+    public void testRecord() {
+        Entry[] entries = {
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"),
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"),
+                new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"),
+                new Entry("owning.package2", "/path/file3", 'D', 0, "loading.package2"),
+        };
+
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+        assertHasEntries(info, entries);
+    }
+
+    @Test
+    public void testRecord_returnsHasChanged() {
+        Entry owner1Path1Loader1 =
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
+        Entry owner1Path1Loader2 =
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2");
+        Entry owner1Path2Loader1 =
+                new Entry("owning.package1", "/path/file2", 'D', 10, "loading.package1");
+        Entry owner2Path1Loader1 =
+                new Entry("owning.package2", "/path/file1", 'D', 10, "loading.package2");
+
+        PackageDynamicCodeLoading info = new PackageDynamicCodeLoading();
+
+        assertTrue(record(info, owner1Path1Loader1));
+        assertFalse(record(info, owner1Path1Loader1));
+
+        assertTrue(record(info, owner1Path1Loader2));
+        assertFalse(record(info, owner1Path1Loader2));
+
+        assertTrue(record(info, owner1Path2Loader1));
+        assertFalse(record(info, owner1Path2Loader1));
+
+        assertTrue(record(info, owner2Path1Loader1));
+        assertFalse(record(info, owner2Path1Loader1));
+
+        assertHasEntries(info,
+                owner1Path1Loader1, owner1Path1Loader2, owner1Path2Loader1, owner2Path1Loader1);
+    }
+
+    @Test
+    public void testRecord_changeUserForFile_throws() {
+        Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
+        Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
+
+        PackageDynamicCodeLoading info = makePackageDcl(entry1);
+
+        assertThrows(() -> record(info, entry2));
+        assertHasEntries(info, entry1);
+    }
+
+    @Test
+    public void testRecord_badFileType_throws() {
+        Entry entry = new Entry("owning.package", "/path/file", 'Z', 10, "loading.package");
+        PackageDynamicCodeLoading info = new PackageDynamicCodeLoading();
+
+        assertThrows(() -> record(info, entry));
+    }
+
+    @Test
+    public void testClear() {
+        Entry[] entries = {
+                new Entry("owner1", "file1", 'D', 10, "loader1"),
+                new Entry("owner2", "file2", 'D', 20, "loader2"),
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        info.clear();
+        assertHasEntries(info, NO_ENTRIES);
+    }
+
+    @Test
+    public void testRemovePackage_present() {
+        Entry other = new Entry("other", "file", 'D', 0, "loader");
+        Entry[] entries = {
+                new Entry("owner", "file1", 'D', 10, "loader1"),
+                new Entry("owner", "file2", 'D', 20, "loader2"),
+                other
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertTrue(info.removePackage("owner"));
+        assertHasEntries(info, other);
+        assertHasPackages(info, "other");
+    }
+
+    @Test
+    public void testRemovePackage_notPresent() {
+        Entry[] entries = { new Entry("owner", "file", 'D', 0, "loader") };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertFalse(info.removePackage("other"));
+        assertHasEntries(info, entries);
+    }
+
+    @Test
+    public void testRemoveUserPackage_notPresent() {
+        Entry[] entries = { new Entry("owner", "file", 'D', 0, "loader") };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertFalse(info.removeUserPackage("other", 0));
+        assertHasEntries(info, entries);
+    }
+
+    @Test
+    public void testRemoveUserPackage_presentWithNoOtherUsers() {
+        Entry other = new Entry("other", "file", 'D', 0, "loader");
+        Entry[] entries = {
+                new Entry("owner", "file1", 'D', 0, "loader1"),
+                new Entry("owner", "file2", 'D', 0, "loader2"),
+                other
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertTrue(info.removeUserPackage("owner", 0));
+        assertHasEntries(info, other);
+        assertHasPackages(info, "other");
+    }
+
+    @Test
+    public void testRemoveUserPackage_presentWithUsers() {
+        Entry other = new Entry("owner", "file", 'D', 1, "loader");
+        Entry[] entries = {
+                new Entry("owner", "file1", 'D', 0, "loader1"),
+                new Entry("owner", "file2", 'D', 0, "loader2"),
+                other
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertTrue(info.removeUserPackage("owner", 0));
+        assertHasEntries(info, other);
+    }
+
+    @Test
+    public void testRemoveFile_present() {
+        Entry[] entries = {
+                new Entry("package1", "file1", 'D', 0, "loader1"),
+                new Entry("package1", "file2", 'D', 0, "loader1"),
+                new Entry("package2", "file1", 'D', 0, "loader2"),
+        };
+        Entry[] expectedSurvivors = {
+                new Entry("package1", "file2", 'D', 0, "loader1"),
+                new Entry("package2", "file1", 'D', 0, "loader2"),
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertTrue(info.removeFile("package1", "file1", 0));
+        assertHasEntries(info, expectedSurvivors);
+    }
+
+    @Test
+    public void testRemoveFile_onlyEntry() {
+        Entry[] entries = {
+                new Entry("package1", "file1", 'D', 0, "loader1"),
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertTrue(info.removeFile("package1", "file1", 0));
+        assertHasEntries(info, NO_ENTRIES);
+        assertHasPackages(info, NO_PACKAGES);
+    }
+
+    @Test
+    public void testRemoveFile_notPresent() {
+        Entry[] entries = {
+                new Entry("package1", "file2", 'D', 0, "loader1"),
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertFalse(info.removeFile("package1", "file1", 0));
+        assertHasEntries(info, entries);
+    }
+
+    @Test
+    public void testRemoveFile_wrongUser() {
+        Entry[] entries = {
+                new Entry("package1", "file1", 'D', 10, "loader1"),
+        };
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+
+        assertFalse(info.removeFile("package1", "file1", 0));
+        assertHasEntries(info, entries);
+    }
+
+    @Test
+    public void testSyncData() {
+        Map<String, Set<Integer>> packageToUsersMap = ImmutableMap.of(
+                "package1", ImmutableSet.of(10, 20),
+                "package2", ImmutableSet.of(20));
+
+        Entry[] entries = {
+                new Entry("deleted.packaged", "file1", 'D', 10, "package1"),
+                new Entry("package1", "file2", 'D', 20, "package2"),
+                new Entry("package1", "file3", 'D', 10, "package2"),
+                new Entry("package1", "file3", 'D', 10, "deleted.package"),
+                new Entry("package2", "file4", 'D', 20, "deleted.package"),
+        };
+
+        Entry[] expectedSurvivors = {
+                new Entry("package1", "file2", 'D', 20, "package2"),
+        };
+
+        PackageDynamicCodeLoading info = makePackageDcl(entries);
+        info.syncData(packageToUsersMap);
+        assertHasEntries(info, expectedSurvivors);
+        assertHasPackages(info, "package1");
+    }
+
+    @Test
+    public void testRead_onlyHeader_emptyResult() throws Exception {
+        assertHasEntries(read("DCL1"), NO_ENTRIES);
+    }
+
+    @Test
+    public void testRead_noHeader_throws() {
+        assertThrows(IOException.class, () -> read(""));
+    }
+
+    @Test
+    public void testRead_wrongHeader_throws() {
+        assertThrows(IOException.class, () -> read("DCL2"));
+    }
+
+    @Test
+    public void testRead_oneEntry() throws Exception {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "D:10:loading.package:/path/fi\\\\le\n";
+        assertHasEntries(read(inputText),
+                new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package"));
+    }
+
+    @Test
+    public void testRead_emptyPackage() throws Exception {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n";
+        PackageDynamicCodeLoading info = read(inputText);
+        assertHasEntries(info, NO_ENTRIES);
+        assertHasPackages(info, NO_PACKAGES);
+    }
+
+    @Test
+    public void testRead_complex() throws Exception {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package1\n"
+                + "D:10:loading.package1,loading.package2:/path/file1\n"
+                + "D:5:loading.package1:/path/file2\n"
+                + "P:owning.package2\n"
+                + "D:0:loading.package2:/path/file3";
+        assertHasEntries(read(inputText),
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"),
+                new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"),
+                new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"),
+                new Entry("owning.package2", "/path/file3", 'D', 0, "loading.package2"));
+    }
+
+    @Test
+    public void testRead_missingPackageLine_throws() {
+        String inputText = ""
+                + "DCL1\n"
+                + "D:10:loading.package:/path/file\n";
+        assertThrows(IOException.class, () -> read(inputText));
+    }
+
+    @Test
+    public void testRead_malformedFile_throws() {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "Hello world!\n";
+        assertThrows(IOException.class, () -> read(inputText));
+    }
+
+    @Test
+    public void testRead_badFileType_throws() {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "X:10:loading.package:/path/file\n";
+        assertThrows(IOException.class, () -> read(inputText));
+    }
+
+    @Test
+    public void testRead_badUserId_throws() {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "D:999999999999999999:loading.package:/path/file\n";
+        assertThrows(IOException.class, () -> read(inputText));
+    }
+
+    @Test
+    public void testRead_missingPackages_throws() {
+        String inputText = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "D:1:,:/path/file\n";
+        assertThrows(IOException.class, () -> read(inputText));
+    }
+
+    @Test
+    public void testWrite_empty() throws Exception {
+        assertEquals("DCL1\n", write(NO_ENTRIES));
+    }
+
+    @Test
+    public void testWrite_oneEntry() throws Exception {
+        String expected = ""
+                + "DCL1\n"
+                + "P:owning.package\n"
+                + "D:10:loading.package:/path/fi\\\\le\n";
+        String actual = write(
+                new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package"));
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testWrite_complex_roundTrips() throws Exception {
+        // There isn't a canonical order for the output in the presence of multiple items.
+        // So we just check that if we read back what we write we end up where we started.
+        Entry[] entries = {
+            new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"),
+            new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"),
+            new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"),
+            new Entry("owning.package2", "/path/fi\\le3", 'D', 0, "loading.package2")
+        };
+        assertHasEntries(read(write(entries)), entries);
+    }
+
+    @Test
+    public void testWrite_failure_throws() {
+        PackageDynamicCodeLoading info = makePackageDcl(
+                new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package"));
+        assertThrows(IOException.class, () -> info.write(new ThrowingOutputStream()));
+    }
+
+    @Test
+    public void testEscape_trivialCase_returnsSameString() {
+        assertSame(TRIVIAL_STRING, escape(TRIVIAL_STRING));
+    }
+
+    @Test
+    public void testEscape() {
+        String input = "backslash\\newline\nreturn\r";
+        String expected = "backslash\\\\newline\\nreturn\\r";
+        assertEquals(expected, escape(input));
+    }
+
+    @Test
+    public void testUnescape_trivialCase_returnsSameString() throws Exception {
+        assertSame(TRIVIAL_STRING, unescape(TRIVIAL_STRING));
+    }
+
+    @Test
+    public void testUnescape() throws Exception {
+        String input = "backslash\\\\newline\\nreturn\\r";
+        String expected = "backslash\\newline\nreturn\r";
+        assertEquals(expected, unescape(input));
+    }
+
+    @Test
+    public void testUnescape_badEscape_throws() {
+        assertThrows(IOException.class, () -> unescape("this is \\bad"));
+    }
+
+    @Test
+    public void testUnescape_trailingBackslash_throws() {
+        assertThrows(IOException.class, () -> unescape("don't do this\\"));
+    }
+
+    @Test
+    public void testEscapeUnescape_roundTrips() throws Exception {
+        assertRoundTripsWithEscape("foo");
+        assertRoundTripsWithEscape("\\\\\n\n\r");
+        assertRoundTripsWithEscape("\\a\\b\\");
+        assertRoundTripsWithEscape("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\");
+    }
+
+    private void assertRoundTripsWithEscape(String original) throws Exception {
+        assertEquals(original, unescape(escape(original)));
+    }
+
+    private boolean record(PackageDynamicCodeLoading info, Entry entry) {
+        return info.record(entry.mOwningPackage, entry.mPath, entry.mFileType, entry.mUserId,
+                entry.mLoadingPackage);
+    }
+
+    private PackageDynamicCodeLoading read(String inputText) throws Exception {
+        ByteArrayInputStream inputStream =
+                new ByteArrayInputStream(inputText.getBytes(UTF_8));
+
+        PackageDynamicCodeLoading info = new PackageDynamicCodeLoading();
+        info.read(inputStream);
+
+        return info;
+    }
+
+    private String write(Entry... entries) throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        makePackageDcl(entries).write(output);
+        return new String(output.toByteArray(), UTF_8);
+    }
+
+    private Set<Entry> entriesFrom(PackageDynamicCodeLoading info) {
+        ImmutableSet.Builder<Entry> entries = ImmutableSet.builder();
+        for (String owningPackage : info.getAllPackagesWithDynamicCodeLoading()) {
+            PackageDynamicCode packageInfo = info.getPackageDynamicCodeInfo(owningPackage);
+            Map<String, DynamicCodeFile> usageMap = packageInfo.mFileUsageMap;
+            for (Map.Entry<String, DynamicCodeFile> fileEntry : usageMap.entrySet()) {
+                String path = fileEntry.getKey();
+                DynamicCodeFile fileInfo = fileEntry.getValue();
+                for (String loadingPackage : fileInfo.mLoadingPackages) {
+                    entries.add(new Entry(owningPackage, path, fileInfo.mFileType, fileInfo.mUserId,
+                            loadingPackage));
+                }
+            }
+        }
+
+        return entries.build();
+    }
+
+    private PackageDynamicCodeLoading makePackageDcl(Entry... entries) {
+        PackageDynamicCodeLoading result = new PackageDynamicCodeLoading();
+        for (Entry entry : entries) {
+            result.record(entry.mOwningPackage, entry.mPath, entry.mFileType, entry.mUserId,
+                    entry.mLoadingPackage);
+        }
+        return result;
+
+    }
+
+    private void assertHasEntries(PackageDynamicCodeLoading info, Entry... expected) {
+        assertEquals(ImmutableSet.copyOf(expected), entriesFrom(info));
+    }
+
+    private void assertHasPackages(PackageDynamicCodeLoading info, String... expected) {
+        assertEquals(ImmutableSet.copyOf(expected), info.getAllPackagesWithDynamicCodeLoading());
+    }
+
+    /**
+     * Immutable representation of one entry in the dynamic code loading data (one package
+     * owning one file loaded by one package). Has well-behaved equality, hash and toString
+     * for ease of use in assertions.
+     */
+    private static class Entry {
+        private final String mOwningPackage;
+        private final String mPath;
+        private final char mFileType;
+        private final int mUserId;
+        private final String mLoadingPackage;
+
+        private Entry(String owningPackage, String path, char fileType, int userId,
+                String loadingPackage) {
+            mOwningPackage = owningPackage;
+            mPath = path;
+            mFileType = fileType;
+            mUserId = userId;
+            mLoadingPackage = loadingPackage;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Entry that = (Entry) o;
+            return mFileType == that.mFileType
+                    && mUserId == that.mUserId
+                    && Objects.equals(mOwningPackage, that.mOwningPackage)
+                    && Objects.equals(mPath, that.mPath)
+                    && Objects.equals(mLoadingPackage, that.mLoadingPackage);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mOwningPackage, mPath, mFileType, mUserId, mLoadingPackage);
+        }
+
+        @Override
+        public String toString() {
+            return "Entry("
+                    + "\"" + mOwningPackage + '"'
+                    + ", \"" + mPath + '"'
+                    + ", '" + mFileType + '\''
+                    + ", " + mUserId
+                    + ", \"" + mLoadingPackage + '\"'
+                    + ')';
+        }
+    }
+
+    private static class ThrowingOutputStream extends OutputStream {
+        @Override
+        public void write(int b) throws IOException {
+            throw new IOException("Intentional failure");
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 8ac2930..561c61f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.app.IBatteryStats;
@@ -193,4 +194,19 @@
 
         assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
+
+    @MediumTest
+    public void testWasDeviceIdleFor_true() {
+        int interval = 1000;
+        mService.onUserActivity();
+        SystemClock.sleep(interval);
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
+    }
+
+    @SmallTest
+    public void testWasDeviceIdleFor_false() {
+        int interval = 1000;
+        mService.onUserActivity();
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index c1963da..94d293e 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -281,6 +281,10 @@
         mFakeHal.mCallback.onValues(newSkin);
         verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
+        Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", status);
+        mFakeHal.mCallback.onValues(newBattery);
+        verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
index a9d4519..70fadd1 100644
--- a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
@@ -20,8 +20,11 @@
 
 import static org.junit.Assert.fail;
 
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 
+import android.util.ArrayMap;
+
 import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.collect.Sets;
@@ -33,6 +36,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -46,10 +50,25 @@
         return Sets.newHashSet(values);
     }
 
+    private static <K, V> Map<K, V> mapOf(Object... keyValuePairs) {
+        if (keyValuePairs.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        final int len = keyValuePairs.length / 2;
+        ArrayMap<K, V> m = new ArrayMap<>(len);
+        for (int i = 0; i < len;  ++i) {
+            m.put((K) keyValuePairs[i * 2], (V) keyValuePairs[(i * 2) + 1]);
+        }
+        return Collections.unmodifiableMap(m);
+
+    }
+
+
     @Test
     public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}");
-        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet());
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet(),
+                emptyMap());
         assertThat(config.minSdk).isEqualTo(2);
         assertThat(config.maxSdk).isEqualTo(3);
     }
@@ -58,7 +77,7 @@
     public void testParsePerSdkConfigNoMinSdk() throws JSONException {
         JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -69,7 +88,7 @@
     public void testParsePerSdkConfigNoMaxSdk() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -80,7 +99,7 @@
     public void testParsePerSdkConfigNoValues() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -88,10 +107,10 @@
     }
 
     @Test
-    public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException, InvalidConfigException {
+    public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -99,10 +118,10 @@
     }
 
     @Test
-    public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException, InvalidConfigException {
+    public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -113,7 +132,7 @@
     public void testParsePerSdkConfigNullValues() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -124,7 +143,8 @@
     public void testParsePerSdkConfigZeroValues()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}");
-        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"));
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
+                emptyMap());
         assertThat(config.values).hasSize(0);
     }
 
@@ -133,18 +153,30 @@
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
                 "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
-        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"));
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
+                emptyMap());
         assertThat(config.values).containsExactly("a", "1");
     }
 
     @Test
+    public void testParsePerSdkConfigSingleKeyNullValue()
+            throws JSONException, InvalidConfigException {
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
+                emptyMap());
+        assertThat(config.values.keySet()).containsExactly("a");
+        assertThat(config.values.get("a")).isNull();
+    }
+
+    @Test
     public void testParsePerSdkConfigMultiKeys()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
                 "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, "
                         + "{\"key\":\"c\", \"value\": \"2\"}]}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(
-                json, setOf("a", "b", "c"));
+                json, setOf("a", "b", "c"), emptyMap());
         assertThat(config.values).containsExactly("a", "1", "c", "2");
     }
 
@@ -153,7 +185,7 @@
         JSONObject json = new JSONObject(
                 "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
         try {
-            SignedConfig.parsePerSdkConfig(json, setOf("b"));
+            SignedConfig.parsePerSdkConfig(json, setOf("b"), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -161,11 +193,67 @@
     }
 
     @Test
+    public void testParsePerSdkConfigSingleKeyWithMap()
+            throws JSONException, InvalidConfigException {
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
+                mapOf("a", mapOf("1", "one")));
+        assertThat(config.values).containsExactly("a", "one");
+    }
+
+    @Test
+    public void testParsePerSdkConfigSingleKeyWithMapInvalidValue() throws JSONException {
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"2\"}]}");
+        try {
+            SignedConfig.parsePerSdkConfig(json, setOf("b"), mapOf("a", mapOf("1", "one")));
+            fail("Expected InvalidConfigException");
+        } catch (InvalidConfigException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testParsePerSdkConfigMultiKeysWithMap()
+            throws JSONException, InvalidConfigException {
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"},"
+                + "{\"key\":\"b\", \"value\": \"1\"}]}");
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
+                mapOf("a", mapOf("1", "one")));
+        assertThat(config.values).containsExactly("a", "one", "b", "1");
+    }
+
+    @Test
+    public void testParsePerSdkConfigSingleKeyWithMapToNull()
+            throws JSONException, InvalidConfigException {
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
+                mapOf("a", mapOf("1", null)));
+        assertThat(config.values).containsExactly("a", null);
+    }
+
+    @Test
+    public void testParsePerSdkConfigSingleKeyWithMapFromNull()
+            throws JSONException, InvalidConfigException {
+        assertThat(mapOf(null, "allitnil")).containsExactly(null, "allitnil");
+        assertThat(mapOf(null, "allitnil").containsKey(null)).isTrue();
+        JSONObject json = new JSONObject(
+                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
+                mapOf("a", mapOf(null, "allitnil")));
+        assertThat(config.values).containsExactly("a", "allitnil");
+    }
+
+    @Test
     public void testParsePerSdkConfigSingleKeyNoValue()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
                 "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}");
-        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"));
+        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
+                emptyMap());
         assertThat(config.values).containsExactly("a", null);
     }
 
@@ -173,7 +261,7 @@
     public void testParsePerSdkConfigValuesInvalid() throws JSONException  {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": \"foo\"}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -184,7 +272,7 @@
     public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": [1, 2]}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -195,7 +283,7 @@
     public void testParsePerSdkConfigConfigEntryNull() throws JSONException {
         JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": [null]}");
         try {
-            SignedConfig.parsePerSdkConfig(json, emptySet());
+            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
         } catch (JSONException | InvalidConfigException e) {
             // expected
@@ -205,14 +293,15 @@
     @Test
     public void testParseVersion() throws InvalidConfigException {
         SignedConfig config = SignedConfig.parse(
-                "{\"version\": 1, \"config\": []}", emptySet());
+                "{\"version\": 1, \"config\": []}", emptySet(), emptyMap());
         assertThat(config.version).isEqualTo(1);
     }
 
     @Test
     public void testParseVersionInvalid() {
         try {
-            SignedConfig.parse("{\"version\": \"notanint\", \"config\": []}", emptySet());
+            SignedConfig.parse("{\"version\": \"notanint\", \"config\": []}", emptySet(),
+                    emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -222,7 +311,7 @@
     @Test
     public void testParseNoVersion() {
         try {
-            SignedConfig.parse("{\"config\": []}", emptySet());
+            SignedConfig.parse("{\"config\": []}", emptySet(), emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -232,7 +321,7 @@
     @Test
     public void testParseNoConfig() {
         try {
-            SignedConfig.parse("{\"version\": 1}", emptySet());
+            SignedConfig.parse("{\"version\": 1}", emptySet(), emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -242,7 +331,7 @@
     @Test
     public void testParseConfigNull() {
         try {
-            SignedConfig.parse("{\"version\": 1, \"config\": null}", emptySet());
+            SignedConfig.parse("{\"version\": 1, \"config\": null}", emptySet(), emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -252,7 +341,7 @@
     @Test
     public void testParseVersionNull() {
         try {
-            SignedConfig.parse("{\"version\": null, \"config\": []}", emptySet());
+            SignedConfig.parse("{\"version\": null, \"config\": []}", emptySet(), emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -262,7 +351,7 @@
     @Test
     public void testParseConfigInvalidEntry() {
         try {
-            SignedConfig.parse("{\"version\": 1, \"config\": [{}]}", emptySet());
+            SignedConfig.parse("{\"version\": 1, \"config\": [{}]}", emptySet(), emptyMap());
             fail("Expected InvalidConfigException");
         } catch (InvalidConfigException e) {
             //expected
@@ -273,7 +362,7 @@
     public void testParseSdkConfigSingle() throws InvalidConfigException {
         SignedConfig config = SignedConfig.parse(
                 "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}",
-                emptySet());
+                emptySet(), emptyMap());
         assertThat(config.perSdkConfig).hasSize(1);
     }
 
@@ -281,7 +370,8 @@
     public void testParseSdkConfigMultiple() throws InvalidConfigException {
         SignedConfig config = SignedConfig.parse(
                 "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, "
-                        + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet());
+                        + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet(),
+                emptyMap());
         assertThat(config.perSdkConfig).hasSize(2);
     }
 
@@ -314,7 +404,6 @@
         SignedConfig config = new SignedConfig(0, Arrays.asList(sdk13, sdk46));
         assertThat(config.getMatchingConfig(2)).isEqualTo(sdk13);
     }
-
     @Test
     public void testGetMatchingConfigNoMatch() {
         SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d950360..9556111 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -238,6 +238,11 @@
         protected void reportUserInteraction(NotificationRecord r) {
             return;
         }
+
+        @Override
+        protected void handleSavePolicyFile() {
+            return;
+        }
     }
 
     private class TestableToastCallback extends ITransientNotification.Stub {
@@ -1809,8 +1814,7 @@
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
-
-        try {
+try {
             mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
                     null, PKG, Process.myUserHandle());
             fail("listeners that don't have a companion device shouldn't be able to call this");
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 613ba00..904d55e 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -32,7 +32,9 @@
 import android.service.usb.UsbHostManagerProto;
 import android.service.usb.UsbIsHeadsetProto;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
@@ -86,6 +88,7 @@
     private int mNumConnects;    // TOTAL # of connect/disconnect
     private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>();
     private ConnectionRecord mLastConnect;
+    private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
 
     /*
      * ConnectionRecord
@@ -300,6 +303,11 @@
         if (mode != ConnectionRecord.DISCONNECT) {
             mLastConnect = rec;
         }
+        if (mode == ConnectionRecord.CONNECT) {
+            mConnected.put(deviceAddress, rec);
+        } else if (mode == ConnectionRecord.DISCONNECT) {
+            mConnected.remove(deviceAddress);
+        }
     }
 
     private void logUsbDevice(UsbDescriptorParser descriptorParser) {
@@ -408,6 +416,14 @@
                 // Tracking
                 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
                         parser.getRawDescriptors());
+
+                // Stats collection
+                if (parser.hasAudioInterface()) {
+                    StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, newDevice.getVendorId(),
+                            newDevice.getProductId(), parser.hasAudioInterface(),
+                            parser.hasHIDInterface(), parser.hasStorageInterface(),
+                            StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_CONNECTED, 0);
+                }
             }
         }
 
@@ -432,9 +448,22 @@
                 mUsbAlsaManager.usbDeviceRemoved(deviceAddress);
                 mSettingsManager.usbDeviceRemoved(device);
                 getCurrentUserSettings().usbDeviceRemoved(device);
-
+                ConnectionRecord current = mConnected.get(deviceAddress);
                 // Tracking
                 addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null);
+
+                if (current != null) {
+                    UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress,
+                            current.mDescriptors);
+                    if (parser.hasAudioInterface()) {
+                        // Stats collection
+                        StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, device.getVendorId(),
+                                device.getProductId(), parser.hasAudioInterface(),
+                                parser.hasHIDInterface(),  parser.hasStorageInterface(),
+                                StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_DISCONNECTED,
+                                System.currentTimeMillis() - current.mTimestamp);
+                    }
+                }
             } else {
                 Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone");
             }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 33da403..96618f5 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -41,12 +41,14 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.usb.UsbPortInfoProto;
 import android.service.usb.UsbPortManagerProto;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
@@ -54,6 +56,7 @@
 import com.android.server.FgThread;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.NoSuchElementException;
 
 /**
@@ -117,6 +120,10 @@
     private final ArrayMap<String, RawPortInfo> mSimulatedPorts =
             new ArrayMap<>();
 
+    // Maintains the current connected status of the port.
+    // Uploads logs only when the connection status is changes.
+    private final HashMap<String, Boolean> mConnected = new HashMap<>();
+
     public UsbPortManager(Context context) {
         mContext = context;
         try {
@@ -700,6 +707,18 @@
         // Guard against possible reentrance by posting the broadcast from the handler
         // instead of from within the critical section.
         mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL));
+
+        // Log to statsd
+        if (!mConnected.containsKey(portInfo.mUsbPort.getId())
+                || (mConnected.get(portInfo.mUsbPort.getId())
+                != portInfo.mUsbPortStatus.isConnected())) {
+            mConnected.put(portInfo.mUsbPort.getId(), portInfo.mUsbPortStatus.isConnected());
+            StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
+                    portInfo.mUsbPortStatus.isConnected()
+                    ? StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_CONNECTED :
+                    StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
+                    portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
+        }
     }
 
     private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
@@ -746,7 +765,12 @@
         public boolean mCanChangeMode;
         public boolean mCanChangePowerRole;
         public boolean mCanChangeDataRole;
-        public int mDisposition; // default initialized to 0 which means added
+        // default initialized to 0 which means added
+        public int mDisposition;
+        // Tracks elapsedRealtime() of when the port was connected
+        public long mConnectedAtMillis;
+        // 0 when port is connected. Else reports the last connected duration
+        public long mLastConnectDurationMillis;
 
         public PortInfo(String portId, int supportedModes) {
             mUsbPort = new UsbPort(portId, supportedModes);
@@ -756,6 +780,8 @@
                 int currentPowerRole, boolean canChangePowerRole,
                 int currentDataRole, boolean canChangeDataRole,
                 int supportedRoleCombinations) {
+            boolean dispositionChanged = false;
+
             mCanChangeMode = canChangeMode;
             mCanChangePowerRole = canChangePowerRole;
             mCanChangeDataRole = canChangeDataRole;
@@ -767,9 +793,18 @@
                     != supportedRoleCombinations) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations);
-                return true;
+                dispositionChanged = true;
             }
-            return false;
+
+            if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+                mConnectedAtMillis = SystemClock.elapsedRealtime();
+                mLastConnectDurationMillis = 0;
+            } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+                mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+                mConnectedAtMillis = 0;
+            }
+
+            return dispositionChanged;
         }
 
         void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
@@ -782,6 +817,10 @@
                     mCanChangePowerRole);
             dump.write("can_change_data_role", UsbPortInfoProto.CAN_CHANGE_DATA_ROLE,
                     mCanChangeDataRole);
+            dump.write("connected_at_millis",
+                    UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis);
+            dump.write("last_connect_duration_millis",
+                    UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis);
 
             dump.end(token);
         }
@@ -791,7 +830,9 @@
             return "port=" + mUsbPort + ", status=" + mUsbPortStatus
                     + ", canChangeMode=" + mCanChangeMode
                     + ", canChangePowerRole=" + mCanChangePowerRole
-                    + ", canChangeDataRole=" + mCanChangeDataRole;
+                    + ", canChangeDataRole=" + mCanChangeDataRole
+                    + ", connectedAtMillis=" + mConnectedAtMillis
+                    + ", lastConnectDurationMillis=" + mLastConnectDurationMillis;
         }
     }
 
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d617de0..36d0188 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -122,10 +122,21 @@
      * The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call
      * extras. Used to pass the phone accounts to display on the front end to the user in order to
      * select phone accounts to (for example) place a call.
+     * @deprecated Use the list from {@link #EXTRA_SUGGESTED_PHONE_ACCOUNTS} instead.
      */
+    @Deprecated
     public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
 
     /**
+     * Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
+     * UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
+     * will have the same length and be in the same order as the list passed with
+     * {@link #AVAILABLE_PHONE_ACCOUNTS}.
+     */
+    public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS =
+            "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
+
+    /**
      * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC)
      * when the last outgoing emergency call was made.  This is used to identify potential emergency
      * callbacks.
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
new file mode 100644
index 0000000..4e6a178
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public final class PhoneAccountSuggestion implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {REASON_NONE, REASON_INTRA_CARRIER, REASON_FREQUENT,
+            REASON_USER_SET, REASON_OTHER}, prefix = { "REASON_" })
+    public @interface SuggestionReason {}
+
+    /**
+     * Indicates that this account is not suggested for use, but is still available.
+     */
+    public static final int REASON_NONE = 0;
+
+    /**
+     * Indicates that the {@link PhoneAccountHandle} is suggested because the number we're calling
+     * is on the same carrier, and therefore may have lower rates.
+     */
+    public static final int REASON_INTRA_CARRIER = 1;
+
+    /**
+     * Indicates that the {@link PhoneAccountHandle} is suggested because the user uses it
+     * frequently for the number that we are calling.
+     */
+    public static final int REASON_FREQUENT = 2;
+
+    /**
+     * Indicates that the {@link PhoneAccountHandle} is suggested because the user explicitly
+     * specified that it be used for the number we are calling.
+     */
+    public static final int REASON_USER_SET = 3;
+
+    /**
+     * Indicates that the {@link PhoneAccountHandle} is suggested for a reason not otherwise
+     * enumerated here.
+     */
+    public static final int REASON_OTHER = 4;
+
+    private PhoneAccountHandle mHandle;
+    private int mReason;
+    private boolean mShouldAutoSelect;
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public PhoneAccountSuggestion(PhoneAccountHandle handle, @SuggestionReason int reason,
+            boolean shouldAutoSelect) {
+        this.mHandle = handle;
+        this.mReason = reason;
+        this.mShouldAutoSelect = shouldAutoSelect;
+    }
+
+    private PhoneAccountSuggestion(Parcel in) {
+        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader());
+        mReason = in.readInt();
+        mShouldAutoSelect = in.readByte() != 0;
+    }
+
+    public static final Creator<PhoneAccountSuggestion> CREATOR =
+            new Creator<PhoneAccountSuggestion>() {
+                @Override
+                public PhoneAccountSuggestion createFromParcel(Parcel in) {
+                    return new PhoneAccountSuggestion(in);
+                }
+
+                @Override
+                public PhoneAccountSuggestion[] newArray(int size) {
+                    return new PhoneAccountSuggestion[size];
+                }
+            };
+
+    /**
+     * @return The {@link PhoneAccountHandle} for this suggestion.
+     */
+    public PhoneAccountHandle getPhoneAccountHandle() {
+        return mHandle;
+    }
+
+    /**
+     * @return The reason for this suggestion
+     */
+    public @SuggestionReason int getReason() {
+        return mReason;
+    }
+
+    /**
+     * Suggests whether the dialer should automatically place the call using this account without
+     * user interaction. This may be set on multiple {@link PhoneAccountSuggestion}s, and the dialer
+     * is free to choose which one to use.
+     * @return {@code true} if the hint is to auto-select, {@code false} otherwise.
+     */
+    public boolean shouldAutoSelect() {
+        return mShouldAutoSelect;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mHandle, flags);
+        dest.writeInt(mReason);
+        dest.writeByte((byte) (mShouldAutoSelect ? 1 : 0));
+    }
+}
diff --git a/tools/hiddenapi/exclude.sh b/tools/hiddenapi/exclude.sh
index 2291e5a..4ffcf68 100755
--- a/tools/hiddenapi/exclude.sh
+++ b/tools/hiddenapi/exclude.sh
@@ -11,6 +11,7 @@
   android.system \
   com.android.bouncycastle \
   com.android.conscrypt \
+  com.android.i18n.phonenumbers \
   com.android.okhttp \
   com.sun \
   dalvik \
diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp
new file mode 100644
index 0000000..ca6b3c4
--- /dev/null
+++ b/tools/processors/view_inspector/Android.bp
@@ -0,0 +1,27 @@
+java_library_host {
+    name: "view-inspector-annotation-processor",
+
+    srcs: ["src/java/**/*.java"],
+    java_resource_dirs: ["src/resources"],
+
+    static_libs: [
+    	"javapoet",
+    ],
+
+    use_tools_jar: true,
+}
+
+java_test_host {
+    name: "view-inspector-annotation-processor-test",
+
+    srcs: ["test/java/**/*.java"],
+    java_resource_dirs: ["test/resources"],
+
+    static_libs: [
+        "guava",
+        "junit",
+        "view-inspector-annotation-processor",
+    ],
+
+    test_suites: ["general-tests"],
+}
diff --git a/tools/processors/view_inspector/TEST_MAPPING b/tools/processors/view_inspector/TEST_MAPPING
new file mode 100644
index 0000000..a91b5b4
--- /dev/null
+++ b/tools/processors/view_inspector/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "view-inspector-annotation-processor-test"
+    }
+  ]
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
new file mode 100644
index 0000000..f157949
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import java.util.Map;
+import java.util.Optional;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Utilities for working with {@link AnnotationMirror}.
+ */
+final class AnnotationUtils {
+    private final Elements mElementUtils;
+    private final Types mTypeUtils;
+
+    AnnotationUtils(ProcessingEnvironment processingEnv) {
+        mElementUtils = processingEnv.getElementUtils();
+        mTypeUtils = processingEnv.getTypeUtils();
+    }
+
+    /**
+     * Get a {@link AnnotationMirror} specified by name from an {@link Element}.
+     *
+     * @param qualifiedName The fully qualified name of the annotation to search for
+     * @param element The element to search for annotations on
+     * @return The mirror of the requested annotation
+     * @throws ProcessingException If there is not exactly one of the requested annotation.
+     */
+    AnnotationMirror exactlyOneMirror(String qualifiedName, Element element) {
+        final Element targetTypeElment = mElementUtils.getTypeElement(qualifiedName);
+        final TypeMirror targetType = targetTypeElment.asType();
+        AnnotationMirror result = null;
+
+        for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
+            final TypeMirror annotationType = annotation.getAnnotationType().asElement().asType();
+            if (mTypeUtils.isSameType(annotationType, targetType)) {
+                if (result == null) {
+                    result = annotation;
+                } else {
+                    final String message = String.format(
+                            "Element had multiple instances of @%s, expected exactly one",
+                            targetTypeElment.getSimpleName());
+
+                    throw new ProcessingException(message, element, annotation);
+                }
+            }
+        }
+
+        if (result == null) {
+            final String message = String.format(
+                    "Expected an @%s annotation, found none", targetTypeElment.getSimpleName());
+            throw new ProcessingException(message, element);
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * Extract a string-valued property from an {@link AnnotationMirror}.
+     *
+     * @param propertyName The name of the requested property
+     * @param annotationMirror The mirror to search for the property
+     * @return The String value of the annotation or null
+     */
+    Optional<String> stringProperty(String propertyName, AnnotationMirror annotationMirror) {
+        final AnnotationValue value = valueByName(propertyName, annotationMirror);
+        if (value != null) {
+            return Optional.of((String) value.getValue());
+        } else {
+            return Optional.empty();
+        }
+    }
+
+
+    /**
+     * Extract a {@link AnnotationValue} from a mirror by string property name.
+     *
+     * @param propertyName The name of the property requested property
+     * @param annotationMirror
+     * @return
+     */
+    AnnotationValue valueByName(String propertyName, AnnotationMirror annotationMirror) {
+        final Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap =
+                annotationMirror.getElementValues();
+
+        for (ExecutableElement method : valueMap.keySet()) {
+            if (method.getSimpleName().contentEquals(propertyName)) {
+                return valueMap.get(method);
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
new file mode 100644
index 0000000..f0b0ff6
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import com.squareup.javapoet.ClassName;
+
+import java.util.Optional;
+
+/**
+ * Model of an inspectable class derived from annotations.
+ *
+ * This class does not use any {javax.lang.model} objects to facilitate building models for testing
+ * {@link InspectionCompanionGenerator}.
+ */
+public final class InspectableClassModel {
+    private final ClassName mClassName;
+    private Optional<String> mNodeName = Optional.empty();
+
+    /**
+     * @param className The name of the modeled class
+     */
+    public InspectableClassModel(ClassName className) {
+        mClassName = className;
+    }
+
+    public ClassName getClassName() {
+        return mClassName;
+    }
+
+    public Optional<String> getNodeName() {
+        return mNodeName;
+    }
+
+    public void setNodeName(Optional<String> nodeName) {
+        mNodeName = nodeName;
+    }
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java
new file mode 100644
index 0000000..a186a82
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import java.util.Optional;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+
+/**
+ * Process {InspectableNodeName} annotations
+ *
+ * @see android.view.inspector.InspectableNodeName
+ */
+public final class InspectableNodeNameProcessor implements ModelProcessor {
+    private final String mQualifiedName;
+    private final ProcessingEnvironment mProcessingEnv;
+    private final AnnotationUtils mAnnotationUtils;
+
+    /**
+     * @param annotationQualifiedName The qualified name of the annotation to process
+     * @param processingEnv The processing environment from the parent processor
+     */
+    public InspectableNodeNameProcessor(
+            String annotationQualifiedName,
+            ProcessingEnvironment processingEnv) {
+        mQualifiedName = annotationQualifiedName;
+        mProcessingEnv = processingEnv;
+        mAnnotationUtils = new AnnotationUtils(processingEnv);
+    }
+
+    /**
+     * Set the node name on the model if one is supplied.
+     *
+     * If the model already has a different node name, the node name will not be updated, and
+     * the processor will print an error the the messager.
+     *
+     * @param element The annotated element to operate on
+     * @param model The model this element should be merged into
+     */
+    @Override
+    public void process(Element element, InspectableClassModel model) {
+        try {
+            final AnnotationMirror mirror =
+                    mAnnotationUtils.exactlyOneMirror(mQualifiedName, element);
+            final Optional<String> nodeName = mAnnotationUtils.stringProperty("value", mirror);
+
+            if (!model.getNodeName().isPresent() || model.getNodeName().equals(nodeName)) {
+                model.setNodeName(nodeName);
+            } else {
+                final String message = String.format(
+                        "Node name was already set to \"%s\", refusing to change it to \"%s\".",
+                        model.getNodeName().get(),
+                        nodeName);
+                throw new ProcessingException(message, element, mirror);
+            }
+        } catch (ProcessingException processingException) {
+            processingException.print(mProcessingEnv.getMessager());
+        }
+    }
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
new file mode 100644
index 0000000..fe0153d
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeSpec;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Modifier;
+
+/**
+ * Generates a source file defining a {@link android.view.inspector.InspectionCompanion}.
+ */
+public final class InspectionCompanionGenerator {
+    private final Filer mFiler;
+    private final Class mRequestingClass;
+
+    /**
+     * @param filer A filer to write the generated source to
+     * @param requestingClass A class object representing the class that invoked the generator
+     */
+    public InspectionCompanionGenerator(final Filer filer, final Class requestingClass) {
+        mFiler = filer;
+        mRequestingClass = requestingClass;
+    }
+
+    /**
+     * Generate and write an inspection companion.
+     *
+     * @param model The model to generated
+     * @throws IOException From the Filer
+     */
+    public void generate(InspectableClassModel model) throws IOException {
+        generateFile(model).writeTo(mFiler);
+    }
+
+    /**
+     * Generate a {@link JavaFile} from a model.
+     *
+     * This is package-public for testing.
+     *
+     * @param model The model to generate from
+     * @return A generated file of an {@link android.view.inspector.InspectionCompanion}
+     */
+    JavaFile generateFile(InspectableClassModel model) {
+        return JavaFile
+                .builder(model.getClassName().packageName(), generateTypeSpec(model))
+                .indent("    ")
+                .build();
+    }
+
+    /**
+     * Generate a {@link TypeSpec} for the {@link android.view.inspector.InspectionCompanion}
+     * for the supplied model.
+     *
+     * @param model The model to generate from
+     * @return A TypeSpec of the inspection companion
+     */
+    private TypeSpec generateTypeSpec(InspectableClassModel model) {
+        TypeSpec.Builder builder = TypeSpec
+                .classBuilder(generateClassName(model))
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addSuperinterface(ParameterizedTypeName.get(
+                        ClassName.get("android.view.inspector", "InspectionCompanion"),
+                        model.getClassName()))
+                .addJavadoc("Inspection companion for {@link $T}.\n\n", model.getClassName())
+                .addJavadoc("Generated by {@link $T}\n", getClass())
+                .addJavadoc("on behalf of {@link $T}.\n", mRequestingClass)
+                .addMethod(generateMapProperties(model))
+                .addMethod(generateReadProperties(model));
+
+        generateGetNodeName(model).ifPresent(builder::addMethod);
+
+        return builder.build();
+    }
+
+    /**
+     * Generate a method definition for
+     * {@link android.view.inspector.InspectionCompanion#getNodeName()}, if needed.
+     *
+     * If {@link InspectableClassModel#getNodeName()} is empty, This method returns an empty
+     * optional, otherwise, it generates a simple method that returns the string value of the
+     * node name.
+     *
+     * @param model The model to generate from
+     * @return The method definition or an empty Optional
+     */
+    private Optional<MethodSpec> generateGetNodeName(InspectableClassModel model) {
+        return model.getNodeName().map(nodeName -> MethodSpec.methodBuilder("getNodeName")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .returns(String.class)
+                .addStatement("return $S", nodeName)
+                .build());
+    }
+
+    /**
+     * Generate a method definition for
+     * {@link android.view.inspector.InspectionCompanion#mapProperties(
+     * android.view.inspector.PropertyMapper)}.
+     *
+     * TODO: implement
+     *
+     * @param model The model to generate from
+     * @return The method definition
+     */
+    private MethodSpec generateMapProperties(InspectableClassModel model) {
+        final ClassName propertyMapper = ClassName.get(
+                "android.view.inspector", "PropertyMapper");
+
+        return MethodSpec.methodBuilder("mapProperties")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .addParameter(propertyMapper, "propertyMapper")
+                // TODO: add method body
+                .build();
+    }
+
+    /**
+     * Generate a method definition for
+     * {@link android.view.inspector.InspectionCompanion#readProperties(
+     * Object, android.view.inspector.PropertyReader)}.
+     *
+     * TODO: implement
+     *
+     * @param model The model to generate from
+     * @return The method definition
+     */
+    private MethodSpec generateReadProperties(InspectableClassModel model) {
+        final ClassName propertyReader = ClassName.get(
+                "android.view.inspector", "PropertyReader");
+
+        return MethodSpec.methodBuilder("readProperties")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .addParameter(model.getClassName(), "inspectable")
+                .addParameter(propertyReader, "propertyReader")
+                // TODO: add method body
+                .build();
+    }
+
+    /**
+     * Generate the final class name for the inspection companion from the model's class name.
+     *
+     * The generated class is added to the same package as the source class. If the class in the
+     * model is a nested class, the nested class names are joined with {"$"}. The suffix
+     * {"$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled class
+     * {com.example.Outer.Inner}, the generated class name will be
+     * {com.example.Outer$Inner$$InspectionCompanion}.
+     *
+     * @param model The model to generate from
+     * @return A class name for the generated inspection companion class
+     */
+    private ClassName generateClassName(final InspectableClassModel model) {
+        final ClassName className = model.getClassName();
+
+        return ClassName.get(
+                className.packageName(),
+                String.join("$", className.simpleNames()) + "$$InspectionCompanion");
+    }
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java
new file mode 100644
index 0000000..3ffcff8
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import javax.lang.model.element.Element;
+
+/**
+ * An interface for annotation processors that operate on a single element and a class model.
+ */
+public interface ModelProcessor {
+    /**
+     * Process the supplied element, mutating the model as needed.
+     *
+     * @param element The annotated element to operate on
+     * @param model The model this element should be merged into
+     */
+    void process(Element element, InspectableClassModel model);
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
new file mode 100644
index 0000000..e531b67
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.squareup.javapoet.ClassName;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+
+
+/**
+ * An annotation processor for the platform inspectable annotations.
+ *
+ * It mostly delegates to {@link ModelProcessor} and {@link InspectionCompanionGenerator}. This
+ * modular architecture allows the core generation code to be reused for comparable annotations
+ * outside the platform, such as in AndroidX.
+ *
+ * @see android.view.inspector.InspectableNodeName
+ * @see android.view.inspector.InspectableProperty
+ */
+@SupportedAnnotationTypes({
+        PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME
+})
+public final class PlatformInspectableProcessor extends AbstractProcessor {
+    static final String NODE_NAME_QUALIFIED_NAME =
+            "android.view.inspector.InspectableNodeName";
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latest();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        final Map<String, InspectableClassModel> modelMap = new HashMap<>();
+
+        for (TypeElement annotation : annotations) {
+            if (annotation.getQualifiedName().contentEquals(NODE_NAME_QUALIFIED_NAME)) {
+                runModelProcessor(
+                        roundEnv.getElementsAnnotatedWith(annotation),
+                        new InspectableNodeNameProcessor(NODE_NAME_QUALIFIED_NAME, processingEnv),
+                        modelMap);
+
+
+            } else {
+                fail("Unexpected annotation type", annotation);
+            }
+        }
+
+        final InspectionCompanionGenerator generator =
+                new InspectionCompanionGenerator(processingEnv.getFiler(), getClass());
+
+        for (InspectableClassModel model : modelMap.values()) {
+            try {
+                generator.generate(model);
+            } catch (IOException ioException) {
+                fail(String.format(
+                        "Unable to generate inspection companion for %s due to %s",
+                        model.getClassName().toString(),
+                        ioException.getMessage()));
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Run a {@link ModelProcessor} for a set of elements
+     *
+     * @param elements Elements to process, should be annotated correctly
+     * @param processor The processor to use
+     * @param modelMap A map of qualified class names to models
+     */
+    private void runModelProcessor(
+            Set<? extends Element> elements,
+            ModelProcessor processor,
+            Map<String, InspectableClassModel> modelMap) {
+        for (Element element : elements) {
+            final Optional<TypeElement> classElement = enclosingClassElement(element);
+
+            if (!classElement.isPresent()) {
+                fail("Element not contained in a class", element);
+                break;
+            }
+
+            final InspectableClassModel model = modelMap.computeIfAbsent(
+                    classElement.get().getQualifiedName().toString(),
+                    k -> new InspectableClassModel(ClassName.get(classElement.get())));
+
+            processor.process(element, model);
+        }
+    }
+
+    /**
+     * Get the nearest enclosing class if there is one.
+     *
+     * If {@param element} represents a class, it will be returned wrapped in an optional.
+     *
+     * @param element An element to search from
+     * @return A TypeElement of the nearest enclosing class or an empty optional
+     */
+    private static Optional<TypeElement> enclosingClassElement(Element element) {
+        Element cursor = element;
+
+        while (cursor != null) {
+            if (cursor.getKind() == ElementKind.CLASS) {
+                return Optional.of((TypeElement) cursor);
+            }
+
+            cursor = cursor.getEnclosingElement();
+        }
+
+        return Optional.empty();
+    }
+
+    /**
+     * Print message and fail the build.
+     *
+     * @param message Message to print
+     */
+    private void fail(String message) {
+        processingEnv.getMessager().printMessage(ERROR, message);
+    }
+
+    /**
+     * Print message and fail the build.
+     *
+     * @param message Message to print
+     * @param element The element that failed
+     */
+    private void fail(String message, Element element) {
+        processingEnv.getMessager().printMessage(ERROR, message, element);
+    }
+}
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java
new file mode 100644
index 0000000..6360e0a
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+
+/**
+ * Internal exception used to signal an error processing an annotation.
+ */
+final class ProcessingException extends RuntimeException {
+    private final Element mElement;
+    private final AnnotationMirror mAnnotationMirror;
+    private final AnnotationValue mAnnotationValue;
+
+    ProcessingException(String message) {
+        this(message, null, null, null);
+    }
+
+    ProcessingException(String message, Element element) {
+        this(message, element, null, null);
+    }
+
+    ProcessingException(String message, Element element, AnnotationMirror annotationMirror) {
+        this(message, element, annotationMirror, null);
+    }
+
+    ProcessingException(
+            String message,
+            Element element,
+            AnnotationMirror annotationMirror,
+            AnnotationValue annotationValue) {
+        super(message);
+        mElement = element;
+        mAnnotationMirror = annotationMirror;
+        mAnnotationValue = annotationValue;
+    }
+
+    /**
+     * Prints the exception to a Messager.
+     *
+     * @param messager A Messager to print to
+     */
+    void print(Messager messager) {
+        if (mElement != null) {
+            if (mAnnotationMirror != null) {
+                if (mAnnotationValue != null) {
+                    messager.printMessage(
+                            ERROR, getMessage(), mElement, mAnnotationMirror, mAnnotationValue);
+                } else {
+                    messager.printMessage(ERROR, getMessage(), mElement, mAnnotationMirror);
+                }
+            } else {
+                messager.printMessage(ERROR, getMessage(), mElement);
+            }
+        } else {
+            messager.printMessage(ERROR, getMessage());
+        }
+    }
+}
diff --git a/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..fa4f71f
--- /dev/null
+++ b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.processor.inspector.view.PlatformInspectableProcessor
diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
new file mode 100644
index 0000000..c02b0bd
--- /dev/null
+++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.processor.view.inspector;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import com.squareup.javapoet.ClassName;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Optional;
+
+/**
+ * Tests for {@link InspectionCompanionGenerator}
+ */
+public class InspectionCompanionGeneratorTest {
+    private static final String RESOURCE_PATH_TEMPLATE =
+            "android/processor/view/inspector/InspectionCompanionGeneratorTest/%s.java.txt";
+    private static final ClassName TEST_CLASS_NAME =
+            ClassName.get("com.android.inspectable", "TestInspectable");
+    private InspectableClassModel mModel;
+    private InspectionCompanionGenerator mGenerator;
+
+    @Before
+    public void setup() {
+        mModel = new InspectableClassModel(TEST_CLASS_NAME);
+        mGenerator = new InspectionCompanionGenerator(null, getClass());
+    }
+
+    @Test
+    public void testNodeName() {
+        mModel.setNodeName(Optional.of("NodeName"));
+        assertGeneratedFileEquals("NodeName");
+    }
+
+    @Test
+    public void testNestedClass() {
+        mModel = new InspectableClassModel(
+                ClassName.get("com.android.inspectable", "Outer", "Inner"));
+        assertGeneratedFileEquals("NestedClass");
+    }
+
+    private void assertGeneratedFileEquals(String fileName) {
+        assertEquals(
+                loadTextResource(String.format(RESOURCE_PATH_TEMPLATE, fileName)),
+                mGenerator.generateFile(mModel).toString());
+    }
+
+    private String loadTextResource(String path) {
+        try {
+            final URL url = Resources.getResource(path);
+            assertNotNull(String.format("Resource file not found: %s", path), url);
+            return Resources.toString(url, Charsets.UTF_8);
+        } catch (IOException e) {
+            fail(e.getMessage());
+            return null;
+        }
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
new file mode 100644
index 0000000..e5fb6a2
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
@@ -0,0 +1,22 @@
+package com.android.inspectable;
+
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link Outer.Inner}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class Outer$Inner$$InspectionCompanion implements InspectionCompanion<Outer.Inner> {
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+    }
+
+    @Override
+    public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) {
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
new file mode 100644
index 0000000..a334f50
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
@@ -0,0 +1,28 @@
+package com.android.inspectable;
+
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Inspection companion for {@link TestInspectable}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+    }
+
+    @Override
+    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    }
+
+    @Override
+    public String getNodeName() {
+        return "NodeName";
+    }
+}