Merge changes from topics "media_metrics_network", "media_metrics_pb_metrics", "media_metrics_track_change" into sc-dev

* changes:
  Unhide media metrics APIs part 2: playback metrics
  Unhide media metrics APIs part 2: Network event
  Unhide media metrics APIs part 2: Track change event
diff --git a/api/Android.bp b/api/Android.bp
index ac2f083..5466bd2 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -64,6 +64,22 @@
 }
 
 genrule {
+    name: "frameworks-base-api-current-compat",
+    srcs: [
+        ":android.api.public.latest",
+        ":android-incompatibilities.api.public.latest",
+        ":frameworks-base-api-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.public.latest) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
+        "$(location :frameworks-base-api-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-current.srcjar",
     srcs: [
         ":android.net.ipsec.ike{.public.stubs.source}",
@@ -162,6 +178,24 @@
 }
 
 genrule {
+    name: "frameworks-base-api-system-current-compat",
+    srcs: [
+        ":android.api.system.latest",
+        ":android-incompatibilities.api.system.latest",
+        ":frameworks-base-api-current.txt",
+        ":frameworks-base-api-system-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.system.latest) " +
+        "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
+        "$(location :frameworks-base-api-system-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-system-removed.txt",
     srcs: [
         ":android.net.ipsec.ike{.system.removed-api.txt}",
@@ -231,6 +265,27 @@
 }
 
 genrule {
+    name: "frameworks-base-api-module-lib-current-compat",
+    srcs: [
+        ":android.api.module-lib.latest",
+        ":android-incompatibilities.api.module-lib.latest",
+        ":frameworks-base-api-current.txt",
+        ":frameworks-base-api-module-lib-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+        // Note: having "public" be the base of module-lib is not perfect -- it should
+        // ideally be a merged public+system), but this will  help when migrating from
+        // MODULE_LIBS -> public.
+        "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
+        "$(location :frameworks-base-api-module-lib-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-module-lib-removed.txt",
     srcs: [
         ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 3bad889..5d64579 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -26,10 +26,24 @@
     tidy_checks_as_errors: [
         "modernize-*",
         "-modernize-avoid-c-arrays",
+        "-modernize-pass-by-value",
+        "-modernize-replace-disallow-copy-and-assign-macro",
+        "-modernize-use-equals-default",
+        "-modernize-use-nodiscard",
+        "-modernize-use-override",
         "-modernize-use-trailing-return-type",
+        "-modernize-use-using",
         "android-*",
         "misc-*",
+        "-misc-non-private-member-variables-in-classes",
         "readability-*",
+        "-readability-braces-around-statements",
+        "-readability-const-return-type",
+        "-readability-convert-member-functions-to-static",
+        "-readability-else-after-return",
+        "-readability-named-parameter",
+        "-readability-redundant-access-specifiers",
+        "-readability-uppercase-literal-suffix",
     ],
     tidy_flags: [
         "-system-headers",
diff --git a/core/api/current.txt b/core/api/current.txt
index d28b584..e357687 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -960,6 +960,8 @@
     field public static final int maxLines = 16843091; // 0x1010153
     field public static final int maxLongVersionCode = 16844163; // 0x1010583
     field public static final int maxRecents = 16843846; // 0x1010446
+    field public static final int maxResizeHeight = 16844339; // 0x1010633
+    field public static final int maxResizeWidth = 16844338; // 0x1010632
     field public static final int maxRows = 16843059; // 0x1010133
     field public static final int maxSdkVersion = 16843377; // 0x1010271
     field public static final int maxWidth = 16843039; // 0x101011f
@@ -1394,6 +1396,8 @@
     field public static final int tabWidgetStyle = 16842883; // 0x1010083
     field public static final int tag = 16842961; // 0x10100d1
     field public static final int targetActivity = 16843266; // 0x1010202
+    field public static final int targetCellHeight = 16844341; // 0x1010635
+    field public static final int targetCellWidth = 16844340; // 0x1010634
     field public static final int targetClass = 16842799; // 0x101002f
     field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
     field public static final int targetId = 16843740; // 0x10103dc
@@ -6074,6 +6078,13 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+    field public static final String EDIT_CONVERSATION = "convo";
+    field public static final String EDIT_IMPORTANCE = "importance";
+    field public static final String EDIT_LAUNCHER = "launcher";
+    field public static final String EDIT_LOCKED_DEVICE = "locked";
+    field public static final String EDIT_SOUND = "sound";
+    field public static final String EDIT_VIBRATION = "vibration";
+    field public static final String EDIT_ZEN = "dnd";
   }
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -7093,6 +7104,7 @@
     method public boolean isUniqueDeviceAttestationSupported();
     method public boolean isUsbDataSignalingEnabled();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
+    method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
     method public void lockNow();
     method public void lockNow(int);
     method public int logoutUser(@NonNull android.content.ComponentName);
@@ -8421,6 +8433,8 @@
     field public int initialKeyguardLayout;
     field public int initialLayout;
     field @Deprecated public String label;
+    field public int maxResizeHeight;
+    field public int maxResizeWidth;
     field public int minHeight;
     field public int minResizeHeight;
     field public int minResizeWidth;
@@ -8429,6 +8443,8 @@
     field @IdRes public int previewLayout;
     field public android.content.ComponentName provider;
     field public int resizeMode;
+    field public int targetCellHeight;
+    field public int targetCellWidth;
     field public int updatePeriodMillis;
     field public int widgetCategory;
     field public int widgetFeatures;
@@ -12191,6 +12207,7 @@
     method public void setAutoRevokePermissionsMode(boolean);
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
+    method public void setInstallScenario(int);
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12413,6 +12430,7 @@
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+    field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
     field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
     field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
     field public static final String FEATURE_LEANBACK = "android.software.leanback";
@@ -12519,6 +12537,10 @@
     field public static final int INSTALL_REASON_POLICY = 1; // 0x1
     field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
     field public static final int INSTALL_REASON_USER = 4; // 0x4
+    field public static final int INSTALL_SCENARIO_BULK = 2; // 0x2
+    field public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; // 0x3
+    field public static final int INSTALL_SCENARIO_DEFAULT = 0; // 0x0
+    field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1
     field public static final int MATCH_ALL = 131072; // 0x20000
     field public static final int MATCH_APEX = 1073741824; // 0x40000000
     field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
@@ -21621,6 +21643,7 @@
     method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
     method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
     method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
+    method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
     method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
     method public static int getMaxSecurityLevel();
     method public int getMaxSessionCount();
@@ -21729,6 +21752,12 @@
     field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5
   }
 
+  public static class MediaDrm.LogMessage {
+    field @NonNull public final String message;
+    field public final int priority;
+    field public final long timestampMillis;
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method @NonNull public String getDiagnosticInfo();
   }
@@ -26924,6 +26953,46 @@
 
 }
 
+package android.net.vcn {
+
+  public final class VcnConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+  }
+
+  public static final class VcnConfig.Builder {
+    ctor public VcnConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+    method @NonNull public android.net.vcn.VcnConfig build();
+  }
+
+  public final class VcnGatewayConnectionConfig {
+    method @NonNull public int[] getExposedCapabilities();
+    method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu();
+    method @NonNull public int[] getRequiredUnderlyingCapabilities();
+    method @NonNull public long[] getRetryInterval();
+  }
+
+  public static final class VcnGatewayConnectionConfig.Builder {
+    ctor public VcnGatewayConnectionConfig.Builder();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]);
+  }
+
+  public class VcnManager {
+    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+  }
+
+}
+
 package android.nfc {
 
   public class FormatException extends java.lang.Exception {
@@ -31716,6 +31785,7 @@
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
+    method public boolean isUserForeground();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
@@ -34931,6 +35001,7 @@
     field public static final String EXTRA_AUTHORITIES = "authorities";
     field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
     field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
+    field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
     field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
@@ -35271,30 +35342,18 @@
 
   public static final class SimPhonebookContract.SimRecords {
     method @NonNull public static android.net.Uri getContentUri(int, int);
+    method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String);
     method @NonNull public static android.net.Uri getItemUri(int, int, int);
-    method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
     field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+    field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff
     field public static final String NAME = "name";
     field public static final String PHONE_NUMBER = "phone_number";
     field public static final String RECORD_NUMBER = "record_number";
     field public static final String SUBSCRIPTION_ID = "subscription_id";
   }
 
-  public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
-    ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
-    method public int describeContents();
-    method public int getEncodedLength();
-    method public int getMaxEncodedLength();
-    method @NonNull public String getName();
-    method @NonNull public String getSanitizedName();
-    method public boolean isSupportedCharacter(int);
-    method public boolean isValid();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
-  }
-
   public class SyncStateContract {
     ctor public SyncStateContract();
   }
@@ -37178,6 +37237,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method @Nullable public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method @Nullable public String getAttestKeyAlias();
     method public byte[] getAttestationChallenge();
     method @NonNull public String[] getBlockModes();
     method @NonNull public java.util.Date getCertificateNotAfter();
@@ -37212,6 +37272,7 @@
     ctor public KeyGenParameterSpec.Builder(@NonNull String, int);
     method @NonNull public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(@NonNull java.security.spec.AlgorithmParameterSpec);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestKeyAlias(@Nullable String);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(@NonNull java.util.Date);
@@ -37309,6 +37370,7 @@
     field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
     field public static final int ORIGIN_UNKNOWN = 4; // 0x4
     field public static final int PURPOSE_AGREE_KEY = 64; // 0x40
+    field public static final int PURPOSE_ATTEST_KEY = 128; // 0x80
     field public static final int PURPOSE_DECRYPT = 2; // 0x2
     field public static final int PURPOSE_ENCRYPT = 1; // 0x1
     field public static final int PURPOSE_SIGN = 4; // 0x4
@@ -39348,16 +39410,22 @@
   }
 
   public static class CallScreeningService.CallResponse {
+    method public int getCallComposerAttachmentsToShow();
     method public boolean getDisallowCall();
     method public boolean getRejectCall();
     method public boolean getSilenceCall();
     method public boolean getSkipCallLog();
     method public boolean getSkipNotification();
+    field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2
+    field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1
+    field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8
+    field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4
   }
 
   public static class CallScreeningService.CallResponse.Builder {
     ctor public CallScreeningService.CallResponse.Builder();
     method public android.telecom.CallScreeningService.CallResponse build();
+    method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int);
     method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean);
     method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean);
@@ -46601,8 +46669,10 @@
     method public long getMetric(int);
     field public static final int ANIMATION_DURATION = 2; // 0x2
     field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+    field public static final int DEADLINE = 13; // 0xd
     field public static final int DRAW_DURATION = 4; // 0x4
     field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field public static final int GPU_DURATION = 12; // 0xc
     field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
     field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
     field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
@@ -54871,6 +54941,7 @@
     method public void setAccessibilityTraversalAfter(@IdRes int, @IdRes int);
     method public void setAccessibilityTraversalBefore(@IdRes int, @IdRes int);
     method public void setBitmap(@IdRes int, String, android.graphics.Bitmap);
+    method public void setBlendMode(@IdRes int, @NonNull String, @Nullable android.graphics.BlendMode);
     method public void setBoolean(@IdRes int, String, boolean);
     method public void setBundle(@IdRes int, String, android.os.Bundle);
     method public void setByte(@IdRes int, String, byte);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b57fdf1..de02d0b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -217,6 +217,7 @@
     method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
     method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
     method public void teardownTestNetwork(@NonNull android.net.Network);
+    field public static final String TEST_TAP_PREFIX = "testtap";
   }
 
   public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7541853..f5366e37 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14,6 +14,7 @@
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
     field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+    field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
@@ -197,6 +198,7 @@
     field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
     field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
+    field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
     field public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
     field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
@@ -271,6 +273,7 @@
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+    field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -343,6 +346,7 @@
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
+    field public static final int config_systemContacts = 17039403; // 0x104002b
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemShell = 17039402; // 0x104002a
   }
@@ -1275,6 +1279,14 @@
 
 }
 
+package android.app.people {
+
+  public final class PeopleManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA) public boolean isConversation(@NonNull String, @NonNull String);
+  }
+
+}
+
 package android.app.prediction {
 
   public final class AppPredictionContext implements android.os.Parcelable {
@@ -8949,6 +8961,16 @@
 
 package android.permission {
 
+  public final class AdminPermissionControlParams implements android.os.Parcelable {
+    method public boolean canAdminGrantSensorsPermissions();
+    method public int describeContents();
+    method public int getGrantState();
+    method @NonNull public String getGranteePackageName();
+    method @NonNull public String getPermission();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.permission.AdminPermissionControlParams> CREATOR;
+  }
+
   public final class PermissionControllerManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
@@ -8980,7 +9002,8 @@
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
-    method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable);
@@ -9451,22 +9474,8 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
   }
 
-  public final class SimPhonebookContract {
-    method @NonNull public static String getEfUriPath(int);
-    field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
-  }
-
-  public static final class SimPhonebookContract.ElementaryFiles {
-    field public static final String EF_ADN_PATH_SEGMENT = "adn";
-    field public static final String EF_FDN_PATH_SEGMENT = "fdn";
-    field public static final String EF_SDN_PATH_SEGMENT = "sdn";
-    field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
-  }
-
   public static final class SimPhonebookContract.SimRecords {
-    field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
     field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
-    field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
   }
 
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
@@ -10760,7 +10769,7 @@
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
@@ -10937,7 +10946,7 @@
   }
 
   public final class RemoteConnection {
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method @Deprecated public void setAudioState(android.telecom.AudioState);
   }
 
@@ -13450,8 +13459,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
     field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
     field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1
@@ -14217,10 +14226,10 @@
   }
 
   public final class RangingSession implements java.lang.AutoCloseable {
-    method public void close();
-    method public void reconfigure(@NonNull android.os.PersistableBundle);
-    method public void start(@NonNull android.os.PersistableBundle);
-    method public void stop();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void close();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void reconfigure(@NonNull android.os.PersistableBundle);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void start(@NonNull android.os.PersistableBundle);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void stop();
   }
 
   public static interface RangingSession.Callback {
@@ -14256,18 +14265,18 @@
   }
 
   public final class UwbManager {
-    method public long elapsedRealtimeResolutionNanos();
-    method public int getAngleOfArrivalSupport();
-    method public int getMaxRemoteDevicesPerInitiatorSession();
-    method public int getMaxRemoteDevicesPerResponderSession();
-    method public int getMaxSimultaneousSessions();
-    method @NonNull public android.os.PersistableBundle getSpecificationInfo();
-    method @NonNull public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
-    method @NonNull public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
-    method public boolean isRangingSupported();
-    method @NonNull public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
-    method public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
-    method public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getAngleOfArrivalSupport();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerInitiatorSession();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerResponderSession();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxSimultaneousSessions();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.PersistableBundle getSpecificationInfo();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public boolean isRangingSupported();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; // 0x2
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; // 0x3
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; // 0x4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 989025c..ff96f92 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2556,6 +2556,7 @@
 
   public final class InputMethodManager {
     method public int getDisplayId();
+    method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method public boolean isInputMethodPickerShown();
   }
 
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 5402381..101917b 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -17,11 +17,9 @@
 package android.app;
 
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.app.IWallpaperManagerCallback;
-import android.app.ILocalWallpaperColorConsumer;
 import android.app.WallpaperInfo;
 import android.content.ComponentName;
 import android.app.WallpaperColors;
@@ -164,18 +162,6 @@
     WallpaperColors getWallpaperColors(int which, int userId, int displayId);
 
     /**
-    * @hide
-    */
-    void removeOnLocalColorsChangedListener(
-            in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
-
-    /**
-    * @hide
-    */
-    void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
-                                    in List<RectF> regions, int which, int userId, int displayId);
-
-    /**
      * Register a callback to receive color updates from a display
      */
     void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 323af821..685c222 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -80,6 +80,48 @@
     public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
 
     /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing sound, like a tone picker
+     * ({@link #setSound(Uri, AudioAttributes)}).
+     */
+    public static final String EDIT_SOUND = "sound";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing vibration ({@link #enableVibration(boolean)},
+     * {@link #setVibrationPattern(long[])}).
+     */
+    public static final String EDIT_VIBRATION = "vibration";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing importance ({@link #setImportance(int)}) and/or conversation
+     * priority.
+     */
+    public static final String EDIT_IMPORTANCE = "importance";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing behavior on devices that are locked or have a turned off
+     * display ({@link #setLockscreenVisibility(int)}, {@link #enableLights(boolean)},
+     * {@link #setLightColor(int)}).
+     */
+    public static final String EDIT_LOCKED_DEVICE = "locked";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
+     */
+    public static final String EDIT_ZEN = "dnd";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing conversation settings (demoting or restoring a channel to
+     * be a Conversation, changing bubble behavior, or setting the priority of a conversation).
+     */
+    public static final String EDIT_CONVERSATION = "convo";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing launcher behavior (showing badges)}.
+     */
+    public static final String EDIT_LAUNCHER = "launcher";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index d7587bd..ac2f223 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -66,7 +66,6 @@
 import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
@@ -108,8 +107,6 @@
     private static boolean DEBUG = false;
     private float mWallpaperXStep = -1;
     private float mWallpaperYStep = -1;
-    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
-            new RectF(0, 0, 1, 1);
 
     /** {@hide} */
     private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -312,8 +309,6 @@
         private int mCachedWallpaperUserId;
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
-        private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
-                mLocalColorCallbacks = new ArrayMap<>();
 
         Globals(IWallpaperManager service, Looper looper) {
             mService = service;
@@ -355,40 +350,6 @@
             }
         }
 
-        private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
-            ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
-                @Override
-                public void onColorsChanged(RectF area, WallpaperColors colors) {
-                    callback.onColorsChanged(area, colors);
-                }
-            };
-            mLocalColorCallbacks.put(callback, callback2);
-            return callback2;
-        }
-
-        public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
-                @NonNull List<RectF> regions, int which, int userId, int displayId) {
-            try {
-                mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
-                                                         userId, displayId);
-            } catch (RemoteException e) {
-                // Can't get colors, connection lost.
-            }
-        }
-
-        public void removeOnColorsChangedListener(
-                @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
-                int displayId) {
-            ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
-            if (callback2 == null) return;
-            try {
-                mService.removeOnLocalColorsChangedListener(
-                        callback2, which, userId, displayId);
-            } catch (RemoteException e) {
-                // Can't get colors, connection lost.
-            }
-        }
-
         /**
          * Stop listening to wallpaper color events.
          *
@@ -1096,29 +1057,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
-            List<RectF> regions) throws IllegalArgumentException {
-        for (RectF region : regions) {
-            if (!LOCAL_COLOR_BOUNDS.contains(region)) {
-                throw new IllegalArgumentException("Regions must be within bounds "
-                        + LOCAL_COLOR_BOUNDS);
-            }
-        }
-        sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
-                                                 mContext.getUserId(), mContext.getDisplayId());
-    }
-
-    /**
-     * @hide
-     */
-    public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
-        sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
-                mContext.getDisplayId());
-    }
-
-    /**
      * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
      * for a given user.  The caller must hold the INTERACT_ACROSS_USERS_FULL
      * permission to access another user's wallpaper data.
@@ -2264,18 +2202,4 @@
             onColorsChanged(colors, which);
         }
     }
-
-    /**
-     * Callback to update a consumer with a local color change
-     * @hide
-     */
-    public interface LocalWallpaperColorConsumer {
-
-        /**
-         * Gets called when a color of an area gets updated
-         * @param area
-         * @param colors
-         */
-        void onColorsChanged(RectF area, WallpaperColors colors);
-    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 05e9dcf..305c224 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1869,13 +1869,13 @@
     /**
      * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
      * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
-     * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
-     * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+     * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner or Profile Owner
+     * will no longer receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
      * There can be at most one app that has this delegation.
      * If another app already had delegated network logging access,
      * it will lose the delegation when a new app is delegated.
      *
-     * <p> Can only be granted by Device Owner.
+     * <p> Can only be granted by Device Owner or Profile Owner of a managed profile.
      */
     public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
 
@@ -13357,6 +13357,7 @@
             }
         }
     }
+
     /**
      * Returns true if the caller is running on a device where the admin can grant
      * permissions related to device sensors.
@@ -13459,4 +13460,22 @@
         }
         return false;
     }
+
+    /**
+     * Gets the list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @return list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @throws SecurityException if the calling application is not a device owner
+     */
+    @NonNull
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        if (mService == null) return Collections.emptyList();
+
+        try {
+            return mService.listForegroundAffiliatedUsers();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 94388cf..91a9f3c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -507,4 +507,6 @@
     boolean isUsbDataSignalingEnabled(String callerPackage);
     boolean isUsbDataSignalingEnabledForUser(int userId);
     boolean canUsbDataSignalingBeDisabled();
+
+    List<UserHandle> listForegroundAffiliatedUsers();
 }
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index ebe9f60..d000f3b 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -49,6 +49,9 @@
     /** Removes all the recent conversations and uncaches their cached shortcuts. */
     void removeAllRecentConversations();
 
+    /** Returns whether the shortcutId is backed by a Conversation in People Service. */
+    boolean isConversation(in String packageName, int userId, in String shortcutId);
+
     /**
      * Returns the last interaction with the specified conversation. If the
      * conversation can't be found or no interactions have been recorded, returns 0L.
diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java
index de7ba62..d348edb 100644
--- a/core/java/android/app/people/PeopleManager.java
+++ b/core/java/android/app/people/PeopleManager.java
@@ -17,6 +17,8 @@
 package android.app.people;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
@@ -53,6 +55,33 @@
                 Context.PEOPLE_SERVICE));
     }
 
+    /**
+     * Returns whether a shortcut has a conversation associated.
+     *
+     * <p>Requires android.permission.READ_PEOPLE_DATA permission.
+     *
+     * <p>This method may return different results for the same shortcut over time, as an app adopts
+     * conversation features or if a user hasn't communicated with the conversation associated to
+     * the shortcut in a while, so the result should not be stored and relied on indefinitely by
+     * clients.
+     *
+     * @param packageName name of the package the conversation is part of
+     * @param shortcutId the shortcut id backing the conversation
+     * @return whether the {@shortcutId} is backed by a Conversation.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA)
+    public boolean isConversation(@NonNull String packageName, @NonNull String shortcutId) {
+        Preconditions.checkStringNotEmpty(packageName);
+        Preconditions.checkStringNotEmpty(shortcutId);
+        try {
+            return mService.isConversation(packageName, mContext.getUserId(), shortcutId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Sets or updates a {@link ConversationStatus} for a conversation.
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
index 439d851..f17b044 100644
--- a/core/java/android/app/smartspace/SmartspaceAction.java
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -89,7 +89,7 @@
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
         mIntent = in.readTypedObject(Intent.CREATOR);
         mUserHandle = in.readTypedObject(UserHandle.CREATOR);
-        mExtras = in.readTypedObject(Bundle.CREATOR);
+        mExtras = in.readBundle();
     }
 
     private SmartspaceAction(
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 42214d0..d893a5e 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -179,6 +179,44 @@
     public int minResizeHeight;
 
     /**
+     * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+     * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
+     *
+     * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int maxResizeWidth;
+
+    /**
+     * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+     * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
+     *
+     * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int maxResizeHeight;
+
+    /**
+     * The default width of a widget when added to a host, in units of launcher grid cells.
+     *
+     * <p>This field corresponds to the <code>android:targetCellWidth</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int targetCellWidth;
+
+    /**
+     * The default height of a widget when added to a host, in units of launcher grid cells.
+     *
+     * <p>This field corresponds to the <code>android:targetCellHeight</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int targetCellHeight;
+
+    /**
      * How often, in milliseconds, that this AppWidget wants to be updated.
      * The AppWidget manager may place a limit on how often a AppWidget is updated.
      *
@@ -330,6 +368,10 @@
         this.minHeight = in.readInt();
         this.minResizeWidth = in.readInt();
         this.minResizeHeight = in.readInt();
+        this.maxResizeWidth = in.readInt();
+        this.maxResizeHeight = in.readInt();
+        this.targetCellWidth = in.readInt();
+        this.targetCellHeight = in.readInt();
         this.updatePeriodMillis = in.readInt();
         this.initialLayout = in.readInt();
         this.initialKeyguardLayout = in.readInt();
@@ -440,6 +482,10 @@
         out.writeInt(this.minHeight);
         out.writeInt(this.minResizeWidth);
         out.writeInt(this.minResizeHeight);
+        out.writeInt(this.maxResizeWidth);
+        out.writeInt(this.maxResizeHeight);
+        out.writeInt(this.targetCellWidth);
+        out.writeInt(this.targetCellHeight);
         out.writeInt(this.updatePeriodMillis);
         out.writeInt(this.initialLayout);
         out.writeInt(this.initialKeyguardLayout);
@@ -463,8 +509,12 @@
         that.provider = this.provider == null ? null : this.provider.clone();
         that.minWidth = this.minWidth;
         that.minHeight = this.minHeight;
-        that.minResizeWidth = this.minResizeHeight;
+        that.minResizeWidth = this.minResizeWidth;
         that.minResizeHeight = this.minResizeHeight;
+        that.maxResizeWidth = this.maxResizeWidth;
+        that.maxResizeHeight = this.maxResizeHeight;
+        that.targetCellWidth = this.targetCellWidth;
+        that.targetCellHeight = this.targetCellHeight;
         that.updatePeriodMillis = this.updatePeriodMillis;
         that.initialLayout = this.initialLayout;
         that.initialKeyguardLayout = this.initialKeyguardLayout;
@@ -512,6 +562,8 @@
         minHeight = TypedValue.complexToDimensionPixelSize(minHeight, displayMetrics);
         minResizeWidth = TypedValue.complexToDimensionPixelSize(minResizeWidth, displayMetrics);
         minResizeHeight = TypedValue.complexToDimensionPixelSize(minResizeHeight, displayMetrics);
+        maxResizeWidth = TypedValue.complexToDimensionPixelSize(maxResizeWidth, displayMetrics);
+        maxResizeHeight = TypedValue.complexToDimensionPixelSize(maxResizeHeight, displayMetrics);
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5166943..10b00f2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4583,6 +4583,14 @@
     public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
 
     /**
+     * Official published name of the (internal) text to speech manager service.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+    /**
      * Official published name of the content capture service.
      *
      * @hide
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0c0e402..80fecc1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -167,10 +167,17 @@
      */
     public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
 
+    /**
+     * Cache shortcuts which are used in People Tile.
+     * @hide
+     */
+    public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
+
     /** @hide */
     @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
             FLAG_CACHE_NOTIFICATION_SHORTCUTS,
             FLAG_CACHE_BUBBLE_SHORTCUTS,
+            FLAG_CACHE_PEOPLE_TILE_SHORTCUTS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutCacheFlags {}
@@ -1179,6 +1186,7 @@
      * <ul>
      *     <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
      *     <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+     *     <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
      * </ul>
      * @throws IllegalStateException when the user is locked, or when the {@code user} user
      * is locked or not running.
@@ -1209,6 +1217,7 @@
      * <ul>
      *     <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
      *     <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+     *     <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
      * </ul>
      * @throws IllegalStateException when the user is locked, or when the {@code user} user
      * is locked or not running.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f9980bc..567501c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2065,6 +2065,13 @@
             this.forceQueryableOverride = true;
         }
 
+        /**
+         * Sets the install scenario for this session, which describes the expected user journey.
+         */
+        public void setInstallScenario(@InstallScenario int installScenario) {
+            this.installScenario = installScenario;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -2224,7 +2231,7 @@
         /** {@hide} */
         public @InstallReason int installReason;
         /** {@hide} */
-        public @InstallReason int installScenario;
+        public @InstallScenario int installScenario;
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long sizeBytes;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a3c3500..fdb00c6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1356,15 +1356,11 @@
 
     /**
      * A value to indicate the lack of CUJ information, disabling all installation scenario logic.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_DEFAULT = 0;
 
     /**
      * Installation scenario providing the fastest “install button to launch" experience possible.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_FAST = 1;
 
@@ -1381,8 +1377,6 @@
      * less optimized applications.  The device state (e.g. memory usage or battery status) should
      * not be considered when making this decision as those factors are taken into account by the
      * Package Manager when acting on the installation scenario.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK = 2;
 
@@ -1393,8 +1387,6 @@
      * operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be.
      *
      * See the comments for INSTALL_SCENARIO_BULK for more information.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3;
 
@@ -3677,6 +3669,15 @@
     public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
             "android.hardware.keystore.limited_use_key";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * a Keystore implementation that can create application-specific attestation keys.
+     * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+            "android.hardware.keystore.app_attest_key";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ce0547f..5f80ba1 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -129,6 +129,12 @@
     /** @hide */
     public static final int FLAG_HAS_ICON_URI = 1 << 15;
 
+    /**
+     * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+     *  need to be aware of the outside world. Replace this with a more extensible solution.
+     * @hide
+     */
+    public static final int FLAG_CACHED_PEOPLE_TILE = 1 << 29;
 
     /**
      * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
@@ -138,7 +144,8 @@
     public static final int FLAG_CACHED_BUBBLES = 1 << 30;
 
     /** @hide */
-    public static final int FLAG_CACHED_ALL = FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES;
+    public static final int FLAG_CACHED_ALL =
+            FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
 
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -159,6 +166,7 @@
             FLAG_HAS_ICON_URI,
             FLAG_CACHED_NOTIFICATIONS,
             FLAG_CACHED_BUBBLES,
+            FLAG_CACHED_PEOPLE_TILE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutFlags {}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ebb3021..a65f36b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -849,9 +850,18 @@
             attributionTag = context.getAttributionTag();
         }
 
+        // Workaround for old APIs not providing a context
+        String packageName;
+        if (context != null) {
+            packageName = context.getPackageName();
+        } else {
+            packageName = ActivityThread.currentPackageName();
+        }
+
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
+            clientProxy = mService.createClient(
+                    hubInfo.getId(), clientInterface, attributionTag, packageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 4961195..92882c4 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -60,7 +60,8 @@
 
     // Creates a client to send and receive messages
     IContextHubClient createClient(
-            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
+            int contextHubId, in IContextHubClientCallback client, in String attributionTag,
+            in String packageName);
 
     // Creates a PendingIntent-based client to send and receive messages
     IContextHubClient createPendingIntentClient(
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 4f293ee..6a3cb42 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -33,4 +34,7 @@
     void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);
+
+    void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName);
+    void unregisterVcnStatusCallback(in IVcnStatusCallback callback);
 }
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
similarity index 60%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/net/vcn/IVcnStatusCallback.aidl
index 28b11ec..a7386718 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2021, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,9 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.net.vcn;
 
-import android.app.WallpaperColors;
-import android.graphics.RectF;
-
-/**
- * @hide
- */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
+/** @hide */
+interface IVcnStatusCallback {
+    void onEnteredSafeMode();
 }
\ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 5eb4ba6..52cc218 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -41,7 +42,6 @@
  * brought up on demand based on active {@link NetworkRequest}(s).
  *
  * @see VcnManager for more information on the Virtual Carrier Network feature
- * @hide
  */
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
@@ -56,7 +56,8 @@
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
         mPackageName = packageName;
-        mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
+        mGatewayConnectionConfigs =
+                Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
 
         validate();
     }
@@ -96,11 +97,7 @@
         return mPackageName;
     }
 
-    /**
-     * Retrieves the set of configured tunnels.
-     *
-     * @hide
-     */
+    /** Retrieves the set of configured GatewayConnection(s). */
     @NonNull
     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -168,11 +165,7 @@
                 }
             };
 
-    /**
-     * This class is used to incrementally build {@link VcnConfig} objects.
-     *
-     * @hide
-     */
+    /** This class is used to incrementally build {@link VcnConfig} objects. */
     public static final class Builder {
         @NonNull private final String mPackageName;
 
@@ -190,7 +183,6 @@
          *
          * @param gatewayConnectionConfig the configuration for an individual gateway connection
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder addGatewayConnectionConfig(
@@ -205,7 +197,6 @@
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
-         * @hide
          */
         @NonNull
         public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index cead2f1..40aa518 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -21,6 +21,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
@@ -55,28 +57,23 @@
  * subscription group under which this configuration is registered (see {@link
  * VcnManager#setVcnConfig}).
  *
- * <p>Services that can be provided by a VCN network, or required for underlying networks are
- * limited to services provided by cellular networks:
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or
+ * required for underlying networks are limited to services provided by cellular networks:
  *
  * <ul>
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
  * </ul>
- *
- * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the
- * underlying Network(s).
- *
- * @hide
  */
 public final class VcnGatewayConnectionConfig {
     // TODO: Use MIN_MTU_V6 once it is public, @hide
@@ -249,8 +246,7 @@
      * ascending numerical order.
      *
      * @see Builder#addExposedCapability(int)
-     * @see Builder#clearExposedCapability(int)
-     * @hide
+     * @see Builder#removeExposedCapability(int)
      */
     @NonNull
     public int[] getExposedCapabilities() {
@@ -278,8 +274,7 @@
      * <p>The returned integer-value capabilities will be sorted in ascending numerical order.
      *
      * @see Builder#addRequiredUnderlyingCapability(int)
-     * @see Builder#clearRequiredUnderlyingCapability(int)
-     * @hide
+     * @see Builder#removeRequiredUnderlyingCapability(int)
      */
     @NonNull
     public int[] getRequiredUnderlyingCapabilities() {
@@ -305,7 +300,6 @@
      * Retrieves the configured retry intervals.
      *
      * @see Builder#setRetryInterval(long[])
-     * @hide
      */
     @NonNull
     public long[] getRetryInterval() {
@@ -317,7 +311,7 @@
      *
      * <p>Left to prevent the need to make major changes while changes are actively in flight.
      *
-     * @deprecated use getRequiredUnderlyingCapabilities() instead
+     * @deprecated use getRetryInterval() instead
      * @hide
      */
     @Deprecated
@@ -329,8 +323,7 @@
     /**
      * Retrieves the maximum MTU allowed for this Gateway Connection.
      *
-     * @see Builder.setMaxMtu(int)
-     * @hide
+     * @see Builder#setMaxMtu(int)
      */
     @IntRange(from = MIN_MTU_V6)
     public int getMaxMtu() {
@@ -388,8 +381,6 @@
 
     /**
      * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
-     *
-     * @hide
      */
     public static final class Builder {
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
@@ -409,7 +400,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
         public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
@@ -427,10 +417,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
-        public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.remove(exposedCapability);
@@ -445,7 +435,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
         public Builder addRequiredUnderlyingCapability(
@@ -468,10 +457,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
-        public Builder clearRequiredUnderlyingCapability(
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeRequiredUnderlyingCapability(
                 @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
@@ -501,7 +490,6 @@
          *     15m]}
          * @return this {@link Builder} instance, for chaining
          * @see VcnManager for additional discussion on fail-safe mode
-         * @hide
          */
         @NonNull
         public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -523,7 +511,6 @@
          * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
          *     the IPv6 minimum MTU of 1280. Defaults to 1500.
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -538,7 +525,6 @@
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
-         * @hide
          */
         @NonNull
         public VcnGatewayConnectionConfig build() {
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1a38338..aed64de 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.os.Binder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -39,12 +40,12 @@
 /**
  * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
  *
- * <p>A VCN creates a virtualization layer to allow MVNOs to aggregate heterogeneous physical
+ * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
  * networks, unifying them as a single carrier network. This enables infrastructure flexibility on
- * the part of MVNOs without impacting user connectivity, abstracting the physical network
+ * the part of carriers without impacting user connectivity, abstracting the physical network
  * technologies as an implementation detail of their public network.
  *
- * <p>Each VCN virtualizes an Carrier's network by building tunnels to a carrier's core network over
+ * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
  * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
  * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
  * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
@@ -62,8 +63,6 @@
  * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
  * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
  * automatically exit Safe Mode if all active tunnels connect successfully.
- *
- * @hide
  */
 @SystemService(Context.VCN_MANAGEMENT_SERVICE)
 public class VcnManager {
@@ -101,7 +100,6 @@
         return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Sets the VCN configuration for a given subscription group.
      *
@@ -113,11 +111,10 @@
      *
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @param config the configuration parameters for the VCN
-     * @throws SecurityException if the caller does not have carrier privileges, or is not running
-     *     as the primary user
-     * @throws IOException if the configuration failed to be persisted. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws SecurityException if the caller does not have carrier privileges for the provided
+     *     subscriptionGroup, or is not running as the primary user
+     * @throws IOException if the configuration failed to be saved and persisted to disk. This may
+     *     occur due to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
@@ -134,7 +131,6 @@
         }
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Clears the VCN configuration for a given subscription group.
      *
@@ -145,9 +141,8 @@
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @throws SecurityException if the caller does not have carrier privileges, or is not running
      *     as the primary user
-     * @throws IOException if the configuration failed to be cleared. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
+     *     to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
@@ -267,6 +262,100 @@
         }
     }
 
+    // TODO: make VcnStatusCallback @SystemApi
+    /**
+     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
+     *
+     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
+     * subscription group.
+     *
+     * @hide
+     */
+    public abstract static class VcnStatusCallback {
+        private VcnStatusCallbackBinder mCbBinder;
+
+        /**
+         * Invoked when the VCN for this Callback's subscription group enters safe mode.
+         *
+         * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
+         * establish a connection within a system-determined timeout (while underlying networks were
+         * available).
+         *
+         * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
+         * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
+         */
+        public abstract void onEnteredSafeMode();
+    }
+
+    /**
+     * Registers the given callback to receive status updates for the specified subscription.
+     *
+     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
+     *
+     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
+     * VcnStatusCallback}s may be reused once unregistered.
+     *
+     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
+     * privileges for the specified subscription at the time of invocation.
+     *
+     * @param subscriptionGroup The subscription group to match for callbacks
+     * @param executor The {@link Executor} to be used for invoking callbacks
+     * @param callback The VcnStatusCallback to be registered
+     * @throws IllegalStateException if callback is currently registered with VcnManager
+     * @hide
+     */
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Executor executor,
+            @NonNull VcnStatusCallback callback) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder != null) {
+                throw new IllegalStateException("callback is already registered with VcnManager");
+            }
+            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
+
+            try {
+                mService.registerVcnStatusCallback(
+                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                callback.mCbBinder = null;
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the given callback.
+     *
+     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
+     * was registered with.
+     *
+     * @param callback The callback to be unregistered
+     * @hide
+     */
+    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder == null) {
+                // no Binder attached to this callback, so it's not currently registered
+                return;
+            }
+
+            try {
+                mService.unregisterVcnStatusCallback(callback.mCbBinder);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } finally {
+                callback.mCbBinder = null;
+            }
+        }
+    }
+
     /**
      * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
      * Server.
@@ -286,7 +375,30 @@
 
         @Override
         public void onPolicyChanged() {
-            mExecutor.execute(() -> mListener.onPolicyChanged());
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
+        }
+    }
+
+    /**
+     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
+     *
+     * @hide
+     */
+    private class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
+        @NonNull private final Executor mExecutor;
+        @NonNull private final VcnStatusCallback mCallback;
+
+        private VcnStatusCallbackBinder(
+                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onEnteredSafeMode() {
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
         }
     }
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b39c182..7437e037 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -113,6 +113,7 @@
     boolean hasBadge(int userId);
     boolean isUserUnlocked(int userId);
     boolean isUserRunning(int userId);
+    boolean isUserForeground();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles();
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ea1ce37..8bdfd3d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2300,6 +2300,19 @@
     }
 
     /**
+     * Checks if the calling user is running on foreground.
+     *
+     * @return whether the calling user is running on foreground.
+     */
+    public boolean isUserForeground() {
+        try {
+            return mService.isUserForeground();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the calling user is running in an "unlocked" state.
      * <p>
      * On devices with direct boot, a user is unlocked only after they've
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl
similarity index 60%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/permission/AdminPermissionControlParams.aidl
index 28b11ec..35e63d4 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/permission/AdminPermissionControlParams.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
+/**
+ * Copyright (c) 2021, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,14 +14,6 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.permission;
 
-import android.app.WallpaperColors;
-import android.graphics.RectF;
-
-/**
- * @hide
- */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+parcelable AdminPermissionControlParams;
diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java
new file mode 100644
index 0000000..49507220
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission;
+
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A data object representing an admin's request to control a certain permission
+ * for a certain app.
+ * This class is processed by the Permission Controller's
+ * setRuntimePermissionGrantStateByDeviceAdmin method.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AdminPermissionControlParams implements Parcelable {
+    // The package to grant/deny the permission to.
+    private final @NonNull String mGranteePackageName;
+    // The permission to grant/deny.
+    private final @NonNull String mPermission;
+    // The grant state (granted/denied/default).
+    private final @DevicePolicyManager.PermissionGrantState int mGrantState;
+    // Whether the admin can grant sensors-related permissions.
+    private final boolean mCanAdminGrantSensorsPermissions;
+
+    /**
+     * @hide
+     * A new instance is only created by the framework, so the constructor need not be visible
+     * as system API.
+     */
+    public AdminPermissionControlParams(@NonNull String granteePackageName,
+            @NonNull String permission,
+            int grantState, boolean canAdminGrantSensorsPermissions) {
+        Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty.");
+        Preconditions.checkStringNotEmpty(permission, "Permission must not be empty.");
+        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
+                || grantState == PERMISSION_GRANT_STATE_DENIED
+                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
+
+        mGranteePackageName = granteePackageName;
+        mPermission = permission;
+        mGrantState = grantState;
+        mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions;
+    }
+
+    public static final @NonNull Creator<AdminPermissionControlParams> CREATOR =
+            new Creator<AdminPermissionControlParams>() {
+                @Override
+                public AdminPermissionControlParams createFromParcel(Parcel in) {
+                    String granteePackageName = in.readString();
+                    String permission = in.readString();
+                    int grantState = in.readInt();
+                    boolean mayAdminGrantSensorPermissions = in.readBoolean();
+
+                    return new AdminPermissionControlParams(granteePackageName, permission,
+                            grantState, mayAdminGrantSensorPermissions);
+                }
+
+                @Override
+                public AdminPermissionControlParams[] newArray(int size) {
+                    return new AdminPermissionControlParams[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mGranteePackageName);
+        dest.writeString(mPermission);
+        dest.writeInt(mGrantState);
+        dest.writeBoolean(mCanAdminGrantSensorsPermissions);
+    }
+
+    /** Returns the name of the package the permission applies to */
+    public @NonNull String getGranteePackageName() {
+        return mGranteePackageName;
+    }
+
+    /** Returns the permission name */
+    public @NonNull String getPermission() {
+        return mPermission;
+    }
+
+    /** Returns the grant state */
+    public int getGrantState() {
+        return mGrantState;
+    }
+
+    /**
+     * return true if the admin may control grants of permissions related to sensors.
+     */
+    public boolean canAdminGrantSensorsPermissions() {
+        return mCanAdminGrantSensorsPermissions;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b",
+                mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions);
+    }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 084cc2f..6d677f3 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.permission.AdminPermissionControlParams;
 import com.android.internal.infra.AndroidFuture;
 
 /**
@@ -39,8 +40,8 @@
     void countPermissionApps(in List<String> permissionNames, int flags,
             in AndroidFuture callback);
     void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback);
-    void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
-                String permission, int grantState, in AndroidFuture callback);
+    void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName,
+            in AdminPermissionControlParams params, in AndroidFuture callback);
     void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
     void notifyOneTimePermissionSessionTimeout(String packageName);
     void updateUserSensitiveForApp(int uid, in AndroidFuture callback);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index f306805..084b18e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -16,13 +16,9 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
 
 import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
-import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkFlagsArgument;
@@ -39,7 +35,6 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager.PermissionGrantState;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -70,6 +65,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -323,11 +319,11 @@
 
     /**
      * Set the runtime permission state from a device admin.
+     * This variant takes into account whether the admin may or may not grant sensors-related
+     * permissions.
      *
      * @param callerPackageName The package name of the admin requesting the change
-     * @param packageName Package the permission belongs to
-     * @param permission Permission to change
-     * @param grantState State to set the permission into
+     * @param params Information about the permission being granted.
      * @param executor Executor to run the {@code callback} on
      * @param callback The callback
      *
@@ -338,30 +334,27 @@
             Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY},
             conditional = true)
     public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
-            @NonNull String packageName, @NonNull String permission,
-            @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor,
+            @NonNull AdminPermissionControlParams params,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         checkStringNotEmpty(callerPackageName);
-        checkStringNotEmpty(packageName);
-        checkStringNotEmpty(permission);
-        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                || grantState == PERMISSION_GRANT_STATE_DENIED
-                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-        checkNotNull(executor);
-        checkNotNull(callback);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Objects.requireNonNull(params, "Admin control params must not be null.");
 
         mRemoteService.postAsync(service -> {
             AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
-            service.setRuntimePermissionGrantStateByDeviceAdmin(
-                    callerPackageName, packageName, permission, grantState,
+            service.setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    callerPackageName, params,
                     setRuntimePermissionGrantStateResult);
             return setRuntimePermissionGrantStateResult;
         }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
             final long token = Binder.clearCallingIdentity();
             try {
                 if (err != null) {
-                    Log.e(TAG, "Error setting permissions state for device admin " + packageName,
-                            err);
+                    Log.e(TAG,
+                            "Error setting permissions state for device admin "
+                                    + callerPackageName, err);
                     callback.accept(false);
                 } else {
                     callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult));
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8105b65..ad9e8b3 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -16,9 +16,7 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
 
@@ -259,6 +257,8 @@
     }
 
     /**
+     * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String,
+     * AdminPermissionControlParams, Consumer)}.
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -267,6 +267,7 @@
      * @param grantState State to set the permission into
      * @param callback Callback waiting for whether the state could be set or not
      */
+    @Deprecated
     @BinderThread
     public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(
             @NonNull String callerPackageName, @NonNull String packageName,
@@ -274,6 +275,20 @@
             @NonNull Consumer<Boolean> callback);
 
     /**
+     * Set the runtime permission state from a device admin.
+     *
+     * @param callerPackageName The package name of the admin requesting the change
+     * @param params Parameters of admin request.
+     * @param callback Callback waiting for whether the state could be set or not
+     */
+    @BinderThread
+    public void onSetRuntimePermissionGrantStateByDeviceAdmin(
+            @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params,
+            @NonNull Consumer<Boolean> callback) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
+    /**
      * Called when a package is considered inactive based on the criteria given by
      * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
      * This method is called at the end of a one-time permission session
@@ -468,32 +483,26 @@
             }
 
             @Override
-            public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
-                    String packageName, String permission, int grantState,
+            public void setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    String callerPackageName, AdminPermissionControlParams params,
                     AndroidFuture callback) {
                 checkStringNotEmpty(callerPackageName);
-                checkStringNotEmpty(packageName);
-                checkStringNotEmpty(permission);
-                checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                        || grantState == PERMISSION_GRANT_STATE_DENIED
-                        || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-                checkNotNull(callback);
-
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
                 }
 
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
                 }
 
                 enforceSomePermissionsGrantedToCaller(
                         Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+                checkNotNull(callback);
 
                 onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
-                        packageName, permission, grantState, callback::complete);
+                        params, callback::complete);
             }
 
             @Override
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 91e091c..e134c29 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -525,6 +525,13 @@
      */
     public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
 
+    /**
+     * Namespace for game overlay related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f02e532..e979e13 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1902,6 +1902,18 @@
     public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
 
     /**
+     * Activity Extra: An {@code Arraylist<String>} of {@link NotificationChannel} field names to
+     * show on the Settings UI.
+     *
+     * <p>
+     * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If
+     * included the system will filter out any Settings that doesn't appear in this list that
+     * otherwise would display.
+     */
+    public static final String EXTRA_CHANNEL_FILTER_LIST
+            = "android.provider.extra.CHANNEL_FILTER_LIST";
+
+    /**
      * Activity Action: Show notification redaction settings.
      *
      * @hide
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index 2efc212..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -29,11 +29,8 @@
 import android.annotation.WorkerThread;
 import android.content.ContentResolver;
 import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 
@@ -63,7 +60,6 @@
      *
      * @hide
      */
-    @SystemApi
     public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
 
     private SimPhonebookContract() {
@@ -76,7 +72,6 @@
      * @hide
      */
     @NonNull
-    @SystemApi
     public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
         switch (efType) {
             case EF_ADN:
@@ -122,12 +117,12 @@
          * The name for this record.
          *
          * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
-         * exceeds the maximum supported length or contains unsupported characters.
-         * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
-         * check whether the name is supported.
+         * exceeds the maximum supported length. Use
+         * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name
+         * will be after encoding.
          *
          * @see ElementaryFiles#NAME_MAX_LENGTH
-         * @see #validateName(ContentResolver, int, int, String) )
+         * @see #getEncodedNameLength(ContentResolver, String)
          */
         public static final String NAME = "name";
         /**
@@ -149,24 +144,31 @@
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
 
         /**
-         * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
-         * that the following path segment contains a name to be validated.
-         *
-         * @hide
-         * @see #validateName(ContentResolver, int, int, String)
+         * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name
+         * length could not be determined because the name could not be encoded.
          */
-        @SystemApi
-        public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+        public static final int ERROR_NAME_UNSUPPORTED = -1;
 
         /**
-         * The key for a cursor extra that contains the result of a validate name query.
+         * The method name used to get the encoded length of a value for {@link SimRecords#NAME}
+         * column.
          *
          * @hide
-         * @see #validateName(ContentResolver, int, int, String)
+         * @see #getEncodedNameLength(ContentResolver, String)
+         * @see ContentResolver#call(String, String, String, Bundle)
          */
-        @SystemApi
-        public static final String EXTRA_NAME_VALIDATION_RESULT =
-                "android.provider.extra.NAME_VALIDATION_RESULT";
+        public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length";
+
+        /**
+         * Extra key used for an integer value that contains the length in bytes of an encoded
+         * name.
+         *
+         * @hide
+         * @see #getEncodedNameLength(ContentResolver, String)
+         * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME
+         */
+        public static final String EXTRA_ENCODED_NAME_LENGTH =
+                "android.provider.extra.ENCODED_NAME_LENGTH";
 
 
         /**
@@ -244,32 +246,34 @@
         }
 
         /**
-         * Validates a value that is being provided for the {@link #NAME} column.
+         * Returns the number of bytes required to encode the specified name when it is stored
+         * on the SIM.
          *
-         * <p>The return value can be used to check if the name is valid. If it is not valid then
-         * inserts and updates to the specified elementary file that use the provided name value
-         * will throw an {@link IllegalArgumentException}.
+         * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name
+         * may require more than 1 byte per character depending on the characters it contains. So
+         * this method can be used to check whether a name exceeds the max length.
          *
-         * <p>If the specified SIM or elementary file don't exist then
-         * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
-         * {@link NameValidationResult#isValid()} will return false.
+         * @return the number of bytes required by the encoded name or
+         * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded.
+         * @throws IllegalStateException if the provider fails to return the length.
+         * @see SimRecords#NAME
+         * @see ElementaryFiles#NAME_MAX_LENGTH
          */
-        @NonNull
         @WorkerThread
-        public static NameValidationResult validateName(
-                @NonNull ContentResolver resolver, int subscriptionId,
-                @ElementaryFiles.EfType int efType,
-                @NonNull String name) {
-            Bundle queryArgs = new Bundle();
-            queryArgs.putString(SimRecords.NAME, name);
-            try (Cursor cursor =
-                         resolver.query(buildContentUri(subscriptionId, efType)
-                                 .appendPath(VALIDATE_NAME_PATH_SEGMENT)
-                                 .build(), null, queryArgs, null)) {
-                NameValidationResult result = cursor.getExtras()
-                        .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
-                return result != null ? result : new NameValidationResult(name, "", 0, 0);
+        public static int getEncodedNameLength(
+                @NonNull ContentResolver resolver, @NonNull String name) {
+            Objects.requireNonNull(name);
+            Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name,
+                    null);
+            if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) {
+                throw new IllegalStateException("Provider malfunction: no length was returned.");
             }
+            int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED);
+            if (length < 0 && length != ERROR_NAME_UNSUPPORTED) {
+                throw new IllegalStateException(
+                        "Provider malfunction: invalid length was returned.");
+            }
+            return length;
         }
 
         private static Uri.Builder buildContentUri(
@@ -281,106 +285,6 @@
                     .appendPath(getEfUriPath(efType));
         }
 
-        /** Contains details about the validity of a value provided for the {@link #NAME} column. */
-        public static final class NameValidationResult implements Parcelable {
-
-            @NonNull
-            public static final Creator<NameValidationResult> CREATOR =
-                    new Creator<NameValidationResult>() {
-
-                        @Override
-                        public NameValidationResult createFromParcel(@NonNull Parcel in) {
-                            return new NameValidationResult(in);
-                        }
-
-                        @NonNull
-                        @Override
-                        public NameValidationResult[] newArray(int size) {
-                            return new NameValidationResult[size];
-                        }
-                    };
-
-            private final String mName;
-            private final String mSanitizedName;
-            private final int mEncodedLength;
-            private final int mMaxEncodedLength;
-
-            /** Creates a new instance from the provided values. */
-            public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
-                    int encodedLength, int maxEncodedLength) {
-                this.mName = Objects.requireNonNull(name);
-                this.mSanitizedName = Objects.requireNonNull(sanitizedName);
-                this.mEncodedLength = encodedLength;
-                this.mMaxEncodedLength = maxEncodedLength;
-            }
-
-            private NameValidationResult(Parcel in) {
-                this(in.readString(), in.readString(), in.readInt(), in.readInt());
-            }
-
-            /** Returns the original name that is being validated. */
-            @NonNull
-            public String getName() {
-                return mName;
-            }
-
-            /**
-             * Returns a sanitized copy of the original name with all unsupported characters
-             * replaced with spaces.
-             */
-            @NonNull
-            public String getSanitizedName() {
-                return mSanitizedName;
-            }
-
-            /**
-             * Returns whether the original name isValid.
-             *
-             * <p>If this returns false then inserts and updates using the name will throw an
-             * {@link IllegalArgumentException}
-             */
-            public boolean isValid() {
-                return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
-                        && Objects.equals(
-                        mName, mSanitizedName);
-            }
-
-            /** Returns whether the character at the specified position is supported by the SIM. */
-            public boolean isSupportedCharacter(int position) {
-                return mName.charAt(position) == mSanitizedName.charAt(position);
-            }
-
-            /**
-             * Returns the number of bytes required to save the name.
-             *
-             * <p>This may be more than the number of characters in the name.
-             */
-            public int getEncodedLength() {
-                return mEncodedLength;
-            }
-
-            /**
-             * Returns the maximum number of bytes that are supported for the name.
-             *
-             * @see ElementaryFiles#NAME_MAX_LENGTH
-             */
-            public int getMaxEncodedLength() {
-                return mMaxEncodedLength;
-            }
-
-            @Override
-            public int describeContents() {
-                return 0;
-            }
-
-            @Override
-            public void writeToParcel(@NonNull Parcel dest, int flags) {
-                dest.writeString(mName);
-                dest.writeString(mSanitizedName);
-                dest.writeInt(mEncodedLength);
-                dest.writeInt(mMaxEncodedLength);
-            }
-        }
     }
 
     /** Constants for metadata about the elementary files of the SIM cards in the phone. */
@@ -446,13 +350,10 @@
          */
         public static final int EF_SDN = 3;
         /** @hide */
-        @SystemApi
         public static final String EF_ADN_PATH_SEGMENT = "adn";
         /** @hide */
-        @SystemApi
         public static final String EF_FDN_PATH_SEGMENT = "fdn";
         /** @hide */
-        @SystemApi
         public static final String EF_SDN_PATH_SEGMENT = "sdn";
         /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
@@ -464,7 +365,6 @@
          *
          * @hide
          */
-        @SystemApi
         public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
 
         /** Content URI for the ADN-like elementary files available on the device. */
@@ -480,8 +380,7 @@
          * Returns a content uri for a specific elementary file.
          *
          * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
-         * If the SIM doesn't support the specified elementary file it will have a zero value for
-         * {@link #MAX_RECORDS}.
+         * If the SIM doesn't support the specified elementary file it will return an empty cursor.
          */
         @NonNull
         public static Uri getItemUri(int subscriptionId, @EfType int efType) {
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index a79b197..5a89cdf 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -188,6 +188,7 @@
     public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
     public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
     public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;
+    public static final int KM_PURPOSE_ATTEST_KEY = KeyPurpose.ATTEST_KEY;
 
     // Key formats.
     public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
deleted file mode 100644
index 9d2e5b9..0000000
--- a/core/java/android/service/wallpaper/EngineWindowPage.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.wallpaper;
-
-import android.app.WallpaperColors;
-import android.graphics.Bitmap;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * This class represents a page of a launcher page used by the wallpaper
- * @hide
- */
-public class EngineWindowPage {
-    private Bitmap mScreenShot;
-    private volatile long  mLastUpdateTime;
-    private Set<RectF> mCallbackAreas = new ArraySet<>();
-    private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
-
-    /** should be locked extrnally */
-    public void addArea(RectF area) {
-        mCallbackAreas.add(area);
-    }
-
-    /** should be locked extrnally */
-    public void addWallpaperColors(RectF area, WallpaperColors colors) {
-        mCallbackAreas.add(area);
-        mRectFColors.put(area, colors);
-    }
-
-    /** get screenshot bitmap */
-    public Bitmap getBitmap() {
-        if (mScreenShot == null || mScreenShot.isRecycled()) return null;
-        return mScreenShot;
-    }
-
-    /** remove callbacks for an area */
-    public void removeArea(RectF area) {
-        mCallbackAreas.remove(area);
-        mRectFColors.remove(area);
-    }
-
-    /** set the last time the screenshot was updated */
-    public void setLastUpdateTime(long lastUpdateTime) {
-        mLastUpdateTime = lastUpdateTime;
-    }
-
-    /** get last screenshot time */
-    public long getLastUpdateTime() {
-        return mLastUpdateTime;
-    }
-
-    /** get colors for an area */
-    public WallpaperColors getColors(RectF rect) {
-        return mRectFColors.get(rect);
-    }
-
-    /** set the new bitmap version */
-    public void setBitmap(Bitmap screenShot) {
-        mScreenShot = screenShot;
-    }
-
-    /** get areas of interest */
-    public Set<RectF> getAreas() {
-        return mCallbackAreas;
-    }
-
-    /** run operations on this page */
-    public synchronized void execSync(Consumer<EngineWindowPage> run) {
-        run.accept(this);
-    }
-
-    /** nullify the area color */
-    public void removeColor(RectF colorArea) {
-        mRectFColors.remove(colorArea);
-    }
-}
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f81ed34..f334d9d 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -16,7 +16,6 @@
 
 package android.service.wallpaper;
 
-import android.graphics.RectF;
 import android.os.ParcelFileDescriptor;
 import android.service.wallpaper.IWallpaperEngine;
 import android.app.WallpaperColors;
@@ -29,5 +28,4 @@
     void engineShown(IWallpaperEngine engine);
     ParcelFileDescriptor setWallpaper(String name);
     void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
-    void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
 }
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index fbb449d..90392e6 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,10 +16,7 @@
 
 package android.service.wallpaper;
 
-import android.app.ILocalWallpaperColorConsumer;
-import android.app.WallpaperColors;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.os.Bundle;
 
@@ -42,6 +39,4 @@
     void destroy();
     void setZoomOut(float scale);
     void scalePreview(in Rect positionInWindow);
-    void removeLocalColorsAreas(in List<RectF> regions);
-    void addLocalColorsAreas(in List<RectF> regions);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b447d5..82e0b4a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,7 +23,6 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 
 import android.annotation.FloatRange;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -43,7 +42,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -55,8 +53,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.view.Display;
@@ -70,8 +66,6 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.MotionEvent;
-import android.view.PixelCopy;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.View;
@@ -89,10 +83,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 
 /**
@@ -124,12 +115,7 @@
     public static final String SERVICE_META_DATA = "android.service.wallpaper";
 
     static final String TAG = "WallpaperService";
-    static final boolean DEBUG = true;
-    static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
-    private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
-    private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
-    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
-            new RectF(0, 0, 1, 1);
+    static final boolean DEBUG = false;
 
     private static final int DO_ATTACH = 10;
     private static final int DO_DETACH = 20;
@@ -148,8 +134,6 @@
     private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_SCALE_PREVIEW = 10110;
-    private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
-            Float.NEGATIVE_INFINITY);
 
     private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
 
@@ -174,14 +158,6 @@
      */
     public class Engine {
         IWallpaperEngineWrapper mIWallpaperEngine;
-        final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
-        final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
-
-        // 2D matrix [x][y] to represent a page of a portion of a window
-        EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
-        Bitmap mLastScreenshot;
-        int mLastWindowPage = -1;
-        float mLastPageOffset = 0;
 
         // Copies from mIWallpaperEngine.
         HandlerCaller mCaller;
@@ -433,8 +409,8 @@
          */
         @VisibleForTesting
         public Engine(Supplier<Long> clockFunction, Handler handler) {
-            mClockFunction = clockFunction;
-            mHandler = handler;
+           mClockFunction = clockFunction;
+           mHandler = handler;
         }
 
         /**
@@ -472,19 +448,6 @@
         }
 
         /**
-         * Return whether the wallpaper is capable of extracting local colors in a rectangle area,
-         * Must implement without calling super:
-         * {@link #addLocalColorsAreas(List)}
-         * {@link #removeLocalColorsAreas(List)}
-         * When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
-         * See {@link com.android.systemui.ImageWallpaper} for an example
-         * @hide
-         */
-        public boolean supportsLocalColorExtraction() {
-            return false;
-        }
-
-        /**
          * Returns true if this engine is running in preview mode -- that is,
          * it is being shown to the user before they select it as the actual
          * wallpaper.
@@ -728,8 +691,6 @@
                     Log.w(TAG, "Can't notify system because wallpaper connection "
                             + "was not established.");
                 }
-                resetWindowPages();
-                processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
             }
@@ -753,28 +714,6 @@
         }
 
         /**
-         * Send the changed local color areas for the connection
-         * @param regions
-         * @param colors
-         * @hide
-         */
-        public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
-                @NonNull List<WallpaperColors> colors)
-                throws RuntimeException {
-            for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
-                try {
-                    mConnection.onLocalWallpaperColorsChanged(
-                            regions.get(i),
-                            colors.get(i),
-                            mDisplayContext.getDisplayId()
-                    );
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        /**
          * Sets internal engine state. Only for testing.
          * @param created {@code true} or {@code false}.
          * @hide
@@ -1129,9 +1068,7 @@
                         mIsCreating = false;
                         mSurfaceCreated = true;
                         if (redrawNeeded) {
-                            resetWindowPages();
                             mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
-                            processLocalColors(mPendingXOffset, mPendingXOffsetStep);
                         }
                         reposition();
                         mIWallpaperEngine.reportShown();
@@ -1272,7 +1209,6 @@
             if (!mDestroyed) {
                 mVisible = visible;
                 reportVisibility();
-                if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             }
         }
 
@@ -1342,405 +1278,6 @@
                 } catch (RemoteException e) {
                 }
             }
-
-            // setup local color extraction data
-            processLocalColors(xOffset, xOffsetStep);
-        }
-
-        private void processLocalColors(float xOffset, float xOffsetStep) {
-            // implemented by the wallpaper
-            if (supportsLocalColorExtraction()) return;
-            if (DEBUG) {
-                Log.d(TAG, "processLocalColors " + xOffset + " of step "
-                        + xOffsetStep);
-            }
-            //below is the default implementation
-            if (!validStep(xOffsetStep)) {
-                if (DEBUG) {
-                    Log.w(TAG, "invalid offset step " + xOffsetStep);
-                }
-                return;
-            }
-
-            int xPage = Math.round(xOffset / xOffsetStep);
-            if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
-            mLastWindowPage = xPage;
-            mLastPageOffset = xOffset;
-            int xPages = Math.round(1 / xOffsetStep);
-            if (DEBUG) {
-                Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
-                Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
-            }
-            EngineWindowPage current;
-            synchronized (mLock) {
-                if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
-                    mWindowPages = new EngineWindowPage[xPages];
-                    initWindowPages(mWindowPages, xOffsetStep);
-                }
-                if (mLocalColorsToAdd.size() != 0) {
-                    for (RectF colorArea : mLocalColorsToAdd) {
-                        if (!isValid(colorArea)) continue;
-                        mLocalColorAreas.add(colorArea);
-                        int colorPage = getRectFPage(colorArea, xOffsetStep);
-                        EngineWindowPage currentPage = mWindowPages[colorPage];
-                        if (currentPage == null) {
-                            currentPage = new EngineWindowPage();
-                            currentPage.addArea(colorArea);
-                            mWindowPages[colorPage] = currentPage;
-                        } else {
-                            currentPage.setLastUpdateTime(0);
-                            currentPage.removeColor(colorArea);
-                        }
-                    }
-                    mLocalColorsToAdd.clear();
-                }
-                if (xPage >= mWindowPages.length) {
-                    if (DEBUG) {
-                        Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
-                        Log.e(TAG, "error on page " + xPage + " out of " + xPages);
-                        Log.e(TAG,
-                                "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
-                    }
-                    xPage = mWindowPages.length - 1;
-                }
-                current = mWindowPages[xPage];
-                if (current == null) {
-                    if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
-                    if (DEBUG) {
-                        Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
-                    }
-                    current = new EngineWindowPage();
-                    mWindowPages[xPage] = current;
-                }
-            }
-            updatePage(current, xPage, xPages, xOffsetStep);
-        }
-
-        private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
-            float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
-
-            return (pageCenter > xOffset && pageCenter < mLastPageOffset)
-                || (pageCenter < xOffset && pageCenter > mLastPageOffset);
-        }
-
-        private void initWindowPages(EngineWindowPage[] windowPages, float step) {
-            for (int i = 0; i < windowPages.length; i++) {
-                windowPages[i] = new EngineWindowPage();
-            }
-            mLocalColorAreas.addAll(mLocalColorsToAdd);
-            mLocalColorsToAdd.clear();
-            for (RectF area: mLocalColorAreas) {
-                if (!isValid(area)) {
-                    mLocalColorAreas.remove(area);
-                    continue;
-                }
-                int pageNum = getRectFPage(area, step);
-                windowPages[pageNum].addArea(area);
-            }
-        }
-
-        void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
-                float xOffsetStep) {
-            // to save creating a runnable, check twice
-            long current = System.nanoTime() / 1_000_000;
-            AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
-            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
-            currentPage.execSync((page) -> {
-                // just in case of race double check
-                long currentInner = System.nanoTime() / 1_000_000;
-                lapsed.set(currentInner - page.getLastUpdateTime());
-                page.setLastUpdateTime(currentInner);
-            });
-            long lastUpdate = currentPage.getLastUpdateTime();
-            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
-            Surface surface = mSurfaceHolder.getSurface();
-            boolean widthIsLarger =
-                    mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
-            int smaller = widthIsLarger ? mSurfaceControl.getWidth()
-                    : mSurfaceControl.getHeight();
-            float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
-            int width = (int) (ratio * mSurfaceControl.getWidth());
-            int height = (int) (ratio * mSurfaceControl.getHeight());
-            if (width <= 0 || height <= 0) {
-                return;
-            }
-            Bitmap screenShot = Bitmap.createBitmap(width, height,
-                    Bitmap.Config.ARGB_8888);
-            final Bitmap finalScreenShot = screenShot;
-            Trace.beginSection("WallpaperService#pixelCopy");
-            PixelCopy.request(surface, screenShot, (res) -> {
-                Trace.endSection();
-                if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
-                if (res != PixelCopy.SUCCESS) {
-                    currentPage.execSync((p) -> {
-                        // reset the time
-                        p.setLastUpdateTime(lastUpdate);
-                        // assign the last bitmap taken for now
-                        p.setBitmap(mLastScreenshot);
-                    });
-                    return;
-                }
-                mLastScreenshot = finalScreenShot;
-                // going to hold this lock for a while
-                currentPage.execSync((p) -> {
-                    p.setBitmap(finalScreenShot);
-                });
-                updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
-            }, mHandler);
-
-        }
-        // locked by the passed page
-        private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
-                float xOffsetStep) {
-            if (page.getBitmap() == null) return;
-            if (DEBUG) {
-                Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
-                        + page.getAreas().size() + " and bitmap size of "
-                        + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
-            }
-            for (RectF area: page.getAreas()) {
-                RectF subArea = generateSubRect(area, pageIndx, numPages);
-                Bitmap b = page.getBitmap();
-                int x = Math.round(b.getWidth() * subArea.left);
-                int y = Math.round(b.getHeight() * subArea.top);
-                int width = Math.round(b.getWidth() * subArea.width());
-                int height = Math.round(b.getHeight() * subArea.height());
-                Bitmap target;
-                try {
-                    target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error creating page local color bitmap", e);
-                    continue;
-                }
-                WallpaperColors color = WallpaperColors.fromBitmap(target);
-                target.recycle();
-                WallpaperColors currentColor = page.getColors(area);
-
-                if (DEBUG) {
-                    Log.d(TAG, "getting local bitmap area x " + x + " y " + y
-                            + " width " + width + " height " + height + " for sub area " + subArea
-                            + " and with page " + pageIndx + " of " + numPages);
-
-                }
-                if (currentColor == null || !color.equals(currentColor)) {
-                    page.addWallpaperColors(area, color);
-                    if (DEBUG) {
-                        Log.d(TAG, "onLocalWallpaperColorsChanged"
-                                + " local color callback for area" + area + " for page " + pageIndx
-                                + " of " + numPages);
-                    }
-                    try {
-                        mConnection.onLocalWallpaperColorsChanged(area, color,
-                                mDisplayContext.getDisplayId());
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
-                    }
-                }
-            }
-        }
-
-        private RectF generateSubRect(RectF in, int pageInx, int numPages) {
-            float minLeft = (float) (pageInx) / (float) (numPages);
-            float maxRight = (float) (pageInx + 1) / (float) (numPages);
-            float left = in.left;
-            float right = in.right;
-
-            // bound rect
-            if (left < minLeft) left = minLeft;
-            if (right > maxRight) right = maxRight;
-
-            // scale up the sub area then trim
-            left = (left * (float) numPages) % 1f;
-            right = (right * (float) numPages) % 1f;
-            if (right == 0f) {
-                right = 1f;
-            }
-
-            return new RectF(left, in.top, right, in.bottom);
-        }
-
-        private void resetWindowPages() {
-            if (supportsLocalColorExtraction()) return;
-            mLastWindowPage = -1;
-            synchronized (mLock) {
-                for (int i = 0; i < mWindowPages.length; i++) {
-                    EngineWindowPage page = mWindowPages[i];
-                    if (page != null) {
-                        page.execSync((p) -> {
-                            p.setLastUpdateTime(0L);
-                        });
-                    }
-                }
-            }
-        }
-
-        private int getRectFPage(RectF area, float step) {
-            if (!isValid(area)) return 0;
-            if (!validStep(step)) return 0;
-            int pages = Math.round(1 / step);
-            int page = Math.round(area.centerX() * pages);
-            if (page == pages) return pages - 1;
-            if (page == mWindowPages.length) page = mWindowPages.length - 1;
-            return page;
-        }
-
-        /**
-         * Add local colors areas of interest
-         * @param regions list of areas
-         * @hide
-         */
-        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
-            if (supportsLocalColorExtraction()) return;
-            if (DEBUG) {
-                Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
-            }
-            float step = mPendingXOffsetStep;
-
-            List<WallpaperColors> colors = getLocalWallpaperColors(regions);
-            synchronized (mLock) {
-                if (!validStep(step)) {
-                    mLocalColorsToAdd.addAll(regions);
-                    return;
-                }
-                for (int i = 0; i < regions.size(); i++) {
-                    RectF area = regions.get(i);
-                    if (!isValid(area)) continue;
-                    int pageInx = getRectFPage(area, step);
-                    // no page should be null
-                    EngineWindowPage page = mWindowPages[pageInx];
-
-                    if (page != null) {
-                        mLocalColorAreas.add(area);
-                        page.addArea(area);
-                        WallpaperColors color = colors.get(i);
-                        if (color != null && !color.equals(page.getColors(area))) {
-                            page.execSync(p -> {
-                                p.addWallpaperColors(area, color);
-                            });
-                        }
-                    } else {
-                        mLocalColorsToAdd.add(area);
-                    }
-                }
-            }
-
-            for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
-                try {
-                    mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
-                            mDisplayContext.getDisplayId());
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
-                    return;
-                }
-            }
-        }
-
-        /**
-         * Remove local colors areas of interest if they exist
-         * @param regions list of areas
-         * @hide
-         */
-        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
-            if (supportsLocalColorExtraction()) return;
-            synchronized (mLock) {
-                float step = mPendingXOffsetStep;
-                mLocalColorsToAdd.removeAll(regions);
-                mLocalColorAreas.removeAll(regions);
-                if (!validStep(step)) {
-                    return;
-                }
-                for (int i = 0; i < regions.size(); i++) {
-                    RectF area = regions.get(i);
-                    if (!isValid(area)) continue;
-                    int pageInx = getRectFPage(area, step);
-                    // no page should be null
-                    EngineWindowPage page = mWindowPages[pageInx];
-                    if (page != null) {
-                        page.execSync(p -> {
-                            p.removeArea(area);
-                        });
-                    }
-                }
-            }
-        }
-
-        private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
-            ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
-            float step = mPendingXOffsetStep;
-            if (!validStep(step)) {
-                if (DEBUG) Log.d(TAG, "invalid step size " + step);
-                step = 1.0f;
-            }
-            for (int i = 0; i < areas.size(); i++) {
-                RectF currentArea = areas.get(i);
-                EngineWindowPage page;
-                RectF area;
-                int pageIndx;
-                synchronized (mLock) {
-                    pageIndx = getRectFPage(currentArea, step);
-                    if (mWindowPages.length == 0 || pageIndx < 0
-                            || pageIndx > mWindowPages.length || !isValid(currentArea)) {
-                        colors.add(null);
-                        continue;
-                    }
-                    area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
-                    page = mWindowPages[pageIndx];
-                }
-                if (page == null) {
-                    colors.add(null);
-                    continue;
-                }
-                float finalStep = step;
-                int finalPageIndx = pageIndx;
-                Bitmap screenShot = page.getBitmap();
-                if (screenShot == null || screenShot.isRecycled()) {
-                    if (DEBUG) {
-                        Log.d(TAG, "invalid bitmap " + screenShot
-                                + " for page " + finalPageIndx);
-                    }
-                    page.setLastUpdateTime(0);
-                    colors.add(null);
-                    continue;
-                }
-                Bitmap b = screenShot;
-                Rect subImage = new Rect(
-                        Math.round(area.left * b.getWidth() / finalStep),
-                        Math.round(area.top * b.getHeight()),
-                        Math.round(area.right * b.getWidth() / finalStep),
-                        Math.round(area.bottom * b.getHeight())
-                );
-                subImage = fixRect(b, subImage);
-                if (DEBUG) {
-                    Log.d(TAG, "getting subbitmap of " + subImage.toString()
-                            + " for RectF " + area.toString()
-                            + " screenshot width " + screenShot.getWidth() + " height "
-                            + screenShot.getHeight());
-                }
-                Bitmap colorImg = Bitmap.createBitmap(screenShot,
-                        subImage.left, subImage.top, subImage.width(), subImage.height());
-                if (DEBUG) {
-                    Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
-                            + colorImg.getHeight());
-                }
-                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
-                colors.add(color);
-            }
-            return colors;
-        }
-
-        // fix the rect to be included within the bounds of the bitmap
-        private Rect fixRect(Bitmap b, Rect r) {
-            r.left =  r.left >= r.right || r.left >= b.getWidth() || r.left > 0
-                    ? 0
-                    : r.left;
-            r.right =  r.left >= r.right || r.right > b.getWidth()
-                    ? b.getWidth()
-                    : r.right;
-            return r;
-        }
-
-        private boolean validStep(float step) {
-            return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
         }
 
         void doCommand(WallpaperCommand cmd) {
@@ -1834,16 +1371,6 @@
         };
     }
 
-    private boolean isValid(RectF area) {
-        boolean valid = area.bottom > area.top && area.left < area.right
-                && LOCAL_COLOR_BOUNDS.contains(area);
-        return valid;
-    }
-
-    private boolean inRectFRange(float number) {
-        return number >= 0f && number <= 1f;
-    }
-
     class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
             implements HandlerCaller.Callback {
         private final HandlerCaller mCaller;
@@ -1950,14 +1477,6 @@
             mCaller.sendMessage(msg);
         }
 
-        public void addLocalColorsAreas(List<RectF> regions) {
-            mEngine.addLocalColorsAreas(regions);
-        }
-
-        public void removeLocalColorsAreas(List<RectF> regions) {
-            mEngine.removeLocalColorsAreas(regions);
-        }
-
         public void destroy() {
             Message msg = mCaller.obtainMessage(DO_DETACH);
             mCaller.sendMessage(msg);
@@ -1997,15 +1516,14 @@
             }
             switch (message.what) {
                 case DO_ATTACH: {
-                    Engine engine = onCreateEngine();
-                    mEngine = engine;
                     try {
                         mConnection.attachEngine(this, mDisplayId);
                     } catch (RemoteException e) {
-                        engine.detach();
                         Log.w(TAG, "Wallpaper host disappeared", e);
                         return;
                     }
+                    Engine engine = onCreateEngine();
+                    mEngine = engine;
                     mActiveEngines.add(engine);
                     engine.attach(this);
                     return;
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
similarity index 63%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechManager.aidl
index 28b11ec..e6b63df 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechManager.aidl
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.speech.tts;
 
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+import android.speech.tts.ITextToSpeechSessionCallback;
 
 /**
+ * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
+ * specified provider proxied by the system service.
+ *
  * @hide
  */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+oneway interface ITextToSpeechManager {
+    void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
+}
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechSession.aidl
similarity index 64%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechSession.aidl
index 28b11ec..b2afeb0 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechSession.aidl
@@ -14,14 +14,20 @@
  * limitations under the License.
  */
 
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.speech.tts;
 
 /**
- * @hide
+ * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ *
+ * @see ITextToSpeechManager
+ * @see ITextToSpeechSessionCallback
+ *
+ * {@hide}
  */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
+oneway interface ITextToSpeechSession {
+
+    /**
+     * Disconnects the client from the TTS provider.
+     */
+    void disconnect();
 }
\ No newline at end of file
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
similarity index 63%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
index 28b11ec..545622a 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
@@ -14,14 +14,19 @@
  * limitations under the License.
  */
 
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.speech.tts;
+import android.speech.tts.ITextToSpeechSession;
 
 /**
+ * Callback interface for a session created by {@link ITextToSpeechManager} API.
+ *
  * @hide
  */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
+oneway interface ITextToSpeechSessionCallback {
+
+    void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
+
+    void onDisconnected();
+
+    void onError(in String errorInfo);
 }
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a18538..5d66dc7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,6 +35,7 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -51,6 +52,7 @@
 import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  *
@@ -695,6 +697,8 @@
         public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
     }
 
+    private static final boolean DEBUG = false;
+
     private final Context mContext;
     @UnsupportedAppUsage
     private Connection mConnectingServiceConnection;
@@ -716,6 +720,9 @@
     private final Map<CharSequence, Uri> mUtterances;
     private final Bundle mParams = new Bundle();
     private final TtsEngines mEnginesHelper;
+    private final boolean mIsSystem;
+    @Nullable private final Executor mInitExecutor;
+
     @UnsupportedAppUsage
     private volatile String mCurrentEngine = null;
 
@@ -758,8 +765,21 @@
      */
     public TextToSpeech(Context context, OnInitListener listener, String engine,
             String packageName, boolean useFallback) {
+        this(context, /* initExecutor= */ null, listener, engine, packageName,
+                useFallback, /* isSystem= */ true);
+    }
+
+    /**
+     * Used internally to instantiate TextToSpeech objects.
+     *
+     * @hide
+     */
+    private TextToSpeech(Context context, @Nullable Executor initExecutor,
+            OnInitListener initListener, String engine, String packageName, boolean useFallback,
+            boolean isSystem) {
         mContext = context;
-        mInitListener = listener;
+        mInitExecutor = initExecutor;
+        mInitListener = initListener;
         mRequestedEngine = engine;
         mUseFallback = useFallback;
 
@@ -768,6 +788,9 @@
         mUtteranceProgressListener = null;
 
         mEnginesHelper = new TtsEngines(mContext);
+
+        mIsSystem = isSystem;
+
         initTts();
     }
 
@@ -842,10 +865,14 @@
     }
 
     private boolean connectToEngine(String engine) {
-        Connection connection = new Connection();
-        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
-        intent.setPackage(engine);
-        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        Connection connection;
+        if (mIsSystem) {
+            connection = new SystemConnection();
+        } else {
+            connection = new DirectConnection();
+        }
+
+        boolean bound = connection.connect(engine);
         if (!bound) {
             Log.e(TAG, "Failed to bind to " + engine);
             return false;
@@ -857,11 +884,19 @@
     }
 
     private void dispatchOnInit(int result) {
-        synchronized (mStartLock) {
-            if (mInitListener != null) {
-                mInitListener.onInit(result);
-                mInitListener = null;
+        Runnable onInitCommand = () -> {
+            synchronized (mStartLock) {
+                if (mInitListener != null) {
+                    mInitListener.onInit(result);
+                    mInitListener = null;
+                }
             }
+        };
+
+        if (mInitExecutor != null) {
+            mInitExecutor.execute(onInitCommand);
+        } else {
+            onInitCommand.run();
         }
     }
 
@@ -2127,13 +2162,17 @@
         return mEnginesHelper.getEngines();
     }
 
-    private class Connection implements ServiceConnection {
+    private abstract class Connection implements ServiceConnection {
         private ITextToSpeechService mService;
 
         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
 
         private boolean mEstablished;
 
+        abstract boolean connect(String engine);
+
+        abstract void disconnect();
+
         private final ITextToSpeechCallback.Stub mCallback =
                 new ITextToSpeechCallback.Stub() {
                     public void onStop(String utteranceId, boolean isStarted)
@@ -2199,11 +2238,6 @@
                 };
 
         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
-            private final ComponentName mName;
-
-            public SetupConnectionAsyncTask(ComponentName name) {
-                mName = name;
-            }
 
             @Override
             protected Integer doInBackground(Void... params) {
@@ -2227,7 +2261,7 @@
                             mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
                         }
 
-                        Log.i(TAG, "Set up connection to " + mName);
+                        Log.i(TAG, "Setting up the connection to TTS engine...");
                         return SUCCESS;
                     } catch (RemoteException re) {
                         Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2249,11 +2283,11 @@
         }
 
         @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
             synchronized(mStartLock) {
                 mConnectingServiceConnection = null;
 
-                Log.i(TAG, "Connected to " + name);
+                Log.i(TAG, "Connected to TTS engine");
 
                 if (mOnSetupConnectionAsyncTask != null) {
                     mOnSetupConnectionAsyncTask.cancel(false);
@@ -2263,7 +2297,7 @@
                 mServiceConnection = Connection.this;
 
                 mEstablished = false;
-                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
                 mOnSetupConnectionAsyncTask.execute();
             }
         }
@@ -2277,7 +2311,7 @@
          *
          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
          */
-        private boolean clearServiceConnection() {
+        protected boolean clearServiceConnection() {
             synchronized(mStartLock) {
                 boolean result = false;
                 if (mOnSetupConnectionAsyncTask != null) {
@@ -2295,8 +2329,8 @@
         }
 
         @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.i(TAG, "Asked to disconnect from " + name);
+        public void onServiceDisconnected(ComponentName componentName) {
+            Log.i(TAG, "Disconnected from TTS engine");
             if (clearServiceConnection()) {
                 /* We need to protect against a rare case where engine
                  * dies just after successful connection - and we process onServiceDisconnected
@@ -2308,11 +2342,6 @@
             }
         }
 
-        public void disconnect() {
-            mContext.unbindService(this);
-            clearServiceConnection();
-        }
-
         public boolean isEstablished() {
             return mService != null && mEstablished;
         }
@@ -2342,6 +2371,91 @@
         }
     }
 
+    // Currently all the clients are routed through the System connection. Direct connection
+    // is left for debugging, testing and benchmarking purposes.
+    // TODO(b/179599071): Remove direct connection once system one is fully tested.
+    private class DirectConnection extends Connection {
+        @Override
+        boolean connect(String engine) {
+            Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+            intent.setPackage(engine);
+            return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+        }
+
+        @Override
+        void disconnect() {
+            mContext.unbindService(this);
+            clearServiceConnection();
+        }
+    }
+
+    private class SystemConnection extends Connection {
+
+        @Nullable
+        private volatile ITextToSpeechSession mSession;
+
+        @Override
+        boolean connect(String engine) {
+            IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
+
+            ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
+
+            if (manager == null) {
+                Log.e(TAG, "System service is not available!");
+                return false;
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Connecting to engine: " + engine);
+            }
+
+            try {
+                manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
+                    @Override
+                    public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
+                        mSession = session;
+                        onServiceConnected(
+                                /* componentName= */ null,
+                                serviceBinder);
+                    }
+
+                    @Override
+                    public void onDisconnected() {
+                        onServiceDisconnected(/* componentName= */ null);
+                    }
+
+                    @Override
+                    public void onError(String errorInfo) {
+                        Log.w(TAG, "System TTS connection error: " + errorInfo);
+                        // The connection was not established successfully - handle as
+                        // disconnection: clear the state and notify the user.
+                        onServiceDisconnected(/* componentName= */ null);
+                    }
+                });
+
+                return true;
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Error communicating with the System Server: ", ex);
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        void disconnect() {
+            ITextToSpeechSession session = mSession;
+
+            if (session != null) {
+                try {
+                    session.disconnect();
+                } catch (RemoteException ex) {
+                    Log.w(TAG, "Error disconnecting session", ex);
+                }
+
+                clearServiceConnection();
+            }
+        }
+    }
+
     private interface Action<R> {
         R run(ITextToSpeechService service) throws RemoteException;
     }
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index bfa8bf2..52ec5bd 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -16,8 +16,10 @@
 
 package android.uwb;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.PersistableBundle;
@@ -247,6 +249,7 @@
      *
      * @param params configuration parameters for starting the session
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void start(@NonNull PersistableBundle params) {
         if (mState != State.IDLE) {
             throw new IllegalStateException();
@@ -271,6 +274,7 @@
      *
      * @param params the parameters to reconfigure and their new values
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void reconfigure(@NonNull PersistableBundle params) {
         if (mState != State.ACTIVE && mState != State.IDLE) {
             throw new IllegalStateException();
@@ -302,6 +306,7 @@
      * <p>On failure to stop the session,
      * {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked.
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void stop() {
         if (mState != State.ACTIVE) {
             throw new IllegalStateException();
@@ -333,6 +338,7 @@
      * {@link #close()}, even if the {@link RangingSession} is already closed.
      */
     @Override
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void close() {
         if (mState == State.CLOSED) {
             mExecutor.execute(() -> mCallback.onClosed(
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 8adfe06..2dc0ba0 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -16,9 +16,11 @@
 
 package android.uwb;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -154,6 +156,7 @@
      * @param executor an {@link Executor} to execute given callback
      * @param callback user implementation of the {@link AdapterStateCallback}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull AdapterStateCallback callback) {
         mAdapterStateListener.register(executor, callback);
@@ -168,6 +171,7 @@
      *
      * @param callback user implementation of the {@link AdapterStateCallback}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
         mAdapterStateListener.unregister(callback);
     }
@@ -181,6 +185,7 @@
      * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public PersistableBundle getSpecificationInfo() {
         try {
             return mUwbAdapter.getSpecificationInfo();
@@ -194,6 +199,7 @@
      *
      * @return true if ranging is supported
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public boolean isRangingSupported() {
         try {
             return mUwbAdapter.isRangingSupported();
@@ -250,6 +256,7 @@
      * @return angle of arrival type supported
      */
     @AngleOfArrivalSupportType
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getAngleOfArrivalSupport() {
         try {
             switch (mUwbAdapter.getAngleOfArrivalSupport()) {
@@ -281,6 +288,7 @@
      * @return {@link List} of supported channel numbers ordered by preference
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public List<Integer> getSupportedChannelNumbers() {
         List<Integer> channels = new ArrayList<>();
         try {
@@ -300,6 +308,7 @@
      * @return {@link List} of supported preamble code indices
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public Set<Integer> getSupportedPreambleCodeIndices() {
         Set<Integer> preambles = new HashSet<>();
         try {
@@ -320,6 +329,7 @@
      * @return the timestamp resolution in nanoseconds
      */
     @SuppressLint("MethodNameUnits")
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public long elapsedRealtimeResolutionNanos() {
         try {
             return mUwbAdapter.getTimestampResolutionNanos();
@@ -333,6 +343,7 @@
      *
      * @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxSimultaneousSessions() {
         try {
             return mUwbAdapter.getMaxSimultaneousSessions();
@@ -347,6 +358,7 @@
      *
      * @return the maximum number of remote devices per {@link RangingSession}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxRemoteDevicesPerInitiatorSession() {
         try {
             return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession();
@@ -361,6 +373,7 @@
      *
      * @return the maximum number of remote devices per {@link RangingSession}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxRemoteDevicesPerResponderSession() {
         try {
             return mUwbAdapter.getMaxRemoteDevicesPerResponderSession();
@@ -396,6 +409,7 @@
      *         {@link RangingSession.Callback#onOpened(RangingSession)}.
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull RangingSession.Callback callbacks) {
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 5937499..6543de1 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -154,6 +154,24 @@
      */
     public static final int VSYNC_TIMESTAMP = 11;
 
+    /**
+     * Metric identifier for GPU duration.
+     * <p>
+     * Represents the total time in nanoseconds this frame took to complete on the GPU.
+     * </p>
+     **/
+    public static final int GPU_DURATION = 12;
+
+    /**
+     * Metric identifier for the total duration that was available to the app to produce a frame.
+     * <p>
+     * Represents the total time in nanoseconds the system allocated for the app to produce its
+     * frame. If FrameMetrics.TOTAL_DURATION < FrameMetrics.DEADLINE, the app hit its intended
+     * deadline and there was no jank visible to the user.
+     * </p>
+     **/
+    public static final int DEADLINE = 13;
+
     private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
 
     /**
@@ -175,6 +193,8 @@
             FIRST_DRAW_FRAME,
             INTENDED_VSYNC_TIMESTAMP,
             VSYNC_TIMESTAMP,
+            GPU_DURATION,
+            DEADLINE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Metric {}
@@ -205,6 +225,8 @@
             Index.ISSUE_DRAW_COMMANDS_START,
             Index.SWAP_BUFFERS,
             Index.FRAME_COMPLETED,
+            Index.GPU_COMPLETED,
+            Index.SWAP_BUFFERS_COMPLETED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Index {
@@ -224,8 +246,10 @@
         int ISSUE_DRAW_COMMANDS_START = 13;
         int SWAP_BUFFERS = 14;
         int FRAME_COMPLETED = 15;
+        int GPU_COMPLETED = 18;
+        int SWAP_BUFFERS_COMPLETED = 19;
 
-        int FRAME_STATS_COUNT = 19; // must always be last and in sync with
+        int FRAME_STATS_COUNT = 20; // must always be last and in sync with
                                     // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
     }
 
@@ -251,9 +275,19 @@
         // COMMAND_ISSUE
         Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
         // SWAP_BUFFERS
-        Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
+        Index.SWAP_BUFFERS, Index.SWAP_BUFFERS_COMPLETED,
         // TOTAL_DURATION
         Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+        // RESERVED for FIRST_DRAW_FRAME
+        0, 0,
+        // RESERVED forINTENDED_VSYNC_TIMESTAMP
+        0, 0,
+        // RESERVED VSYNC_TIMESTAMP
+        0, 0,
+        // GPU_DURATION
+        Index.SWAP_BUFFERS, Index.GPU_COMPLETED,
+        // DEADLINE
+        Index.INTENDED_VSYNC, Index.FRAME_DEADLINE,
     };
 
     /**
@@ -294,7 +328,7 @@
      * @return the value of the metric or -1 if it is not available.
      */
     public long getMetric(@Metric int id) {
-        if (id < UNKNOWN_DELAY_DURATION || id > VSYNC_TIMESTAMP) {
+        if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
             return -1;
         }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6a629ca..66b9617 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2404,7 +2404,7 @@
         if (Float.isNaN(brightness) || brightness > 1.0f
                 || (brightness < 0.0f && brightness != -1.0f)) {
             throw new IllegalArgumentException("brightness must be a number between 0.0f and 1.0f,"
-                    + " or -1 to turn the backlight off.");
+                    + " or -1 to turn the backlight off: " + brightness);
         }
         return nativeSetDisplayBrightness(displayToken, brightness);
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba78f96..44d4d6b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24032,6 +24032,7 @@
      * @see #getBackgroundTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mBackgroundTint == null) {
             mBackgroundTint = new TintInfo();
@@ -24294,6 +24295,7 @@
      * @see #getForegroundTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setForegroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mForegroundInfo == null) {
             mForegroundInfo = new ForegroundInfo();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7b2bb73..a8fff8b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -749,7 +749,7 @@
         @Override
         public boolean hasActiveConnection(View view) {
             synchronized (mH) {
-                if (!hasServedByInputMethodLocked(view)) {
+                if (!hasServedByInputMethodLocked(view) || mCurMethod == null) {
                     return false;
                 }
 
@@ -765,6 +765,17 @@
         return mDelegate;
     }
 
+    /**
+     * Checks whether the active input connection (if any) is for the given view.
+     *
+     * @hide
+     * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+     */
+    @TestApi
+    public boolean hasActiveInputConnection(@Nullable View view) {
+        return mDelegate.hasActiveConnection(view);
+    }
+
     private View getServedViewLocked() {
         return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
     }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 2e75834..bc2b221 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -31,6 +31,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
@@ -261,7 +262,7 @@
             // us honest and minimize usage of WebView internals when binding the proxy.
             if (sProviderInstance != null) return sProviderInstance;
 
-            sTimestamps[WEBVIEW_LOAD_START] = System.currentTimeMillis();
+            sTimestamps[WEBVIEW_LOAD_START] = SystemClock.elapsedRealtime();
             final int uid = android.os.Process.myUid();
             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                     || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -401,7 +402,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
-            sTimestamps[CREATE_CONTEXT_START] = System.currentTimeMillis();
+            sTimestamps[CREATE_CONTEXT_START] = SystemClock.elapsedRealtime();
             try {
                 // Construct an app context to load the Java code into the current app.
                 Context webViewContext = initialApplication.createApplicationContext(
@@ -410,7 +411,7 @@
                 sPackageInfo = newPackageInfo;
                 return webViewContext;
             } finally {
-                sTimestamps[CREATE_CONTEXT_END] = System.currentTimeMillis();
+                sTimestamps[CREATE_CONTEXT_END] = SystemClock.elapsedRealtime();
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -436,26 +437,26 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
-                sTimestamps[ADD_ASSETS_START] = System.currentTimeMillis();
+                sTimestamps[ADD_ASSETS_START] = SystemClock.elapsedRealtime();
                 for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
                     initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
                 }
                 sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 ClassLoader clazzLoader = webViewContext.getClassLoader();
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                 sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                         getWebViewLibrary(sPackageInfo.applicationInfo));
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
                 sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 try {
                     return getWebViewProviderClass(clazzLoader);
                 } finally {
-                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = System.currentTimeMillis();
+                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = SystemClock.elapsedRealtime();
                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 }
             } catch (ClassNotFoundException e) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index ed20d26..0a08ccd 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -695,6 +695,7 @@
      * @see #getImageTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
         mDrawableBlendMode = blendMode;
         mHasDrawableBlendMode = true;
diff --git a/core/java/android/widget/ImeAwareEditText.java b/core/java/android/widget/ImeAwareEditText.java
index 9cd4585..0d98085 100644
--- a/core/java/android/widget/ImeAwareEditText.java
+++ b/core/java/android/widget/ImeAwareEditText.java
@@ -80,7 +80,7 @@
 
     public void scheduleShowSoftInput() {
         final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm.isActive(this)) {
+        if (imm.hasActiveInputConnection(this)) {
             // This means that ImeAwareEditText is already connected to the IME.
             // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
             mHasPendingShowSoftInputRequest = false;
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 67216f59..a44808e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -823,6 +823,7 @@
      * @see #setIndeterminateTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1132,6 +1133,7 @@
      * @see #getProgressTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setProgressTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1248,6 +1250,7 @@
      * @see #setProgressBackgroundTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1360,6 +1363,7 @@
      * @see #setSecondaryProgressTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 4e3d99b..81572b5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,6 +47,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.BlendMode;
 import android.graphics.Outline;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
@@ -1054,6 +1055,8 @@
                 return ColorStateList.class;
             case BaseReflectionAction.ICON:
                 return Icon.class;
+            case BaseReflectionAction.BLEND_MODE:
+                return BlendMode.class;
             default:
                 return null;
         }
@@ -1400,6 +1403,7 @@
         static final int INTENT = 14;
         static final int COLOR_STATE_LIST = 15;
         static final int ICON = 16;
+        static final int BLEND_MODE = 17;
 
         @UnsupportedAppUsage
         String methodName;
@@ -1589,6 +1593,10 @@
                     break;
                 case ICON:
                     this.value = in.readTypedObject(Icon.CREATOR);
+                    break;
+                case BLEND_MODE:
+                    this.value = BlendMode.fromValue(in.readInt());
+                    break;
                 default:
                     break;
             }
@@ -1632,6 +1640,9 @@
                 case BUNDLE:
                     out.writeBundle((Bundle) this.value);
                     break;
+                case BLEND_MODE:
+                    out.writeInt(BlendMode.toValue((BlendMode) this.value));
+                    break;
                 case URI:
                 case BITMAP:
                 case INTENT:
@@ -4132,6 +4143,18 @@
     }
 
     /**
+     * Call a method taking one BlendMode on a view in the layout for this RemoteViews.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param value The value to pass to the method.
+     */
+    public void setBlendMode(@IdRes int viewId, @NonNull String methodName,
+            @Nullable BlendMode value) {
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value));
+    }
+
+    /**
      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6983d35c..d72a0ec 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,11 +49,12 @@
         "libhwui",
         "liblog",
         "libminikin",
-        "libnativehelper",
         "libz",
         "libziparchive",
     ],
 
+    static_libs: ["libnativehelper_lazy"],
+
     export_include_dirs: [
         ".",
         "include",
@@ -272,12 +273,13 @@
                 "libstatspull",
             ],
             export_shared_lib_headers: [
-                // AndroidRuntime.h depends on nativehelper/jni.h
-                "libnativehelper",
-
                 // our headers include libnativewindow's public headers
                 "libnativewindow",
             ],
+            export_static_lib_headers: [
+                // AndroidRuntime.h depends on nativehelper/jni.h
+                "libnativehelper_lazy",
+            ],
             header_libs: [
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6456e4e..62e077f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1313,11 +1313,13 @@
         android:protectionLevel="dangerous|instant" />
 
     <!-- ====================================================================== -->
-    <!-- Permissions for accessing the UCE Service                              -->
+    <!-- Permissions for accessing the vendor UCE Service                              -->
     <!-- ====================================================================== -->
 
     <!-- @hide Allows an application to Access UCE-Presence.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1325,6 +1327,8 @@
 
     <!-- @hide Allows an application to Access UCE-OPTIONS.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -2011,6 +2015,12 @@
     <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to ultra wideband device.
+     <p>Not for use by third-party applications.
+     @hide -->
+    <permission android:name="android.permission.UWB_PRIVILEGED"
+                android:protectionLevel="signature|privileged" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -2457,6 +2467,15 @@
     <permission android:name="android.permission.BIND_GBA_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+         <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+         Contacts app roles.
+         <p>Protection level: internal|role
+         @SystemApi
+         @hide -->
+    <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+        android:protectionLevel="internal|role" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -5496,6 +5515,12 @@
     <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
         android:protectionLevel="signature|recents" />
 
+    <!-- @hide @SystemApi Allows an application to retrieve whether shortcut is backed by a
+         Conversation.
+         TODO(b/180412052): STOPSHIP: Define a role so it can be granted to Shell and AiAi. -->
+    <permission android:name="android.permission.READ_PEOPLE_DATA"
+                android:protectionLevel="signature|appPredictor|recents"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 67b810e..69bb20c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8010,6 +8010,14 @@
         <attr name="minResizeWidth" format="dimension"/>
         <!-- Minimum height that the AppWidget can be resized to. -->
         <attr name="minResizeHeight" format="dimension"/>
+        <!-- Maximum width that the AppWidget can be resized to. -->
+        <attr name="maxResizeWidth" format="dimension"/>
+        <!-- Maximum height that the AppWidget can be resized to. -->
+        <attr name="maxResizeHeight" format="dimension"/>
+        <!-- Default width of the AppWidget in units of launcher grid cells. -->
+        <attr name="targetCellWidth" format="integer"/>
+        <!-- Default height of the AppWidget in units of launcher grid cells. -->
+        <attr name="targetCellHeight" format="integer"/>
         <!-- Update period in milliseconds, or 0 if the AppWidget will update itself. -->
         <attr name="updatePeriodMillis" format="integer" />
         <!-- A resource id of a layout. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd048f3..faa2157 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1948,6 +1948,8 @@
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
     <!-- The name of the package that will hold the system shell role. -->
     <string name="config_systemShell" translatable="false">com.android.shell</string>
+    <!-- The name of the package that will hold the system contacts role. -->
+    <string name="config_systemContacts" translatable="false">com.android.contacts</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -2509,10 +2511,9 @@
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
 
     <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
-         Default is carrier_config, but it should be set to modem if the modem is returning
-         predictive (instead of instantaneous) bandwidth estimate.
-         Values are carrier_config and modem. -->
-    <string name="config_bandwidthEstimateSource">carrier_config</string>
+         Default is bandwidth_estimator.
+         Values are bandwidth_estimator, carrier_config and modem. -->
+    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
 
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fdef08b..e43aa3d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3072,6 +3072,10 @@
     <public name="windowSplashScreenBrandingImage"/>
     <public name="splashScreenTheme" />
     <public name="rippleStyle" />
+    <public name="maxResizeWidth" />
+    <public name="maxResizeHeight" />
+    <public name="targetCellWidth" />
+    <public name="targetCellHeight" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3134,6 +3138,8 @@
     <public name="config_systemAutomotiveProjection" />
     <!-- @hide @SystemApi -->
     <public name="config_systemShell" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemContacts" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
index be38260..bc7be1b 100644
--- a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -16,14 +16,8 @@
 
 package android.provider;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.testng.Assert.assertThrows;
 
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
-
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Test;
@@ -71,50 +65,5 @@
                         SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
         );
     }
-
-    @Test
-    public void nameValidationResult_isValid_validNames() {
-        assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
-        assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
-        assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
-        assertThat(
-                new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
-    }
-
-    @Test
-    public void nameValidationResult_isValid_invalidNames() {
-        assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
-        assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
-        NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
-                "A b c", 5, 5);
-        assertThat(unsupportedCharactersResult.isValid()).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
-    }
-
-    @Test
-    public void nameValidationResult_parcel() {
-        ContentValues values = new ContentValues();
-        values.put("name", "Name");
-        values.put("phone_number", "123");
-
-        NameValidationResult result;
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
-            parcel.setDataPosition(0);
-            result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
-        } finally {
-            parcel.recycle();
-        }
-
-        assertThat(result.getName()).isEqualTo("name");
-        assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
-        assertThat(result.getEncodedLength()).isEqualTo(1);
-        assertThat(result.getMaxEncodedLength()).isEqualTo(2);
-    }
 }
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fe4d0cf..8fd5d80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -478,6 +478,8 @@
         <permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
         <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
         <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
+        <!-- Permission required for CTS test - PeopleManagerTest -->
+        <permission name="android.permission.READ_PEOPLE_DATA" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 2b0d7e5..c79c12c 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -279,8 +279,8 @@
  * }
  */
 public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
-
-    private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
+    private static final X500Principal DEFAULT_CERT_SUBJECT =
+            new X500Principal("CN=Android Keystore Key");
     private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
     private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
@@ -317,6 +317,7 @@
     private final boolean mUnlockedDeviceRequired;
     private final boolean mCriticalToDeviceEncryption;
     private final int mMaxUsageCount;
+    private final String mAttestKeyAlias;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -358,7 +359,8 @@
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
             boolean criticalToDeviceEncryption,
-            int maxUsageCount) {
+            int maxUsageCount,
+            String attestKeyAlias) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -413,6 +415,7 @@
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mCriticalToDeviceEncryption = criticalToDeviceEncryption;
         mMaxUsageCount = maxUsageCount;
+        mAttestKeyAlias = attestKeyAlias;
     }
 
     /**
@@ -869,6 +872,18 @@
     }
 
     /**
+     * Returns the alias of the attestation key that will be used to sign the attestation
+     * certificate of the generated key.  Note that an attestation certificate will only be
+     * generated if an attestation challenge is set.
+     *
+     * @see Builder#setAttestKeyAlias(String)
+     */
+    @Nullable
+    public String getAttestKeyAlias() {
+        return mAttestKeyAlias;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -906,6 +921,7 @@
         private boolean mUnlockedDeviceRequired = false;
         private boolean mCriticalToDeviceEncryption = false;
         private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -975,6 +991,7 @@
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
             mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
             mMaxUsageCount = sourceSpec.getMaxUsageCount();
+            mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
         }
 
         /**
@@ -1695,6 +1712,28 @@
         }
 
         /**
+         * Sets the alias of the attestation key that will be used to sign the attestation
+         * certificate for the generated key pair, if an attestation challenge is set with {@link
+         * #setAttestationChallenge}.  If an attestKeyAlias is set but no challenge, {@link
+         * java.security.KeyPairGenerator#initialize} will throw {@link
+         * java.security.InvalidAlgorithmParameterException}.
+         *
+         * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an
+         * appropriate system-provided attestation signing key.  If not null, the alias must
+         * reference an Android Keystore Key that was created with {@link
+         * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw
+         * {@link java.security.InvalidAlgorithmParameterException}.
+         *
+         * @param attestKeyAlias the alias of the attestation key to be used to sign the
+         *        attestation certificate.
+         */
+        @NonNull
+        public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) {
+            mAttestKeyAlias = attestKeyAlias;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1731,7 +1770,8 @@
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
                     mCriticalToDeviceEncryption,
-                    mMaxUsageCount);
+                    mMaxUsageCount,
+                    mAttestKeyAlias);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 293ab05..7b0fa91 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -70,6 +70,7 @@
             PURPOSE_VERIFY,
             PURPOSE_WRAP_KEY,
             PURPOSE_AGREE_KEY,
+            PURPOSE_ATTEST_KEY,
     })
     public @interface PurposeEnum {}
 
@@ -113,6 +114,13 @@
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
     /**
+     * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning
+     * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In
+     * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys.
+     */
+    public static final int PURPOSE_ATTEST_KEY = 1 << 7;
+
+    /**
      * @hide
      */
     public static abstract class Purpose {
@@ -132,6 +140,8 @@
                     return KeymasterDefs.KM_PURPOSE_WRAP;
                 case PURPOSE_AGREE_KEY:
                     return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
+                case PURPOSE_ATTEST_KEY:
+                    return KeymasterDefs.KM_PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
@@ -151,6 +161,8 @@
                     return PURPOSE_WRAP_KEY;
                 case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
                     return PURPOSE_AGREE_KEY;
+                case KeymasterDefs.KM_PURPOSE_ATTEST_KEY:
+                    return PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index aaa3715..fe92270 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -588,7 +588,8 @@
         private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
         private boolean mCriticalToDeviceEncryption = false;
         private boolean mIsStrongBoxBacked = false;
-        private  int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 1f2f853..c20cf01 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -110,6 +110,7 @@
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
         out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
         out.writeInt(mSpec.getMaxUsageCount());
+        out.writeString(mSpec.getAttestKeyAlias());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -170,6 +171,7 @@
         final boolean unlockedDeviceRequired = in.readBoolean();
         final boolean criticalToDeviceEncryption = in.readBoolean();
         final int maxUsageCount = in.readInt();
+        final String attestKeyAlias = in.readString();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -205,7 +207,8 @@
                 userConfirmationRequired,
                 unlockedDeviceRequired,
                 criticalToDeviceEncryption,
-                maxUsageCount);
+                maxUsageCount,
+                attestKeyAlias);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 4d27c34..b3bfd6a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,9 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.KeyPurpose;
 import android.hardware.security.keymint.SecurityLevel;
+import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
@@ -37,9 +39,11 @@
 import android.security.keystore.KeymasterUtils;
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.StrongBoxUnavailableException;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreSecurityLevel;
 import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
 import android.telephony.TelephonyManager;
@@ -61,6 +65,7 @@
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -69,6 +74,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Provides a way to create instances of a KeyPair which will be placed in the
@@ -153,6 +159,7 @@
     private int mKeymasterAlgorithm = -1;
     private int mKeySizeBits;
     private SecureRandom mRng;
+    private KeyDescriptor mAttestKeyDescriptor;
 
     private int[] mKeymasterPurposes;
     private int[] mKeymasterBlockModes;
@@ -197,83 +204,9 @@
                 // Legacy/deprecated spec
                 KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
                 try {
-                    KeyGenParameterSpec.Builder specBuilder;
-                    String specKeyAlgorithm = legacySpec.getKeyType();
-                    if (specKeyAlgorithm != null) {
-                        // Spec overrides the generator's default key algorithm
-                        try {
-                            keymasterAlgorithm =
-                                    KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
-                                            specKeyAlgorithm);
-                        } catch (IllegalArgumentException e) {
-                            throw new InvalidAlgorithmParameterException(
-                                    "Invalid key type in parameters", e);
-                        }
-                    }
-                    switch (keymasterAlgorithm) {
-                        case KeymasterDefs.KM_ALGORITHM_EC:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            // MD5 was never offered for Android Keystore for ECDSA.
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            break;
-                        case KeymasterDefs.KM_ALGORITHM_RSA:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_ENCRYPT
-                                    | KeyProperties.PURPOSE_DECRYPT
-                                    | KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_MD5,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            // Authorized to be used with any encryption and signature padding
-                            // schemes (including no padding).
-                            specBuilder.setEncryptionPaddings(
-                                    KeyProperties.ENCRYPTION_PADDING_NONE,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
-                            specBuilder.setSignaturePaddings(
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PSS);
-                            // Disable randomized encryption requirement to support encryption
-                            // padding NONE above.
-                            specBuilder.setRandomizedEncryptionRequired(false);
-                            break;
-                        default:
-                            throw new ProviderException(
-                                    "Unsupported algorithm: " + mKeymasterAlgorithm);
-                    }
-
-                    if (legacySpec.getKeySize() != -1) {
-                        specBuilder.setKeySize(legacySpec.getKeySize());
-                    }
-                    if (legacySpec.getAlgorithmParameterSpec() != null) {
-                        specBuilder.setAlgorithmParameterSpec(
-                                legacySpec.getAlgorithmParameterSpec());
-                    }
-                    specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
-                    specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
-                    specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
-                    specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
-                    specBuilder.setUserAuthenticationRequired(false);
-
-                    spec = specBuilder.build();
+                    keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm,
+                            legacySpec);
+                    spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm);
                 } catch (NullPointerException | IllegalArgumentException e) {
                     throw new InvalidAlgorithmParameterException(e);
                 }
@@ -342,6 +275,10 @@
             mJcaKeyAlgorithm = jcaKeyAlgorithm;
             mRng = random;
             mKeyStore = KeyStore2.getInstance();
+
+            mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
+            checkAttestKeyPurpose(spec);
+
             success = true;
         } finally {
             if (!success) {
@@ -350,6 +287,156 @@
         }
     }
 
+    private void checkAttestKeyPurpose(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0
+                && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) {
+            throw new InvalidAlgorithmParameterException(
+                    "PURPOSE_ATTEST_KEY may not be specified with any other purposes");
+        }
+    }
+
+    private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestKeyAlias() != null) {
+            KeyDescriptor attestKeyDescriptor = new KeyDescriptor();
+            attestKeyDescriptor.domain = Domain.APP;
+            attestKeyDescriptor.alias = spec.getAttestKeyAlias();
+            try {
+                KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor);
+                checkAttestKeyChallenge(spec);
+                checkAttestKeyPurpose(attestKey.metadata.authorizations);
+                checkAttestKeySecurityLevel(spec, attestKey);
+            } catch (KeyStoreException e) {
+                throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e);
+            }
+            return attestKeyDescriptor;
+        }
+        return null;
+    }
+
+    private void checkAttestKeyChallenge(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestationChallenge() == null) {
+            throw new InvalidAlgorithmParameterException(
+                    "AttestKey specified but no attestation challenge provided");
+        }
+    }
+
+    private void checkAttestKeyPurpose(Authorization[] keyAuths)
+            throws InvalidAlgorithmParameterException {
+        Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE
+                && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY;
+
+        if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) {
+            throw new InvalidAlgorithmParameterException(
+                    ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+        }
+    }
+
+    private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key)
+            throws InvalidAlgorithmParameterException {
+        boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX;
+        if (spec.isStrongBoxBacked() != attestKeyInStrongBox) {
+            if (attestKeyInStrongBox) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign non-StrongBox key with "
+                                + "StrongBox attestKey");
+
+            } else {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign StrongBox key with "
+                                + "non-StrongBox attestKey");
+            }
+        }
+    }
+
+    private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm,
+            KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException {
+        String specKeyAlgorithm = legacySpec.getKeyType();
+        if (specKeyAlgorithm != null) {
+            // Spec overrides the generator's default key algorithm
+            try {
+                keymasterAlgorithm =
+                        KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+                                specKeyAlgorithm);
+            } catch (IllegalArgumentException e) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid key type in parameters", e);
+            }
+        }
+        return keymasterAlgorithm;
+    }
+
+    private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec,
+            int keymasterAlgorithm) {
+        KeyGenParameterSpec.Builder specBuilder;
+        switch (keymasterAlgorithm) {
+            case KeymasterDefs.KM_ALGORITHM_EC:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                // MD5 was never offered for Android Keystore for ECDSA.
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                break;
+            case KeymasterDefs.KM_ALGORITHM_RSA:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_ENCRYPT
+                        | KeyProperties.PURPOSE_DECRYPT
+                        | KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_MD5,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                // Authorized to be used with any encryption and signature padding
+                // schemes (including no padding).
+                specBuilder.setEncryptionPaddings(
+                        KeyProperties.ENCRYPTION_PADDING_NONE,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+                specBuilder.setSignaturePaddings(
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+                // Disable randomized encryption requirement to support encryption
+                // padding NONE above.
+                specBuilder.setRandomizedEncryptionRequired(false);
+                break;
+            default:
+                throw new ProviderException(
+                        "Unsupported algorithm: " + mKeymasterAlgorithm);
+        }
+
+        if (legacySpec.getKeySize() != -1) {
+            specBuilder.setKeySize(legacySpec.getKeySize());
+        }
+        if (legacySpec.getAlgorithmParameterSpec() != null) {
+            specBuilder.setAlgorithmParameterSpec(
+                    legacySpec.getAlgorithmParameterSpec());
+        }
+        specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+        specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+        specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+        specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+        specBuilder.setUserAuthenticationRequired(false);
+
+        return specBuilder.build();
+    }
+
     private void resetAll() {
         mEntryAlias = null;
         mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
@@ -464,7 +551,7 @@
         try {
             KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
 
-            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
+            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor,
                     constructKeyGenerationArguments(), flags, additionalEntropy);
 
             AndroidKeyStorePublicKey publicKey =
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f481228..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -357,12 +357,13 @@
         "libharfbuzz_ng",
         "liblog",
         "libminikin",
-        "libnativehelper",
         "libz",
         "libziparchive",
         "libjpeg",
     ],
 
+    static_libs: ["libnativehelper_lazy"],
+
     target: {
         android: {
             srcs: [ // sources that depend on android only libraries
@@ -444,6 +445,7 @@
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
         "hwui/Bitmap.cpp",
+        "hwui/BlurDrawLooper.cpp",
         "hwui/Canvas.cpp",
         "hwui/ImageDecoder.cpp",
         "hwui/MinikinSkia.cpp",
@@ -480,6 +482,8 @@
 
     target: {
         android: {
+            header_libs: ["libandroid_headers_private" ],
+
             srcs: [
                 "hwui/AnimatedImageThread.cpp",
                 "pipeline/skia/ATraceMemoryDump.cpp",
@@ -567,6 +571,7 @@
     name: "hwui_test_defaults",
     defaults: ["hwui_defaults"],
     test_suites: ["device-tests"],
+    header_libs: ["libandroid_headers_private"],
     target: {
         android: {
             shared_libs: [
@@ -604,7 +609,6 @@
     shared_libs: [
         "libmemunreachable",
     ],
-
     srcs: [
         "tests/unit/main.cpp",
         "tests/unit/ABitmapTests.cpp",
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8b20492..ce9b288 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -42,7 +42,7 @@
         "GpuCompleted",
 };
 
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
               "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index ee7d15a..45a367f 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -55,6 +55,7 @@
     QueueBufferDuration,
 
     GpuCompleted,
+    SwapBuffersCompleted,
 
     // Must be the last value!
     // Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT
@@ -120,6 +121,10 @@
 
     void markSwapBuffers() { set(FrameInfoIndex::SwapBuffers) = systemTime(SYSTEM_TIME_MONOTONIC); }
 
+    void markSwapBuffersCompleted() {
+        set(FrameInfoIndex::SwapBuffersCompleted) = systemTime(SYSTEM_TIME_MONOTONIC);
+    }
+
     void markFrameCompleted() { set(FrameInfoIndex::FrameCompleted) = systemTime(SYSTEM_TIME_MONOTONIC); }
 
     void addFlag(int frameInfoFlag) {
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
index 75b8038..0643e79 100644
--- a/libs/hwui/FrameMetricsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -16,14 +16,16 @@
 
 #pragma once
 
+#include <utils/Mutex.h>
 #include <utils/Log.h>
 #include <utils/RefBase.h>
 
+#include <ui/FatVector.h>
+
 #include "FrameInfo.h"
 #include "FrameMetricsObserver.h"
 
 #include <string.h>
-#include <vector>
 
 namespace android {
 namespace uirenderer {
@@ -32,9 +34,13 @@
 public:
     FrameMetricsReporter() {}
 
-    void addObserver(FrameMetricsObserver* observer) { mObservers.push_back(observer); }
+    void addObserver(FrameMetricsObserver* observer) {
+        std::lock_guard lock(mObserversLock);
+        mObservers.push_back(observer);
+    }
 
     bool removeObserver(FrameMetricsObserver* observer) {
+        std::lock_guard lock(mObserversLock);
         for (size_t i = 0; i < mObservers.size(); i++) {
             if (mObservers[i].get() == observer) {
                 mObservers.erase(mObservers.begin() + i);
@@ -44,16 +50,28 @@
         return false;
     }
 
-    bool hasObservers() { return mObservers.size() > 0; }
+    bool hasObservers() {
+        std::lock_guard lock(mObserversLock);
+        return mObservers.size() > 0;
+    }
 
     void reportFrameMetrics(const int64_t* stats) {
-        for (size_t i = 0; i < mObservers.size(); i++) {
-            mObservers[i]->notify(stats);
+        FatVector<sp<FrameMetricsObserver>, 10> copy;
+        {
+            std::lock_guard lock(mObserversLock);
+            copy.reserve(mObservers.size());
+            for (size_t i = 0; i < mObservers.size(); i++) {
+                copy.push_back(mObservers[i]);
+            }
+        }
+        for (size_t i = 0; i < copy.size(); i++) {
+            copy[i]->notify(stats);
         }
     }
 
 private:
-    std::vector<sp<FrameMetricsObserver> > mObservers;
+    FatVector<sp<FrameMetricsObserver>, 10> mObservers GUARDED_BY(mObserversLock);
+    std::mutex mObserversLock;
 };
 
 }  // namespace uirenderer
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index ccce403..4a2e30d 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -79,7 +79,9 @@
 // and filter it out of the frame profile data
 static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
 
-JankTracker::JankTracker(ProfileDataContainer* globalData) {
+JankTracker::JankTracker(ProfileDataContainer* globalData)
+        : mData(globalData->getDataMutex())
+        , mDataMutex(globalData->getDataMutex()) {
     mGlobalData = globalData;
     nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
@@ -107,6 +109,8 @@
 }
 
 void JankTracker::finishFrame(const FrameInfo& frame) {
+    std::lock_guard lock(mDataMutex);
+
     // Fast-path for jank-free frames
     int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
     if (mDequeueTimeForgiveness && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
@@ -125,7 +129,11 @@
         }
     }
 
-    LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
+    LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64 " start=%" PRIi64
+                        " gpuComplete=%" PRIi64, totalDuration,
+                        frame[FrameInfoIndex::IntendedVsync],
+                        frame[FrameInfoIndex::GpuCompleted]);
+
     mData->reportFrame(totalDuration);
     (*mGlobalData)->reportFrame(totalDuration);
 
@@ -188,6 +196,7 @@
 
 void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
                            const ProfileData* data) {
+
     if (description) {
         switch (description->type) {
             case JankTrackerType::Generic:
@@ -227,6 +236,7 @@
 }
 
 void JankTracker::reset() {
+    std::lock_guard lock(mDataMutex);
     mFrames.clear();
     mData->reset();
     (*mGlobalData)->reset();
@@ -235,6 +245,7 @@
 }
 
 void JankTracker::finishGpuDraw(const FrameInfo& frame) {
+    std::lock_guard lock(mDataMutex);
     int64_t totalGPUDrawTime = frame.gpuDrawTime();
     if (totalGPUDrawTime >= 0) {
         mData->reportGPUFrame(totalGPUDrawTime);
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index b3fbbfe..0964553 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -84,12 +84,15 @@
     // This is only used if we are in pipelined mode and are using HWC2,
     // otherwise it's 0.
     nsecs_t mDequeueTimeForgiveness = 0;
-    ProfileDataContainer mData;
-    ProfileDataContainer* mGlobalData;
+    ProfileDataContainer mData GUARDED_BY(mDataMutex);
+    ProfileDataContainer* mGlobalData GUARDED_BY(mDataMutex);
     ProfileDataDescription mDescription;
 
     // Ring buffer large enough for 2 seconds worth of frames
     RingBuffer<FrameInfo, 120> mFrames;
+
+    // Mutex to protect acccess to mData and mGlobalData obtained from mGlobalData->getDataMutex
+    std::mutex& mDataMutex;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
index 38e0f0a..41afc0e 100644
--- a/libs/hwui/ProfileDataContainer.cpp
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -38,6 +38,8 @@
 }
 
 void ProfileDataContainer::rotateStorage() {
+    std::lock_guard lock(mJankDataMutex);
+
     // If we are mapped we want to stop using the ashmem backend and switch to malloc
     // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
     // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
@@ -50,6 +52,7 @@
 }
 
 void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+    std::lock_guard lock(mJankDataMutex);
     int regionSize = ashmem_get_size_region(ashmemfd);
     if (regionSize < 0) {
         int err = errno;
@@ -70,7 +73,9 @@
         return;
     }
 
-    newData->mergeWith(*mData);
+    if (mData != nullptr) {
+        newData->mergeWith(*mData);
+    }
     freeData();
     mData = newData;
     mIsMapped = true;
diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h
index a398694..a61b8dc 100644
--- a/libs/hwui/ProfileDataContainer.h
+++ b/libs/hwui/ProfileDataContainer.h
@@ -19,6 +19,9 @@
 #include "ProfileData.h"
 #include "utils/Macros.h"
 
+#include <mutex>
+#include <utils/Mutex.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -26,7 +29,8 @@
     PREVENT_COPY_AND_ASSIGN(ProfileDataContainer);
 
 public:
-    explicit ProfileDataContainer() {}
+    explicit ProfileDataContainer(std::mutex& jankDataMutex)
+            : mData(new ProfileData()), mJankDataMutex(jankDataMutex) {}
 
     ~ProfileDataContainer() { freeData(); }
 
@@ -36,13 +40,16 @@
     ProfileData* get() { return mData; }
     ProfileData* operator->() { return mData; }
 
+    std::mutex& getDataMutex() { return mJankDataMutex; }
+
 private:
     void freeData();
 
     // By default this will use malloc memory. It may be moved later to ashmem
     // if there is shared space for it and a request comes in to do that.
-    ProfileData* mData = new ProfileData;
+    ProfileData* mData GUARDED_BY(mJankDataMutex);
     bool mIsMapped = false;
+    std::mutex& mJankDataMutex;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8fddf71..1fddac4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -463,9 +463,7 @@
 }
 
 void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPoint(x, y, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); });
 }
 
 void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) {
@@ -493,9 +491,7 @@
 
 void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawRegion(region, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); });
 }
 
 void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@@ -509,24 +505,18 @@
 
 void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
                                 const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawDRRect(outer, inner, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); });
 }
 
 void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) {
     if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawCircle(x, y, radius, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); });
 }
 
 void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawOval(oval, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); });
 }
 
 void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -547,9 +537,7 @@
     if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
         return;
     }
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPath(path, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); });
 }
 
 void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 155f6df..eac3f22 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -208,29 +208,21 @@
      */
     PaintCoW&& filterPaint(PaintCoW&& paint) const;
 
+    // proc(const SkPaint& modifiedPaint)
     template <typename Proc> void apply_looper(const Paint* paint, Proc proc) {
         SkPaint skp;
-        SkDrawLooper* looper = nullptr;
+        BlurDrawLooper* looper = nullptr;
         if (paint) {
             skp = *filterPaint(paint);
             looper = paint->getLooper();
         }
         if (looper) {
-            SkSTArenaAlloc<256> alloc;
-            SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-            if (ctx) {
-                SkDrawLooper::Context::Info info;
-                for (;;) {
-                    SkPaint p = skp;
-                    if (!ctx->next(&info, &p)) {
-                        break;
-                    }
-                    mCanvas->save();
-                    mCanvas->translate(info.fTranslate.fX, info.fTranslate.fY);
-                    proc(p);
-                    mCanvas->restore();
-                }
-            }
+            looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+                mCanvas->save();
+                mCanvas->translate(offset.fX, offset.fY);
+                proc(modifiedPaint);
+                mCanvas->restore();
+            });
         } else {
             proc(skp);
         }
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
new file mode 100644
index 0000000..27a038d
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BlurDrawLooper.h"
+#include <SkMaskFilter.h>
+
+namespace android {
+
+BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset)
+        : mColor(color), mBlurSigma(blurSigma), mOffset(offset) {}
+
+BlurDrawLooper::~BlurDrawLooper() = default;
+
+SkPoint BlurDrawLooper::apply(SkPaint* paint) const {
+    paint->setColor(mColor);
+    if (mBlurSigma > 0) {
+        paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true));
+    }
+    return mOffset;
+}
+
+sk_sp<BlurDrawLooper> BlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, float blurSigma,
+                                           SkPoint offset) {
+    if (cs) {
+        SkPaint tmp;
+        tmp.setColor(color, cs);  // converts color to sRGB
+        color = tmp.getColor4f();
+    }
+    return sk_sp<BlurDrawLooper>(new BlurDrawLooper(color, blurSigma, offset));
+}
+
+}  // namespace android
diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h
new file mode 100644
index 0000000..7e6786f
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+
+class SkColorSpace;
+
+namespace android {
+
+class BlurDrawLooper : public SkRefCnt {
+public:
+    static sk_sp<BlurDrawLooper> Make(SkColor4f, SkColorSpace*, float blurSigma, SkPoint offset);
+
+    ~BlurDrawLooper() override;
+
+    // proc(SkPoint offset, const SkPaint& modifiedPaint)
+    template <typename DrawProc>
+    void apply(const SkPaint& paint, DrawProc proc) const {
+        SkPaint p(paint);
+        proc(this->apply(&p), p);  // draw the shadow
+        proc({0, 0}, paint);       // draw the original (on top)
+    }
+
+private:
+    const SkColor4f mColor;
+    const float mBlurSigma;
+    const SkPoint mOffset;
+
+    SkPoint apply(SkPaint* paint) const;
+
+    BlurDrawLooper(SkColor4f, float, SkPoint);
+};
+
+}  // namespace android
+
+#endif  // ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 05bae5c..d9c9eee 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,11 +17,11 @@
 #ifndef ANDROID_GRAPHICS_PAINT_H_
 #define ANDROID_GRAPHICS_PAINT_H_
 
+#include "BlurDrawLooper.h"
 #include "Typeface.h"
 
 #include <cutils/compiler.h>
 
-#include <SkDrawLooper.h>
 #include <SkFont.h>
 #include <SkPaint.h>
 #include <string>
@@ -59,8 +59,8 @@
     SkFont& getSkFont() { return mFont; }
     const SkFont& getSkFont() const { return mFont; }
 
-    SkDrawLooper* getLooper() const { return mLooper.get(); }
-    void setLooper(sk_sp<SkDrawLooper> looper) { mLooper = std::move(looper); }
+    BlurDrawLooper* getLooper() const { return mLooper.get(); }
+    void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); }
 
     // These shadow the methods on SkPaint, but we need to so we can keep related
     // attributes in-sync.
@@ -155,7 +155,7 @@
 
 private:
     SkFont mFont;
-    sk_sp<SkDrawLooper> mLooper;
+    sk_sp<BlurDrawLooper> mLooper;
 
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 3c86b28..bcec0fa 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -25,7 +25,6 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
-#include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkFont.h"
 #include "SkFontMetrics.h"
@@ -39,6 +38,7 @@
 #include "unicode/ushape.h"
 #include "utils/Blur.h"
 
+#include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
 #include <hwui/Paint.h>
@@ -964,13 +964,13 @@
         }
         else {
             SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
-            paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+            paint->setLooper(BlurDrawLooper::Make(color, cs.get(), sigma, {dx, dy}));
         }
     }
 
     static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
+        return paint->getLooper() != nullptr;
     }
 
     static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index ee7c4d8..b288402 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,28 +194,20 @@
     return filterPaint(std::move(paint));
 }
 
-static SkDrawLooper* get_looper(const Paint* paint) {
+static BlurDrawLooper* get_looper(const Paint* paint) {
     return paint ? paint->getLooper() : nullptr;
 }
 
 template <typename Proc>
-void applyLooper(SkDrawLooper* looper, const SkPaint* paint, Proc proc) {
+void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) {
     if (looper) {
-        SkSTArenaAlloc<256> alloc;
-        SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-        if (ctx) {
-            SkDrawLooper::Context::Info info;
-            for (;;) {
-                SkPaint p;
-                if (paint) {
-                    p = *paint;
-                }
-                if (!ctx->next(&info, &p)) {
-                    break;
-                }
-                proc(info.fTranslate.fX, info.fTranslate.fY, &p);
-            }
+        SkPaint p;
+        if (paint) {
+            p = *paint;
         }
+        looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+            proc(offset.fX, offset.fY, &modifiedPaint);
+        });
     } else {
         proc(0, 0, paint);
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9543d47..b760db2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -178,12 +178,16 @@
     if (surfaceControl == mSurfaceControl) return;
 
     auto funcs = mRenderThread.getASurfaceControlFunctions();
+
     if (mSurfaceControl != nullptr) {
+        funcs.unregisterListenerFunc(this, &onSurfaceStatsAvailable);
         funcs.releaseFunc(mSurfaceControl);
     }
     mSurfaceControl = surfaceControl;
+    mExpectSurfaceStats = surfaceControl != nullptr;
     if (mSurfaceControl != nullptr) {
         funcs.acquireFunc(mSurfaceControl);
+        funcs.registerListenerFunc(surfaceControl, this, &onSurfaceStatsAvailable);
     }
 }
 
@@ -332,8 +336,8 @@
     // just keep using the previous frame's structure instead
     if (!wasSkipped(mCurrentFrameInfo)) {
         mCurrentFrameInfo = mJankTracker.startFrame();
-        mLast4FrameInfos.next().first = mCurrentFrameInfo;
     }
+
     mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
     mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
     mCurrentFrameInfo->markSyncStart();
@@ -538,17 +542,14 @@
         }
         mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
         mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
-        mLast4FrameInfos[-1].second = frameCompleteNr;
         mHaveNewSurface = false;
         mFrameNumber = -1;
     } else {
         mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
         mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
-        mLast4FrameInfos[-1].second = -1;
     }
 
-    // TODO: Use a fence for real completion?
-    mCurrentFrameInfo->markFrameCompleted();
+    mCurrentFrameInfo->markSwapBuffersCompleted();
 
 #if LOG_FRAMETIME_MMA
     float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
@@ -572,30 +573,73 @@
         mFrameCompleteCallbacks.clear();
     }
 
-    mJankTracker.finishFrame(*mCurrentFrameInfo);
-    if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
-        mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
-    }
-
-    if (mLast4FrameInfos.size() == mLast4FrameInfos.capacity()) {
-        // By looking 4 frames back, we guarantee all SF stats are available. There are at
-        // most 3 buffers in BufferQueue. Surface object keeps stats for the last 8 frames.
-        FrameInfo* forthBehind = mLast4FrameInfos.front().first;
-        int64_t composedFrameId = mLast4FrameInfos.front().second;
-        nsecs_t acquireTime = -1;
-        if (mNativeSurface) {
-            native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId,
-                                               nullptr, &acquireTime, nullptr, nullptr, nullptr,
-                                               nullptr, nullptr, nullptr, nullptr);
+    if (requireSwap) {
+        if (mExpectSurfaceStats) {
+            std::lock_guard lock(mLast4FrameInfosMutex);
+            std::pair<FrameInfo*, int64_t>& next = mLast4FrameInfos.next();
+            next.first = mCurrentFrameInfo;
+            next.second = frameCompleteNr;
+        } else {
+            mCurrentFrameInfo->markFrameCompleted();
+            mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
+                    = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
+            finishFrame(mCurrentFrameInfo);
         }
-        // Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING
-        forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1;
-        mJankTracker.finishGpuDraw(*forthBehind);
     }
 
     mRenderThread.cacheManager().onFrameCompleted();
 }
 
+void CanvasContext::finishFrame(FrameInfo* frameInfo) {
+
+    // TODO (b/169858044): Consolidate this into a single call.
+    mJankTracker.finishFrame(*frameInfo);
+    mJankTracker.finishGpuDraw(*frameInfo);
+
+    // TODO (b/169858044): Move this into JankTracker to adjust deadline when queue is
+    // double-stuffed.
+    if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
+        mFrameMetricsReporter->reportFrameMetrics(frameInfo->data());
+    }
+}
+
+void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
+            ASurfaceControlStats* stats) {
+
+    CanvasContext* instance = static_cast<CanvasContext*>(context);
+
+    const ASurfaceControlFunctions& functions =
+            instance->mRenderThread.getASurfaceControlFunctions();
+
+    nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
+    uint64_t frameNumber = functions.getFrameNumberFunc(stats);
+
+    FrameInfo* frameInfo = nullptr;
+    {
+        std::lock_guard(instance->mLast4FrameInfosMutex);
+        for (size_t i = 0; i < instance->mLast4FrameInfos.size(); i++) {
+            if (instance->mLast4FrameInfos[i].second == frameNumber) {
+                frameInfo = instance->mLast4FrameInfos[i].first;
+                break;
+            }
+        }
+    }
+    if (frameInfo != nullptr) {
+        if (gpuCompleteTime == -1) {
+            gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
+        }
+        if (gpuCompleteTime < frameInfo->get(FrameInfoIndex::SwapBuffers)) {
+            // TODO (b/180488606): Investigate why this can happen for first frames.
+            ALOGW("Impossible GPU complete time swapBuffers=%" PRIi64 " gpuComplete=%" PRIi64,
+                    frameInfo->get(FrameInfoIndex::SwapBuffers), gpuCompleteTime);
+            gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
+        }
+        frameInfo->set(FrameInfoIndex::FrameCompleted) = gpuCompleteTime;
+        frameInfo->set(FrameInfoIndex::GpuCompleted) = gpuCompleteTime;
+        instance->finishFrame(frameInfo);
+    }
+}
+
 // Called by choreographer to do an RT-driven animation
 void CanvasContext::doFrame() {
     if (!mRenderPipeline->isSurfaceReady()) return;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 917b00c..2e7b2f6 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -37,6 +37,7 @@
 #include <SkSize.h>
 #include <cutils/compiler.h>
 #include <utils/Functor.h>
+#include <utils/Mutex.h>
 
 #include <functional>
 #include <future>
@@ -196,6 +197,10 @@
 
     SkISize getNextFrameSize() const;
 
+    // Called when SurfaceStats are available.
+    static void onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
+            ASurfaceControlStats* stats);
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                   IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
@@ -212,6 +217,7 @@
     void setupPipelineSurface();
 
     SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
+    void finishFrame(FrameInfo* frameInfo);
 
     // The same type as Frame.mWidth and Frame.mHeight
     int32_t mLastFrameWidth = 0;
@@ -261,7 +267,12 @@
     std::vector<sp<RenderNode>> mRenderNodes;
 
     FrameInfo* mCurrentFrameInfo = nullptr;
-    RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos;
+
+    // List of frames that are awaiting GPU completion reporting
+    RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos
+            GUARDED_BY(mLast4FrameInfosMutex);
+    std::mutex mLast4FrameInfosMutex;
+
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
@@ -276,6 +287,9 @@
     std::unique_ptr<IRenderPipeline> mRenderPipeline;
 
     std::vector<std::function<void(int64_t)>> mFrameCompleteCallbacks;
+
+    // If set to true, we expect that callbacks into onSurfaceStatsAvailable
+    bool mExpectSurfaceStats = false;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index e14842f..b9568fc 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -215,6 +215,7 @@
 
 void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
     mRenderThread.queue().runSync([&]() {
+        std::lock_guard lock(mRenderThread.getJankDataMutex());
         mContext->profiler().dumpData(fd);
         if (dumpFlags & DumpFlags::FrameStats) {
             mContext->dumpFrames(fd);
@@ -234,6 +235,7 @@
 
 uint32_t RenderProxy::frameTimePercentile(int percentile) {
     return mRenderThread.queue().runSync([&]() -> auto {
+        std::lock_guard lock(mRenderThread.globalProfileData().getDataMutex());
         return mRenderThread.globalProfileData()->findPercentile(percentile);
     });
 }
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 2610186..5dc02e8 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -59,6 +59,26 @@
     releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
     LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
             "Failed to find required symbol ASurfaceControl_release!");
+
+    registerListenerFunc = (ASC_registerSurfaceStatsListener) dlsym(handle_,
+            "ASurfaceControl_registerSurfaceStatsListener");
+    LOG_ALWAYS_FATAL_IF(registerListenerFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_registerSurfaceStatsListener!");
+
+    unregisterListenerFunc = (ASC_unregisterSurfaceStatsListener) dlsym(handle_,
+            "ASurfaceControl_unregisterSurfaceStatsListener");
+    LOG_ALWAYS_FATAL_IF(unregisterListenerFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_unregisterSurfaceStatsListener!");
+
+    getAcquireTimeFunc = (ASCStats_getAcquireTime) dlsym(handle_,
+            "ASurfaceControlStats_getAcquireTime");
+    LOG_ALWAYS_FATAL_IF(getAcquireTimeFunc == nullptr,
+            "Failed to find required symbol ASurfaceControlStats_getAcquireTime!");
+
+    getFrameNumberFunc = (ASCStats_getFrameNumber) dlsym(handle_,
+            "ASurfaceControlStats_getFrameNumber");
+    LOG_ALWAYS_FATAL_IF(getFrameNumberFunc == nullptr,
+            "Failed to find required symbol ASurfaceControlStats_getFrameNumber!");
 }
 
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
@@ -146,7 +166,8 @@
         , mFrameCallbackTaskPending(false)
         , mRenderState(nullptr)
         , mEglManager(nullptr)
-        , mFunctorManager(WebViewFunctorManager::instance()) {
+        , mFunctorManager(WebViewFunctorManager::instance())
+        , mGlobalProfileData(mJankDataMutex) {
     Properties::load();
     start("RenderThread");
 }
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index bb7c518..a7d1ba8 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,6 +17,7 @@
 #ifndef RENDERTHREAD_H_
 #define RENDERTHREAD_H_
 
+#include <surface_control_private.h>
 #include <GrDirectContext.h>
 #include <SkBitmap.h>
 #include <cutils/compiler.h>
@@ -81,11 +82,22 @@
 typedef void (*ASC_acquire)(ASurfaceControl* control);
 typedef void (*ASC_release)(ASurfaceControl* control);
 
+typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, void* context,
+        ASurfaceControl_SurfaceStatsListener func);
+typedef void (*ASC_unregisterSurfaceStatsListener)(void* context,
+                                       ASurfaceControl_SurfaceStatsListener func);
+
+typedef int64_t (*ASCStats_getAcquireTime)(ASurfaceControlStats* stats);
+typedef uint64_t (*ASCStats_getFrameNumber)(ASurfaceControlStats* stats);
+
 struct ASurfaceControlFunctions {
     ASurfaceControlFunctions();
-
     ASC_acquire acquireFunc;
     ASC_release releaseFunc;
+    ASC_registerSurfaceStatsListener registerListenerFunc;
+    ASC_unregisterSurfaceStatsListener unregisterListenerFunc;
+    ASCStats_getAcquireTime getAcquireTimeFunc;
+    ASCStats_getFrameNumber getFrameNumberFunc;
 };
 
 class ChoreographerSource;
@@ -114,6 +126,7 @@
     RenderState& renderState() const { return *mRenderState; }
     EglManager& eglManager() const { return *mEglManager; }
     ProfileDataContainer& globalProfileData() { return mGlobalProfileData; }
+    std::mutex& getJankDataMutex() { return mJankDataMutex; }
     Readback& readback();
 
     GrDirectContext* getGrContext() const { return mGrContext.get(); }
@@ -205,6 +218,7 @@
     sp<VulkanManager> mVkManager;
 
     ASurfaceControlFunctions mASurfaceControlFunctions;
+    std::mutex mJankDataMutex;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7951537..a1ba70a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -16,7 +16,6 @@
 
 #include "tests/common/TestUtils.h"
 
-#include <SkBlurDrawLooper.h>
 #include <SkColorMatrixFilter.h>
 #include <SkColorSpace.h>
 #include <SkImagePriv.h>
@@ -85,15 +84,3 @@
     ASSERT_EQ(sRGB1.get(), sRGB2.get());
 }
 
-TEST(SkiaBehavior, blurDrawLooper) {
-    sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
-
-    SkDrawLooper::BlurShadowRec blur;
-    bool success = looper->asABlurShadow(&blur);
-    ASSERT_TRUE(success);
-
-    ASSERT_EQ(SK_ColorRED, blur.fColor);
-    ASSERT_EQ(5.0f, blur.fSigma);
-    ASSERT_EQ(3.0f, blur.fOffset.fX);
-    ASSERT_EQ(4.0f, blur.fOffset.fY);
-}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index f77ca2a..dae3c94 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,7 +17,6 @@
 #include "tests/common/TestUtils.h"
 
 #include <hwui/Paint.h>
-#include <SkBlurDrawLooper.h>
 #include <SkCanvasStateUtils.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
@@ -37,7 +36,7 @@
     // it is transparent to ensure that we still draw the rect since it has a looper
     paint.setColor(SK_ColorTRANSPARENT);
     // this is how view's shadow layers are implemented
-    paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+    paint.setLooper(BlurDrawLooper::Make({0, 0, 0, 240.0f / 255}, nullptr, 6.0f, {0, 10}));
     canvas.drawRect(3, 3, 7, 7, paint);
 
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index e2fdf2f..09c6a4f 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -20,7 +20,6 @@
 #include <utils/Blur.h>
 
 #include <SkColorFilter.h>
-#include <SkDrawLooper.h>
 #include <SkPaint.h>
 #include <SkShader.h>
 
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -2493,4 +2494,80 @@
             return mPlaybackId;
         }
     }
+
+    /**
+     * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
+     * instance.
+     */
+    @NonNull
+    public native List<LogMessage> getLogMessages();
+
+    /**
+     * A {@link LogMessage} records an event in the {@link MediaDrm} framework
+     * or vendor plugin.
+     */
+    public static class LogMessage {
+
+        /**
+         * Timing of the recorded event measured in milliseconds since the Epoch,
+         * 1970-01-01 00:00:00 +0000 (UTC).
+         */
+        public final long timestampMillis;
+
+        /**
+         * Priority of the recorded event.
+         * <p>
+         * Possible priority constants are defined in {@link Log}, e.g.:
+         * <ul>
+         *     <li>{@link Log#ASSERT}</li>
+         *     <li>{@link Log#ERROR}</li>
+         *     <li>{@link Log#WARN}</li>
+         *     <li>{@link Log#INFO}</li>
+         *     <li>{@link Log#DEBUG}</li>
+         *     <li>{@link Log#VERBOSE}</li>
+         * </ul>
+         */
+        @Log.Level
+        public final int priority;
+
+        /**
+         * Description of the recorded event.
+         */
+        @NonNull
+        public final String message;
+
+        private LogMessage(long timestampMillis, int priority, String message) {
+            this.timestampMillis = timestampMillis;
+            if (priority < Log.VERBOSE || priority > Log.ASSERT) {
+                throw new IllegalArgumentException("invalid log priority " + priority);
+            }
+            this.priority = priority;
+            this.message = message;
+        }
+
+        private char logPriorityChar() {
+            switch (priority) {
+                case Log.VERBOSE:
+                    return 'V';
+                case Log.DEBUG:
+                    return 'D';
+                case Log.INFO:
+                    return 'I';
+                case Log.WARN:
+                    return 'W';
+                case Log.ERROR:
+                    return 'E';
+                case Log.ASSERT:
+                    return 'F';
+                default:
+            }
+            return 'U';
+        }
+
+        @Override
+        public String toString() {
+            return String.format("LogMessage{%s %c %s}",
+                    Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2c45ed3..c51c9dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -921,7 +921,7 @@
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
+            mp.native_setAudioSessionId(audioSessionId);
             mp.setDataSource(context, uri);
             if (holder != null) {
                 mp.setDisplay(holder);
@@ -987,7 +987,7 @@
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
+            mp.native_setAudioSessionId(audioSessionId);
 
             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
             afd.close();
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
+        "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
     ],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 0e8719e..22c3572 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -37,6 +37,7 @@
 #include <mediadrm/DrmUtils.h>
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
+#include <utils/Vector.h>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -187,6 +188,11 @@
     jclass classId;
 };
 
+struct LogMessageFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -208,6 +214,7 @@
     jmethodID createFromParcelId;
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
+    LogMessageFields logMessage;
 };
 
 static fields_t gFields;
@@ -224,6 +231,19 @@
     return result;
 }
 
+jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) {
+    jclass clazz = gFields.arraylistClassId;
+    jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+    clazz = gFields.logMessage.classId;
+    for (auto log: logs) {
+        jobject jLog = env->NewObject(clazz, gFields.logMessage.init,
+                static_cast<jlong>(log.timeMs),
+                static_cast<jint>(log.priority),
+                env->NewStringUTF(log.message.c_str()));
+        env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog);
+    }
+    return arrayList;
+}
 }  // namespace anonymous
 
 // ----------------------------------------------------------------------------
@@ -907,6 +927,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus");
     gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V");
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
+    gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+    GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -1996,6 +2020,22 @@
     throwExceptionAsNecessary(env, err, "Failed to set playbackId");
 }
 
+static jobject android_media_MediaDrm_getLogMessages(
+        JNIEnv *env, jobject thiz) {
+    sp<IDrm> drm = GetDrm(env, thiz);
+    if (!CheckDrm(env, drm)) {
+        return NULL;
+    }
+
+    Vector<drm::V1_4::LogMessage> logs;
+    status_t err = drm->getLogMessages(logs);
+    ALOGI("drm->getLogMessages %zu logs", logs.size());
+    if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) {
+        return NULL;
+    }
+    return hidlLogMessagesToJavaList(env, logs);
+}
+
 static const JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaDrm_native_release },
 
@@ -2123,6 +2163,9 @@
 
     { "setPlaybackId", "([BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_setPlaybackId },
+
+    { "getLogMessages", "()Ljava/util/List;",
+      (void *)android_media_MediaDrm_getLogMessages },
 };
 
 int register_android_media_Drm(JNIEnv *env) {
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3daaf05..253ef67 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -88,7 +88,7 @@
         "libarect",
     ],
 
-    header_libs: [ "libhwui_internal_headers",],
+    header_libs: [ "libhwui_internal_headers", "libandroid_headers_private"],
 
     whole_static_libs: ["libnativewindow"],
 
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 7a18bd5..b01878b 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -300,3 +300,13 @@
   local:
     *;
 };
+
+LIBANDROID_PLATFORM {
+  global:
+    extern "C++" {
+        ASurfaceControl_registerSurfaceStatsListener*;
+        ASurfaceControl_unregisterSurfaceStatsListener*;
+        ASurfaceControlStats_getAcquireTime*;
+        ASurfaceControlStats_getFrameNumber*;
+    };
+} LIBANDROID;
\ No newline at end of file
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index c1b5f1d..e51add2 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -17,6 +17,7 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/native_window.h>
 #include <android/surface_control.h>
+#include <surface_control_private.h>
 
 #include <configstore/Utils.h>
 
@@ -197,6 +198,48 @@
     SurfaceControl_release(surfaceControl);
 }
 
+struct ASurfaceControlStats {
+    int64_t acquireTime;
+    sp<Fence> previousReleaseFence;
+    uint64_t frameNumber;
+};
+
+void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, void* context,
+        ASurfaceControl_SurfaceStatsListener func) {
+    SurfaceStatsCallback callback = [func](void* callback_context,
+                                                               nsecs_t,
+                                                               const sp<Fence>&,
+                                                               const SurfaceStats& surfaceStats) {
+
+        ASurfaceControlStats aSurfaceControlStats;
+
+        ASurfaceControl* aSurfaceControl =
+                reinterpret_cast<ASurfaceControl*>(surfaceStats.surfaceControl.get());
+        aSurfaceControlStats.acquireTime = surfaceStats.acquireTime;
+        aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence;
+        aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber;
+
+        (*func)(callback_context, aSurfaceControl, &aSurfaceControlStats);
+    };
+    TransactionCompletedListener::getInstance()->addSurfaceStatsListener(context,
+            reinterpret_cast<void*>(func), ASurfaceControl_to_SurfaceControl(control), callback);
+}
+
+
+void ASurfaceControl_unregisterSurfaceStatsListener(void* context,
+        ASurfaceControl_SurfaceStatsListener func) {
+    TransactionCompletedListener::getInstance()->removeSurfaceStatsListener(context,
+            reinterpret_cast<void*>(func));
+}
+
+int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) {
+    return stats->acquireTime;
+}
+
+uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) {
+    return stats->frameNumber;
+}
+
 ASurfaceTransaction* ASurfaceTransaction_create() {
     Transaction* transaction = new Transaction;
     return reinterpret_cast<ASurfaceTransaction*>(transaction);
@@ -215,11 +258,6 @@
     transaction->apply();
 }
 
-typedef struct ASurfaceControlStats {
-    int64_t acquireTime;
-    sp<Fence> previousReleaseFence;
-} ASurfaceControlStats;
-
 struct ASurfaceTransactionStats {
     std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
     int64_t latchTime;
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 92d7bf0..d7c8291 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -456,7 +456,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_WIFI      = TetheringManager.TETHERING_WIFI;
+    public static final int TETHERING_WIFI      = 0;
 
     /**
      * USB tethering type.
@@ -464,7 +464,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_USB       = TetheringManager.TETHERING_USB;
+    public static final int TETHERING_USB       = 1;
 
     /**
      * Bluetooth tethering type.
@@ -472,7 +472,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
+    public static final int TETHERING_BLUETOOTH = 2;
 
     /**
      * Wifi P2p tethering type.
@@ -2799,7 +2799,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+    public static final int TETHER_ERROR_NO_ERROR = 0;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
      * {@hide}
@@ -2875,8 +2875,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_PROVISION_FAILED =
-            TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+    public static final int TETHER_ERROR_PROVISION_FAILED = 11;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
      * {@hide}
@@ -2890,8 +2889,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
-            TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
index 4e89414..a174a7b 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
@@ -41,7 +41,6 @@
 
     /**
      * Prefix for tap interfaces created by this class.
-     * @hide
      */
     public static final String TEST_TAP_PREFIX = "testtap";
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 539a81b..a15ceb6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -345,6 +345,12 @@
     <!-- Permissions required for CTS test - AdbManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
 
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
+
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
+
     <!-- Permission needed for CTS test - DisplayTest -->
     <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
 
@@ -406,6 +412,9 @@
     <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
     <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
 
+    <!-- Permission required for CTS test - PeopleManagerTest -->
+    <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d023..60994d8 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -144,6 +144,10 @@
 
 Delegates SysUI events to WM Shell controllers.
 
+### [com.android.systemui.people.widget.PeopleSpaceWidgetEnabler](/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java)
+
+Enables People Space widgets.
+
 ---
 
  * [Plugins](/packages/SystemUI/docs/plugins.md)
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
similarity index 90%
rename from packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
rename to packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index 1f6b24b..dd35dd9 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
+  ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -20,6 +20,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?android:attr/colorBackground"
         android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
 </vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
index b7a9fafd..604ab72 100644
--- a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
@@ -16,8 +16,23 @@
 * limitations under the License.
 */
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <solid android:color="?android:attr/colorBackground" />
-  <corners android:radius="10dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:id="@+id/background">
+    <shape>
+      <solid android:color="?android:attr/colorControlNormal" />
+      <corners android:radius="10dp" />
+    </shape>
+  </item>
+  <item android:id="@+id/ripple">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+      <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+          <solid android:color="?android:attr/colorControlNormal" />
+          <corners android:radius="10dp" />
+        </shape>
+    </item>
+    </ripple>
+  </item>
+</layer-list>
+
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index a928b75..2a5784a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,7 +40,7 @@
     <dimen name="keyguard_security_view_top_margin">8dp</dimen>
     <dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
 
-    <dimen name="keyguard_eca_top_margin">24dp</dimen>
+    <dimen name="keyguard_eca_top_margin">18dp</dimen>
 
     <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
          Should be 0 on devices with plenty of room (e.g. tablets) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index cd82b80..2391803 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -52,9 +52,8 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="NumPadKey.Delete">
-        <item name="android:src">@drawable/ic_backspace_black_24dp</item>
-        <item name="android:tint">?android:attr/textColorSecondary</item>
-        <item name="android:tintMode">src_in</item>
+        <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="android:src">@drawable/ic_backspace_24dp</item>
     </style>
     <style name="NumPadKey.Enter">
       <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/circle_green_10dp.xml
similarity index 80%
copy from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
copy to packages/SystemUI/res/drawable/circle_green_10dp.xml
index 59af775..571ec62 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/circle_green_10dp.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -13,7 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorBackground" />
-    <corners android:radius="@dimen/people_space_widget_round_radius" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <size android:height="10dp"
+          android:width="10dp" />
+    <solid android:color="#34A853" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_content_background.xml b/packages/SystemUI/res/drawable/people_space_content_background.xml
index 53108409..32314d2 100644
--- a/packages/SystemUI/res/drawable/people_space_content_background.xml
+++ b/packages/SystemUI/res/drawable/people_space_content_background.xml
@@ -16,5 +16,5 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android" >
     <solid android:color="?android:attr/colorControlHighlight" />
-    <corners android:radius="@dimen/people_space_widget_radius" />
+    <corners android:radius="@dimen/people_space_image_radius" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
similarity index 87%
rename from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
rename to packages/SystemUI/res/drawable/people_space_new_story_outline.xml
index 59af775..a1737f9 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
@@ -13,7 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
     <solid android:color="?android:attr/colorBackground" />
-    <corners android:radius="@dimen/people_space_widget_round_radius" />
+    <stroke android:width="2dp" android:color="?android:attr/colorAccent" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
index e9f3424..b1c1328 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
@@ -19,70 +19,78 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
     <LinearLayout
-        android:background="@drawable/people_space_round_tile_view_card"
+        android:background="@drawable/people_space_tile_view_card"
         android:id="@+id/item"
-        android:paddingVertical="6dp"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
         <LinearLayout
             android:orientation="horizontal"
-            android:gravity="center_vertical"
-            android:paddingStart="12dp"
+            android:gravity="center"
+            android:paddingVertical="2dp"
+            android:paddingHorizontal="8dp"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
-
+            <LinearLayout
+                android:background="@drawable/people_space_new_story_outline"
+                android:id="@+id/person_icon_with_story"
+                android:gravity="center_horizontal"
+                android:layout_width="60dp"
+                android:layout_height="60dp">
                 <ImageView
-                    android:id="@+id/person_icon"
-                    android:layout_width="60dp"
-                    android:layout_height="60dp" />
+                    android:id="@+id/person_icon_inside_ring"
+                    android:layout_marginEnd="4dp"
+                    android:layout_marginStart="4dp"
+                    android:layout_marginBottom="4dp"
+                    android:layout_marginTop="4dp"
+                    android:layout_width="52dp"
+                    android:layout_height="52dp"/>
+            </LinearLayout>
+            <ImageView
+                android:id="@+id/person_icon_only"
+                android:layout_width="60dp"
+                android:layout_height="60dp"/>
 
-                <LinearLayout
-                    android:background="@drawable/people_space_rounded_border"
-                    android:layout_marginStart="-12dp"
-                    android:layout_marginTop="28dp"
-                    android:layout_marginBottom="14dp"
-                    android:layout_width="16dp"
-                    android:layout_height="16dp">
-
-                    <ImageView
-                        android:id="@+id/package_icon"
-                        android:layout_width="12dp"
-                        android:layout_marginStart="2dp"
-                        android:layout_marginEnd="2dp"
-                        android:layout_marginBottom="2dp"
-                        android:layout_marginTop="2dp"
-                        android:layout_height="12dp" />
-                </LinearLayout>
+            <ImageView
+                android:id="@+id/package_icon"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginStart="-20dp"
+                android:layout_marginTop="22dp"/>
 
             <LinearLayout
                 android:orientation="vertical"
                 android:paddingStart="8dp"
-                android:paddingEnd="12dp"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
 
+                <ImageView
+                    android:id="@+id/availability"
+                    android:layout_width="10dp"
+                    android:layout_height="10dp"
+                    android:background="@drawable/circle_green_10dp"/>
                 <TextView
                     android:id="@+id/name"
+                    android:text="@string/empty_user_name"
                     android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
                     android:textColor="?android:attr/textColorPrimary"
-                    android:textSize="16sp"
+                    android:textSize="14sp"
                     android:maxLines="1"
                     android:ellipsize="end"
                     android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
+                    android:layout_height="wrap_content"/>
 
                 <TextView
-                    android:id="@+id/status"
+                    android:id="@+id/last_interaction"
+                    android:text="@string/empty_status"
                     android:textColor="?android:attr/textColorSecondary"
                     android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:paddingVertical="3dp"
                     android:textSize="12sp"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:maxLines="3"
-                    android:ellipsize="end" />
+                    android:ellipsize="end"/>
             </LinearLayout>
         </LinearLayout>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
new file mode 100644
index 0000000..9ea7aa3
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <RelativeLayout
+        android:background="@drawable/people_space_tile_view_card"
+        android:id="@+id/item"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <include layout="@layout/punctuation_layout"/>
+        <RelativeLayout
+            android:gravity="start"
+            android:id="@+id/column_one"
+            android:paddingVertical="10dp"
+            android:paddingStart="8dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent">
+            <TextView
+                android:id="@+id/subtext"
+                android:layout_toStartOf="@+id/content_layout"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:gravity="top|start"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textSize="12sp"
+                android:maxWidth="60dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:ellipsize="end"/>
+            <LinearLayout
+                android:orientation="horizontal"
+                android:id="@+id/avatar_and_app_icon"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentStart="true"
+                android:gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:id="@+id/person_icon_with_story"
+                    android:background="@drawable/people_space_new_story_outline"
+                    android:gravity="center_horizontal"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp">
+                    <ImageView
+                        android:id="@+id/person_icon_inside_ring"
+                        android:layout_marginEnd="4dp"
+                        android:layout_marginStart="4dp"
+                        android:layout_marginBottom="4dp"
+                        android:layout_marginTop="4dp"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"/>
+                </LinearLayout>
+                <ImageView
+                    android:id="@+id/person_icon_only"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"/>
+                <ImageView
+                    android:id="@id/package_icon"
+                    android:layout_marginStart="-16dp"
+                    android:layout_marginTop="18dp"
+                    android:paddingBottom="10dp"
+                    android:paddingEnd="8dp"
+                    android:layout_width="28dp"
+                    android:layout_height="32dp"/>
+            </LinearLayout>
+        </RelativeLayout>
+        <LinearLayout
+            android:id="@+id/content_layout"
+            android:paddingBottom="4dp"
+            android:paddingStart="4dp"
+            android:paddingTop="8dp"
+            android:paddingEnd="8dp"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@id/column_one"
+            android:gravity="top|end"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/content"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:gravity="top|end"
+                android:textSize="12sp"
+                android:paddingTop="2dp"
+                android:paddingEnd="4dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:ellipsize="end"/>
+            <LinearLayout
+                android:id="@+id/content_background"
+                android:background="@drawable/people_space_content_background"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <ImageView
+                    android:id="@+id/image"
+                    android:adjustViewBounds="true"
+                    android:maxHeight="44dp"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:scaleType="centerCrop"/>
+            </LinearLayout>
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/person_label"
+            android:paddingBottom="10dp"
+            android:paddingEnd="8dp"
+            android:gravity="start|bottom"
+            android:layout_toEndOf="@id/column_one"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/content_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <ImageView
+                android:id="@+id/availability"
+                android:layout_width="10dp"
+                android:layout_height="10dp"
+                android:paddingVertical="2dp"
+                android:background="@drawable/circle_green_10dp"/>
+            <TextView
+                android:id="@+id/name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        </LinearLayout>
+    </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
index 03589d3..3300495 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -14,183 +14,144 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
-        <LinearLayout
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="start">
-            <TextView
-                android:id="@+id/punctuation1"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="350" />
-            <TextView
-                android:id="@+id/punctuation2"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="5" />
-            <TextView
-                android:id="@+id/punctuation3"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="355"/>
-            <TextView
-                android:id="@+id/punctuation4"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="-5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="10" />
-            <TextView
-                android:id="@+id/punctuation5"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="15" />
-            <TextView
-                android:id="@+id/punctuation6"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="-5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="345" />
-        </LinearLayout>
-        <LinearLayout
-            android:id="@+id/item"
-            android:orientation="vertical"
-            android:paddingTop="6dp"
-            android:layout_width="match_parent"
+        <RelativeLayout
+            android:gravity="start"
+            android:id="@+id/column_one"
+            android:paddingVertical="8dp"
+            android:paddingStart="8dp"
+            android:layout_width="wrap_content"
             android:layout_height="match_parent">
             <LinearLayout
                 android:orientation="horizontal"
-                android:paddingHorizontal="12dp"
-                android:paddingBottom="4dp"
-                android:gravity="top"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-
-                <ImageView
-                    android:id="@+id/person_icon"
-                    android:layout_width="34dp"
-                    android:layout_height="34dp" />
-
-                <LinearLayout
-                    android:background="@drawable/people_space_rounded_border"
-                    android:layout_marginStart="-5dp"
-                    android:layout_marginTop="18dp"
-                    android:layout_width="8dp"
-                    android:layout_height="8dp">
-
-                    <ImageView
-                        android:id="@+id/package_icon"
-                        android:layout_width="6dp"
-                        android:layout_marginEnd="1dp"
-                        android:layout_marginStart="1dp"
-                        android:layout_marginBottom="1dp"
-                        android:layout_marginTop="1dp"
-                        android:layout_height="6dp" />
-                </LinearLayout>
-
-                <LinearLayout
-                    android:orientation="vertical"
-                    android:paddingStart="6dp"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <TextView
-                        android:id="@+id/name"
-                        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                        android:textColor="?android:attr/textColorPrimary"
-                        android:textSize="14sp"
-                        android:maxLines="1"
-                        android:ellipsize="end"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content" />
-
-                    <TextView
-                        android:id="@+id/time"
-                        android:textColor="?android:attr/textColorSecondary"
-                        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                        android:textSize="10sp"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:maxLines="1"
-                        android:ellipsize="end" />
-                </LinearLayout>
-            </LinearLayout>
-            <LinearLayout
                 android:id="@+id/content_background"
                 android:background="@drawable/people_space_content_background"
-                android:layout_gravity="center"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-                <TextView
-                    android:id="@+id/content"
-                    android:paddingVertical="3dp"
-                    android:paddingHorizontal="12dp"
-                    android:gravity="center"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textSize="16sp"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:maxLines="2"
-                    android:ellipsize="end" />
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_width="60dp"
+                android:layout_height="60dp">
                 <ImageView
                     android:id="@+id/image"
+                    android:gravity="center"
+                    android:background="@drawable/people_space_content_background"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:visibility="gone"
                     android:scaleType="centerCrop"/>
+                <ImageView
+                    android:id="@+id/status_defined_icon"
+                    android:gravity="start|top"
+                    android:layout_marginStart="-52dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_width="18dp"
+                    android:layout_height="18dp"/>
             </LinearLayout>
+            <LinearLayout
+                android:orientation="horizontal"
+                android:id="@+id/avatar_and_app_icon"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentStart="true"
+                android:paddingStart="4dp"
+                android:gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:id="@+id/person_icon_with_story"
+                    android:background="@drawable/people_space_new_story_outline"
+                    android:gravity="center_horizontal"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp">
+                    <ImageView
+                        android:id="@+id/person_icon_inside_ring"
+                        android:layout_marginEnd="4dp"
+                        android:layout_marginStart="4dp"
+                        android:layout_marginBottom="4dp"
+                        android:layout_marginTop="4dp"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"/>
+                </LinearLayout>
+                <ImageView
+                    android:id="@+id/person_icon_only"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"/>
+                <ImageView
+                    android:id="@id/package_icon"
+                    android:layout_marginStart="-16dp"
+                    android:layout_marginTop="18dp"
+                    android:paddingBottom="10dp"
+                    android:paddingEnd="8dp"
+                    android:layout_width="28dp"
+                    android:layout_height="32dp"/>
+            </LinearLayout>
+        </RelativeLayout>
+        <LinearLayout
+            android:id="@+id/content_layout"
+            android:paddingTop="10dp"
+            android:paddingBottom="4dp"
+            android:paddingHorizontal="8dp"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@id/column_one"
+            android:gravity="top|end"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/status"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="12sp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:ellipsize="end"/>
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/person_label"
+            android:paddingBottom="10dp"
+            android:paddingEnd="8dp"
+            android:gravity="start|bottom"
+            android:layout_toEndOf="@id/column_one"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/content_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/time"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textSize="12sp"
+                android:paddingVertical="2dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:visibility="gone"
+                android:ellipsize="end"/>
+            <ImageView
+                android:id="@+id/availability"
+                android:layout_width="10dp"
+                android:layout_height="10dp"
+                android:paddingVertical="2dp"
+                android:background="@drawable/circle_green_10dp"/>
+            <TextView
+                android:id="@+id/name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
         </LinearLayout>
     </RelativeLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/punctuation_layout.xml b/packages/SystemUI/res/layout/punctuation_layout.xml
new file mode 100644
index 0000000..25c7648
--- /dev/null
+++ b/packages/SystemUI/res/layout/punctuation_layout.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/punctuation_layout"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="start">
+    <TextView
+        android:id="@+id/punctuation1"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="350"/>
+    <TextView
+        android:id="@+id/punctuation2"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="5"/>
+    <TextView
+        android:id="@+id/punctuation3"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="355"/>
+    <TextView
+        android:id="@+id/punctuation4"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="10"/>
+    <TextView
+        android:id="@+id/punctuation5"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="15"/>
+    <TextView
+        android:id="@+id/punctuation6"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="345"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index dc341274..059bda3 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -54,16 +54,4 @@
         android:paddingBottom="10dp"
         android:importantForAccessibility="yes" />
 
-    <TextView
-        android:id="@+id/header_debug_info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:fontFamily="sans-serif-condensed"
-        android:padding="2dp"
-        android:textColor="#00A040"
-        android:textSize="11dp"
-        android:textStyle="bold"
-        android:visibility="invisible"/>
-
 </com.android.systemui.qs.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 78927f8..13c0110 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -312,6 +312,7 @@
         <item>com.android.systemui.accessibility.SystemActions</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.wmshell.WMShell</item>
+        <item>com.android.systemui.people.widget.PeopleSpaceWidgetEnabler</item>
     </string-array>
 
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0d92aea..b07df9c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1348,8 +1348,8 @@
     <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
     <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
 
-    <dimen name="people_space_widget_radius">24dp</dimen>
-    <dimen name="people_space_widget_round_radius">100dp</dimen>
+    <dimen name="people_space_widget_radius">28dp</dimen>
+    <dimen name="people_space_image_radius">20dp</dimen>
     <dimen name="people_space_widget_background_padding">6dp</dimen>
 
     <dimen name="rounded_slider_height">48dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d997ca2..3b42600 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -247,6 +247,10 @@
     <string name="screenshot_dismiss_description">Dismiss screenshot</string>
     <!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
     <string name="screenshot_preview_description">Screenshot preview</string>
+    <!-- Content description for the top boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+    <string name="screenshot_top_boundary">Top boundary</string>
+    <!-- Content description for the bottom boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+    <string name="screenshot_bottom_boundary">Bottom boundary</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
@@ -1341,6 +1345,9 @@
     <!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
 
+    <!-- Monitoring dialog: Description of Network Logging in the work profile. [CHAR LIMIT=NONE]-->
+    <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string>
+
     <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_named_vpn">You\'re connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
 
@@ -2810,8 +2817,26 @@
     <string name="less_than_timestamp" translatable="false">Less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string>
     <!-- Timestamp for notification when over a certain time window [CHAR LIMIT=120] -->
     <string name="over_timestamp" translatable="false">Over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string>
-    <!-- Status text for a birthday today [CHAR LIMIT=120] -->
-    <string name="birthday_status" translatable="false">Today is their birthday!</string>
+    <!-- Status text for a birthday today [CHAR LIMIT=30] -->
+    <string name="birthday_status" translatable="false">Birthday</string>
+    <!-- Status text for an upcoming birthday [CHAR LIMIT=30] -->
+    <string name="upcoming_birthday_status" translatable="false">Birthday soon</string>
+    <!-- Status text for an anniversary [CHAR LIMIT=30] -->
+    <string name="anniversary_status" translatable="false">Anniversary</string>
+    <!-- Status text for sharing location [CHAR LIMIT=30] -->
+    <string name="location_status" translatable="false">Sharing location</string>
+    <!-- Status text for a new story posted [CHAR LIMIT=30] -->
+    <string name="new_story_status" translatable="false">New story</string>
+    <!-- Status text for watching a video [CHAR LIMIT=30] -->
+    <string name="video_status" translatable="false">Watching</string>
+    <!-- Status text for listening to audio [CHAR LIMIT=30] -->
+    <string name="audio_status" translatable="false">Listening</string>
+    <!-- Status text for playing a game [CHAR LIMIT=30] -->
+    <string name="game_status" translatable="false">Playing</string>
+    <!-- Empty user name before user has selected a friend [CHAR LIMIT=30] -->
+    <string name="empty_user_name" translatable="false">Your friend</string>
+    <!-- Empty status shown before user has selected a friend [CHAR LIMIT=30] -->
+    <string name="empty_status" translatable="false">Their status</string>
 
     <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
     [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index d0c63a8..fbdac5e1 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -16,11 +16,11 @@
 
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
     android:minWidth="140dp"
-    android:minHeight="40dp"
+    android:minHeight="55dp"
     android:minResizeWidth="110dp"
-    android:minResizeHeight="40dp"
+    android:minResizeHeight="55dp"
     android:updatePeriodMillis="60000"
-    android:previewImage="@drawable/ic_android"
+    android:previewImage="@drawable/ic_person"
     android:resizeMode="horizontal|vertical"
     android:configure="com.android.systemui.people.PeopleSpaceActivity"
     android:initialLayout="@layout/people_space_widget">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 825ea25..4e06491 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -24,14 +24,12 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 
 import com.android.internal.widget.LockscreenCredential;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -188,10 +186,6 @@
             key.reloadColors();
         }
         mPasswordEntry.reloadColors();
-        int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary)
-                .getDefaultColor();
-        mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor));
-
         mDeleteButton.reloadColors();
         mOkButton.reloadColors();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index cdf9858..97d6e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -17,14 +17,16 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.view.ContextThemeWrapper;
 import android.view.ViewGroup;
 
 import androidx.annotation.StyleRes;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -34,13 +36,18 @@
 class NumPadAnimator {
     private ValueAnimator mAnimator;
     private GradientDrawable mBackground;
+    private RippleDrawable mRipple;
+    private GradientDrawable mRippleMask;
     private int mMargin;
     private int mNormalColor;
     private int mHighlightColor;
     private int mStyle;
 
-    NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) {
-        mBackground = (GradientDrawable) background.mutate();
+    NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) {
+        LayerDrawable ld = (LayerDrawable) drawable.mutate();
+        mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background);
+        mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple);
+        mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask);
         mStyle = style;
 
         reloadColors(context);
@@ -49,13 +56,14 @@
 
         // Actual values will be updated later, usually during an onLayout() call
         mAnimator = ValueAnimator.ofFloat(0f);
-        mAnimator.setDuration(250);
-        mAnimator.setInterpolator(Interpolators.LINEAR);
+        mAnimator.setDuration(100);
+        mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(1);
         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator anim) {
                     mBackground.setCornerRadius((float) anim.getAnimatedValue());
-                    mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor,
-                            anim.getAnimatedFraction()));
+                    mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
                 }
         });
 
@@ -66,9 +74,9 @@
     }
 
     void onLayout(int height) {
-        float startRadius = height / 10f;
-        float endRadius = height / 2f;
-        mBackground.setCornerRadius(endRadius);
+        float startRadius = height / 2f;
+        float endRadius = height / 4f;
+        mBackground.setCornerRadius(startRadius);
         mAnimator.setFloatValues(startRadius, endRadius);
     }
 
@@ -91,6 +99,7 @@
         a.recycle();
 
         mBackground.setColor(mNormalColor);
+        mRipple.setColor(ColorStateList.valueOf(mHighlightColor));
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 886c372..8cb1bc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,7 +17,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
@@ -37,7 +37,7 @@
     public NumPadButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 attrs.getStyleAttribute());
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 01e1c63..a4a781d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.AttributeSet;
@@ -127,7 +127,7 @@
 
         setContentDescription(mDigitText.getText().toString());
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 R.style.NumPadKey);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index b6a232d..71ec33e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,10 +16,7 @@
 
 package com.android.systemui;
 
-import android.app.WallpaperColors;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -29,16 +26,13 @@
 import android.util.Size;
 import android.view.SurfaceHolder;
 
-import androidx.annotation.NonNull;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.glwallpaper.EglHelper;
+import com.android.systemui.glwallpaper.GLWallpaperRenderer;
 import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 
 import javax.inject.Inject;
 
@@ -51,13 +45,8 @@
     // We delayed destroy render context that subsequent render requests have chance to cancel it.
     // This is to avoid destroying then recreating render context in a very short time.
     private static final int DELAY_FINISH_RENDERING = 1000;
-    private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
-            new RectF(0, 0, 1, 1);
     private static final boolean DEBUG = false;
-    private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
     private HandlerThread mWorker;
-    // scaled down version
-    private Bitmap mMiniBitmap;
 
     @Inject
     public ImageWallpaper() {
@@ -81,7 +70,6 @@
         super.onDestroy();
         mWorker.quitSafely();
         mWorker = null;
-        mMiniBitmap = null;
     }
 
     class GLEngine extends Engine {
@@ -92,7 +80,7 @@
         @VisibleForTesting
         static final int MIN_SURFACE_HEIGHT = 64;
 
-        private ImageWallpaperRenderer mRenderer;
+        private GLWallpaperRenderer mRenderer;
         private EglHelper mEglHelper;
         private final Runnable mFinishRenderingTask = this::finishRendering;
         private boolean mNeedRedraw;
@@ -113,12 +101,6 @@
             setFixedSizeAllowed(true);
             setOffsetNotificationsEnabled(false);
             updateSurfaceSize();
-            mMiniBitmap = null;
-            if (mWorker == null || mWorker.getThreadHandler() == null) {
-                updateMiniBitmap();
-            } else {
-                mWorker.getThreadHandler().post(this::updateMiniBitmap);
-            }
         }
 
         EglHelper getEglHelperInstance() {
@@ -129,20 +111,6 @@
             return new ImageWallpaperRenderer(getDisplayContext());
         }
 
-        private void updateMiniBitmap() {
-            mRenderer.useBitmap(b -> {
-                int size = Math.min(b.getWidth(), b.getHeight());
-                float scale = 1.0f;
-                if (size > MIN_SURFACE_WIDTH) {
-                    scale = (float) MIN_SURFACE_WIDTH / (float) size;
-                }
-                mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
-                        Math.round(scale * b.getHeight()), false);
-                computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
-                mLocalColorsToAdd.clear();
-            });
-        }
-
         private void updateSurfaceSize() {
             SurfaceHolder holder = getSurfaceHolder();
             Size frameSize = mRenderer.reportSurfaceSize();
@@ -158,7 +126,6 @@
 
         @Override
         public void onDestroy() {
-            mMiniBitmap = null;
             mWorker.getThreadHandler().post(() -> {
                 mRenderer.finish();
                 mRenderer = null;
@@ -167,61 +134,6 @@
             });
         }
 
-
-
-        @Override
-        public boolean supportsLocalColorExtraction() {
-            return true;
-        }
-
-        @Override
-        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
-            mWorker.getThreadHandler().post(() -> {
-                Bitmap bitmap = mMiniBitmap;
-                if (bitmap == null) {
-                    mLocalColorsToAdd.addAll(regions);
-                } else {
-                    computeAndNotifyLocalColors(regions, bitmap);
-                }
-            });
-        }
-
-        private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
-            List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
-            try {
-                notifyLocalColorsChanged(regions, colors);
-            } catch (RuntimeException e) {
-                Log.e(TAG, e.getMessage(), e);
-            }
-        }
-
-        @Override
-        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
-            // No-OP
-        }
-
-        private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
-                Bitmap b) {
-            List<WallpaperColors> colors = new ArrayList<>(areas.size());
-            for (int i = 0; i < areas.size(); i++) {
-                RectF area = areas.get(i);
-                if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
-                    colors.add(null);
-                    continue;
-                }
-                Rect subImage = new Rect(
-                        Math.round(area.left * b.getWidth()),
-                        Math.round(area.top * b.getHeight()),
-                        Math.round(area.right * b.getWidth()),
-                        Math.round(area.bottom * b.getHeight()));
-                Bitmap colorImg = Bitmap.createBitmap(b,
-                        subImage.left, subImage.top, subImage.width(), subImage.height());
-                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
-                colors.add(color);
-            }
-            return colors;
-        }
-
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
             if (mWorker == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 78f7966..865ca40 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -19,18 +19,15 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
@@ -40,7 +37,6 @@
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 import com.android.systemui.util.NotificationChannels;
 
@@ -125,21 +121,6 @@
                             mServices[i].onBootCompleted();
                         }
                     }
-
-                    // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
-                    // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
-                    try {
-                        int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
-                                Settings.Global.SHOW_PEOPLE_SPACE, 1);
-                        context.getPackageManager().setComponentEnabledSetting(
-                                new ComponentName(context, PeopleSpaceWidgetProvider.class),
-                                showPeopleSpace == 1
-                                        ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                                PackageManager.DONT_KILL_APP);
-                    } catch (Exception e) {
-                        Log.w(TAG, "Error enabling People Space widget:", e);
-                    }
                 }
             }, bootCompletedFilter);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index e5c9d10..ec3188a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.media.systemsounds.HomeSoundEffectController;
+import com.android.systemui.people.widget.PeopleSpaceWidgetEnabler;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
@@ -177,4 +178,10 @@
     @IntoMap
     @ClassKey(HomeSoundEffectController.class)
     public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
+
+    /** Inject into PeopleSpaceWidgetEnabler. */
+    @Binds
+    @IntoMap
+    @ClassKey(PeopleSpaceWidgetEnabler.class)
+    public abstract SystemUI bindPeopleSpaceWidgetEnabler(PeopleSpaceWidgetEnabler sysui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 01a353c..1a0356c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -58,14 +58,6 @@
         mWallpaper = new ImageGLWallpaper(mProgram);
     }
 
-    /**
-     * @hide
-     * @return
-     */
-    public void useBitmap(Consumer<Bitmap> c) {
-        mTexture.use(c);
-    }
-
     @Override
     public boolean isWcgContent() {
         return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 3a06f7a..2873cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.view.View;
 
 /**
@@ -29,7 +30,7 @@
  * See {@link com.android.systemui.statusbar.phone.KeyguardBottomAreaView}.
  */
 public class KeyguardIndication {
-    @NonNull
+    @Nullable
     private final CharSequence mMessage;
     @NonNull
     private final ColorStateList mTextColor;
@@ -56,7 +57,7 @@
     /**
      * Message to display
      */
-    public @NonNull CharSequence getMessage() {
+    public @Nullable CharSequence getMessage() {
         return mMessage;
     }
 
@@ -88,6 +89,17 @@
         return mBackground;
     }
 
+    @Override
+    public String toString() {
+        String str = "KeyguardIndication{";
+        if (!TextUtils.isEmpty(mMessage)) str += "mMessage=" + mMessage;
+        if (mIcon != null) str += " mIcon=" + mIcon;
+        if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener;
+        if (mBackground != null) str += " mBackground=" + mBackground;
+        str += "}";
+        return str;
+    }
+
     /**
      * KeyguardIndication Builder
      */
@@ -101,7 +113,7 @@
         public Builder() { }
 
         /**
-         * Required field. Message to display.
+         * Message to display. Indication requires a non-null message or icon.
          */
         public Builder setMessage(@NonNull CharSequence message) {
             this.mMessage = message;
@@ -117,9 +129,9 @@
         }
 
         /**
-         * Optional. Icon to show next to the text. Icon location changes based on language
-         * display direction. For LTR, icon shows to the left of the message. For RTL, icon shows
-         * to the right of the message.
+         * Icon to show next to the text. Indication requires a non-null icon or message.
+         * Icon location changes based on language display direction. For LTR, icon shows to the
+         * left of the message. For RTL, icon shows to the right of the message.
          */
         public Builder setIcon(Drawable icon) {
             this.mIcon = icon;
@@ -146,8 +158,13 @@
          * Build the KeyguardIndication.
          */
         public KeyguardIndication build() {
-            if (mMessage == null) throw new IllegalStateException("message must be set");
-            if (mTextColor == null) throw new IllegalStateException("text color must be set");
+            if (TextUtils.isEmpty(mMessage) && mIcon == null) {
+                throw new IllegalStateException("message or icon must be set");
+            }
+            if (mTextColor == null) {
+                throw new IllegalStateException("text color must be set");
+            }
+
             return new KeyguardIndication(
                     mMessage, mTextColor, mIcon, mOnClickListener, mBackground);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 8c04143..d4678f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.text.TextUtils;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -105,8 +104,7 @@
     public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
             boolean showImmediately) {
         final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
-        final boolean hasNewIndication = newIndication != null
-                && !TextUtils.isEmpty(newIndication.getMessage());
+        final boolean hasNewIndication = newIndication != null;
         if (!hasNewIndication) {
             mIndicationMessages.remove(type);
             mIndicationQueue.removeIf(x -> x == type);
@@ -203,7 +201,6 @@
             mIndicationQueue.add(type); // re-add to show later
         }
 
-        // pass the style update to be run right before our new indication is shown:
         mView.switchIndication(mIndicationMessages.get(type));
 
         // only schedule next indication if there's more than just this indication in the queue
@@ -289,8 +286,7 @@
         if (hasIndications()) {
             pw.println("    All messages:");
             for (int type : mIndicationMessages.keySet()) {
-                pw.println("        type=" + type
-                        + " message=" + mIndicationMessages.get(type).getMessage());
+                pw.println("        type=" + type + " " + mIndicationMessages.get(type));
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index c67aef6..2f9b17a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,6 +19,8 @@
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
 
+import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle;
+
 import android.app.Activity;
 import android.app.INotificationManager;
 import android.app.people.IPeopleManager;
@@ -39,6 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
+import java.util.Collections;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -49,6 +52,7 @@
 public class PeopleSpaceActivity extends Activity {
 
     private static final String TAG = "PeopleSpaceActivity";
+    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
     private ViewGroup mPeopleSpaceLayout;
     private IPeopleManager mPeopleManager;
@@ -134,9 +138,11 @@
     /** Stores the user selected configuration for {@code mAppWidgetId}. */
     private void storeWidgetConfiguration(PeopleSpaceTile tile) {
         if (PeopleSpaceUtils.DEBUG) {
-            Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
-                    + tile.getId() + " for widget ID: "
-                    + mAppWidgetId);
+            if (DEBUG) {
+                Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
+                        + tile.getId() + " for widget ID: "
+                        + mAppWidgetId);
+            }
         }
 
         PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId);
@@ -144,12 +150,22 @@
         // TODO: Populate new widget with existing conversation notification, if there is any.
         PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
                 mPeopleManager);
+        if (mLauncherApps != null) {
+            try {
+                if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+                mLauncherApps.cacheShortcuts(tile.getPackageName(),
+                        Collections.singletonList(tile.getId()),
+                        getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+            } catch (Exception e) {
+                Log.w(TAG, "Exception caching shortcut:" + e);
+            }
+        }
         finishActivity();
     }
 
     /** Finish activity with a successful widget configuration result. */
     private void finishActivity() {
-        if (PeopleSpaceUtils.DEBUG) Log.d(TAG, "Widget added!");
+        if (DEBUG) Log.d(TAG, "Widget added!");
         mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
         setActivityResult(RESULT_OK);
         finish();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 7eb1fc1..cd1131b 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -17,12 +17,21 @@
 package com.android.systemui.people;
 
 import static android.app.Notification.EXTRA_MESSAGES;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_MEDIA;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
 
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -37,6 +46,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.icu.text.MeasureFormat;
 import android.icu.util.Measure;
 import android.icu.util.MeasureUnit;
@@ -71,6 +81,7 @@
 import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -287,7 +298,7 @@
         SharedPreferences.Editor widgetEditor = widgetSp.edit();
         widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName());
         widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId());
-        int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+        int userId = getUserId(tile);
         widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId);
         widgetEditor.apply();
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -441,20 +452,141 @@
     /** Creates a {@link RemoteViews} for {@code tile}. */
     public static RemoteViews createRemoteViews(Context context,
             PeopleSpaceTile tile, int appWidgetId) {
-        RemoteViews views;
+        RemoteViews viewsForTile = getViewForTile(context, tile);
+        RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile);
+        return setLaunchIntents(context, views, tile, appWidgetId);
+    }
+
+    /**
+     * The prioritization for the {@code tile} content is missed calls, followed by notification
+     * content, then birthdays, then the most recent status, and finally last interaction.
+     */
+    private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) {
         if (tile.getNotificationKey() != null) {
-            views = createNotificationRemoteViews(context, tile);
-        } else if (tile.getBirthdayText() != null) {
-            views = createStatusRemoteViews(context, tile);
-        } else {
-            views = createLastInteractionRemoteViews(context, tile);
+            if (DEBUG) Log.d(TAG, "Create notification view");
+            return createNotificationRemoteViews(context, tile);
         }
-        return setCommonRemoteViewsFields(context, views, tile, appWidgetId);
+
+        // TODO: Add sorting when we expose timestamp of statuses.
+        List<ConversationStatus> statusesForEntireView =
+                tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
+                        c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+        ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
+        if (birthdayStatus != null) {
+            if (DEBUG) Log.d(TAG, "Create birthday view");
+            return createStatusRemoteViews(context, birthdayStatus);
+        }
+
+        if (!statusesForEntireView.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Create status view for: " + statusesForEntireView.get(0).getActivity());
+            }
+            return createStatusRemoteViews(context, statusesForEntireView.get(0));
+        }
+
+        return createLastInteractionRemoteViews(context, tile);
+    }
+
+    @Nullable
+    private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
+            List<ConversationStatus> statuses) {
+        Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+                c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+        if (birthdayStatus.isPresent()) {
+            return birthdayStatus.get();
+        }
+        if (!TextUtils.isEmpty(tile.getBirthdayText())) {
+            return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns whether a {@code status} should have its own entire templated view.
+     *
+     * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+     * not valid to compose an entire view.
+     */
+    private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+        switch (status.getActivity()) {
+            // Birthday & Anniversary don't require text provided or icon provided.
+            case ACTIVITY_BIRTHDAY:
+            case ACTIVITY_ANNIVERSARY:
+                return true;
+            default:
+                // For future birthday, location, new story, video, music, game, and other, the
+                // app must provide either text or an icon.
+                return !TextUtils.isEmpty(status.getDescription())
+                        || status.getIcon() != null;
+        }
+    }
+
+    private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status) {
+        RemoteViews views = new RemoteViews(
+                context.getPackageName(), R.layout.people_space_small_avatar_tile);
+        CharSequence statusText = status.getDescription();
+        if (TextUtils.isEmpty(statusText)) {
+            statusText = getStatusTextByType(context, status.getActivity());
+        }
+        views.setTextViewText(R.id.status, statusText);
+        Icon statusIcon = status.getIcon();
+        if (statusIcon != null) {
+            views.setImageViewIcon(R.id.image, statusIcon);
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
+        } else {
+            views.setViewVisibility(R.id.content_background, View.GONE);
+        }
+        // TODO: Set status pre-defined icons
+        return views;
+    }
+
+    private static String getStatusTextByType(Context context, int activity) {
+        switch (activity) {
+            case ACTIVITY_BIRTHDAY:
+                return context.getString(R.string.birthday_status);
+            case ACTIVITY_UPCOMING_BIRTHDAY:
+                return context.getString(R.string.upcoming_birthday_status);
+            case ACTIVITY_ANNIVERSARY:
+                return context.getString(R.string.anniversary_status);
+            case ACTIVITY_LOCATION:
+                return context.getString(R.string.location_status);
+            case ACTIVITY_NEW_STORY:
+                return context.getString(R.string.new_story_status);
+            case ACTIVITY_MEDIA:
+                return context.getString(R.string.video_status);
+            case ACTIVITY_GAME:
+                return context.getString(R.string.game_status);
+            default:
+                return EMPTY_STRING;
+        }
     }
 
     private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
-            PeopleSpaceTile tile, int appWidgetId) {
+            PeopleSpaceTile tile) {
         try {
+            boolean isAvailable =
+                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+                            c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+            if (isAvailable) {
+                views.setViewVisibility(R.id.availability, View.VISIBLE);
+            } else {
+                views.setViewVisibility(R.id.availability, View.GONE);
+            }
+            boolean hasNewStory =
+                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+                            c -> c.getActivity() == ACTIVITY_NEW_STORY);
+            if (hasNewStory) {
+                views.setViewVisibility(R.id.person_icon_with_story, View.VISIBLE);
+                views.setViewVisibility(R.id.person_icon_only, View.GONE);
+                views.setImageViewIcon(R.id.person_icon_inside_ring, tile.getUserIcon());
+            } else {
+                views.setViewVisibility(R.id.person_icon_with_story, View.GONE);
+                views.setViewVisibility(R.id.person_icon_only, View.VISIBLE);
+                views.setImageViewIcon(R.id.person_icon_only, tile.getUserIcon());
+            }
+
             views.setTextViewText(R.id.name, tile.getUserName().toString());
             views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
             views.setBoolean(R.id.content_background, "setClipToOutline", true);
@@ -466,7 +598,16 @@
                                     tile.getPackageName())
                     )
             );
+            return views;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to set common fields: " + e);
+        }
+        return views;
+    }
 
+    private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
+            PeopleSpaceTile tile, int appWidgetId) {
+        try {
             Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
             activityIntent.addFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK
@@ -484,48 +625,42 @@
                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
             return views;
         } catch (Exception e) {
-            Log.e(TAG, "Failed to set common fields: " + e);
+            Log.e(TAG, "Failed to add launch intents: " + e);
         }
-        return null;
+        return views;
     }
 
     private static RemoteViews createNotificationRemoteViews(Context context,
             PeopleSpaceTile tile) {
         RemoteViews views = new RemoteViews(
-                context.getPackageName(), R.layout.people_space_small_avatar_tile);
+                context.getPackageName(), R.layout.people_space_notification_content_tile);
         Uri image = tile.getNotificationDataUri();
         if (image != null) {
-            //TODO: Use NotificationInlineImageCache
+            // TODO: Use NotificationInlineImageCache
             views.setImageViewUri(R.id.image, image);
-            views.setViewVisibility(R.id.image, View.VISIBLE);
+            views.setViewVisibility(R.id.content_background, View.VISIBLE);
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
             views.setViewVisibility(R.id.content, View.GONE);
         } else {
             CharSequence content = tile.getNotificationContent();
             views = setPunctuationRemoteViewsFields(views, content);
             views.setTextViewText(R.id.content, content);
             views.setViewVisibility(R.id.content, View.VISIBLE);
-            views.setViewVisibility(R.id.image, View.GONE);
+            views.setViewVisibility(R.id.content_background, View.GONE);
         }
-        views.setTextViewText(R.id.time, PeopleSpaceUtils.getLastInteractionString(
+        // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
+        views.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString(
                 context, tile.getLastInteractionTimestamp(), false));
         return views;
     }
 
-    private static RemoteViews createStatusRemoteViews(Context context,
-            PeopleSpaceTile tile) {
-        RemoteViews views = new RemoteViews(
-                context.getPackageName(), R.layout.people_space_large_avatar_tile);
-        views.setTextViewText(R.id.status, tile.getBirthdayText());
-        return views;
-    }
-
     private static RemoteViews createLastInteractionRemoteViews(Context context,
             PeopleSpaceTile tile) {
         RemoteViews views = new RemoteViews(
                 context.getPackageName(), R.layout.people_space_large_avatar_tile);
         String status = PeopleSpaceUtils.getLastInteractionString(
                 context, tile.getLastInteractionTimestamp(), true);
-        views.setTextViewText(R.id.status, status);
+        views.setTextViewText(R.id.last_interaction, status);
         return views;
     }
 
@@ -634,7 +769,7 @@
     private static Long getLastInteraction(IPeopleManager peopleManager,
             PeopleSpaceTile tile) {
         try {
-            int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+            int userId = getUserId(tile);
             String pkg = tile.getPackageName();
             return peopleManager.getLastInteraction(pkg, userId, tile.getId());
         } catch (Exception e) {
@@ -682,20 +817,35 @@
         Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction);
         MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
                 MeasureFormat.FormatWidth.WIDE);
+        MeasureFormat shortFormatter = MeasureFormat.getInstance(Locale.getDefault(),
+                MeasureFormat.FormatWidth.SHORT);
         if (durationSinceLastInteraction.toHours() < MIN_HOUR) {
-            return context.getString(includeLastChatted ? R.string.last_interaction_status_less_than
-                            : R.string.less_than_timestamp,
-                    formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status_less_than,
+                        formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE)));
         } else if (durationSinceLastInteraction.toDays() < ONE_DAY) {
-            return context.getString(
-                    includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
-                    formatter.formatMeasures(
-                            new Measure(durationSinceLastInteraction.toHours(), MeasureUnit.HOUR)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status,
+                        formatter.formatMeasures(
+                                new Measure(durationSinceLastInteraction.toHours(),
+                                        MeasureUnit.HOUR)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toHours(),
+                            MeasureUnit.HOUR)));
         } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
-            return context.getString(
-                    includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
-                    formatter.formatMeasures(
-                            new Measure(durationSinceLastInteraction.toDays(), MeasureUnit.DAY)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status,
+                        formatter.formatMeasures(
+                                new Measure(durationSinceLastInteraction.toDays(),
+                                        MeasureUnit.DAY)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toHours(),
+                            MeasureUnit.DAY)));
         } else {
             return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
                             ? (includeLastChatted ? R.string.last_interaction_status :
@@ -883,4 +1033,14 @@
     public static String getKey(String shortcutId, String packageName, int userId) {
         return shortcutId + "/" + userId + "/" + packageName;
     }
+
+    /** Returns the userId associated with a {@link PeopleSpaceTile} */
+    public static int getUserId(PeopleSpaceTile tile) {
+        return getUserHandle(tile).getIdentifier();
+    }
+
+    /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */
+    public static UserHandle getUserHandle(PeopleSpaceTile tile) {
+        return UserHandle.getUserHandleForUid(tile.getUid());
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java
new file mode 100644
index 0000000..b188acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.people.widget;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.FeatureFlags;
+
+import javax.inject.Inject;
+
+/**
+ * Enables People Space widgets.
+ */
+@SysUISingleton
+public class PeopleSpaceWidgetEnabler extends SystemUI {
+    private static final String TAG = "PeopleSpaceWdgtEnabler";
+    private Context mContext;
+    private FeatureFlags mFeatureFlags;
+
+    @Inject
+    public PeopleSpaceWidgetEnabler(Context context, FeatureFlags featureFlags) {
+        super(context);
+        mContext = context;
+        mFeatureFlags = featureFlags;
+    }
+
+    @Override
+    public void start() {
+        Log.d(TAG, "Starting service");
+        try {
+            boolean showPeopleSpace = mFeatureFlags.isPeopleTileEnabled();
+            mContext.getPackageManager().setComponentEnabledSetting(
+                    new ComponentName(mContext, PeopleSpaceWidgetProvider.class),
+                    showPeopleSpace
+                            ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                            : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+        } catch (Exception e) {
+            Log.w(TAG, "Error enabling People Space widget:", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index f5577d3..3d1055f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,13 +16,20 @@
 
 package com.android.systemui.people.widget;
 
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
 import android.app.PendingIntent;
 import android.app.people.IPeopleManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProvider;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 import android.widget.RemoteViews;
@@ -32,6 +39,8 @@
 import com.android.systemui.R;
 import com.android.systemui.people.PeopleSpaceUtils;
 
+import java.util.Collections;
+
 /** People Space Widget Provider class. */
 public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
     private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -88,11 +97,31 @@
     @Override
     public void onDeleted(Context context, int[] appWidgetIds) {
         super.onDeleted(context, appWidgetIds);
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+
         for (int widgetId : appWidgetIds) {
             if (DEBUG) Log.d(TAG, "Widget removed");
             mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
+            if (launcherApps != null) {
+                SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId),
+                        Context.MODE_PRIVATE);
+                String packageName = widgetSp.getString(PACKAGE_NAME, null);
+                String shortcutId = widgetSp.getString(SHORTCUT_ID, null);
+                int userId = widgetSp.getInt(USER_ID, -1);
+
+                if (packageName != null && shortcutId != null && userId != -1) {
+                    try {
+                        if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId);
+                        launcherApps.uncacheShortcuts(packageName,
+                                Collections.singletonList(shortcutId),
+                                UserHandle.of(userId),
+                                LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+                    } catch (Exception e) {
+                        Log.d(TAG, "Exception uncaching shortcut:" + e);
+                    }
+                }
+            }
             PeopleSpaceUtils.removeStorageForTile(context, widgetId);
         }
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3866382..1411fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -170,7 +170,8 @@
         // Update visibility of footer
         mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
                 || vpnName != null || vpnNameWorkProfile != null
-                || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
+                || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
+                || (hasWorkProfile && isNetworkLoggingEnabled);
         // Update the string
         mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
                 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
@@ -275,12 +276,30 @@
                     vpnName);
         }
         if (isProfileOwnerOfOrganizationOwnedDevice) {
+            if (isNetworkLoggingEnabled) {
+                if (organizationName == null) {
+                    return mContext.getString(
+                            R.string.quick_settings_disclosure_management_monitoring);
+                }
+                return mContext.getString(
+                        R.string.quick_settings_disclosure_named_management_monitoring,
+                        organizationName);
+            }
             if (workProfileOrganizationName == null) {
                 return mContext.getString(R.string.quick_settings_disclosure_management);
             }
             return mContext.getString(R.string.quick_settings_disclosure_named_management,
                     workProfileOrganizationName);
         }
+        if (hasWorkProfile && isNetworkLoggingEnabled) {
+            if (workProfileOrganizationName == null) {
+                return mContext.getString(
+                        R.string.quick_settings_disclosure_managed_profile_monitoring);
+            }
+            return mContext.getString(
+                    R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+                    workProfileOrganizationName);
+        }
         return null;
     }
 
@@ -367,7 +386,8 @@
         }
 
         // network logging section
-        CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled);
+        CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
+                isNetworkLoggingEnabled);
         if (networkLoggingMessage == null) {
             dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
         } else {
@@ -492,9 +512,15 @@
         return mContext.getString(R.string.monitoring_description_ca_certificate);
     }
 
-    protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) {
+    protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
+            boolean isNetworkLoggingEnabled) {
         if (!isNetworkLoggingEnabled) return null;
-        return mContext.getString(R.string.monitoring_description_management_network_logging);
+        if (isDeviceManaged) {
+            return mContext.getString(R.string.monitoring_description_management_network_logging);
+        } else {
+            return mContext.getString(
+                    R.string.monitoring_description_managed_profile_network_logging);
+        }
     }
 
     protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 9383aef..1386ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -24,15 +24,21 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
+import com.android.internal.widget.ExploreByTouchHelper;
 import com.android.systemui.R;
 
 /**
@@ -80,6 +86,8 @@
         t.recycle();
         // 48 dp touchable region around each handle.
         mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+
+        setAccessibilityDelegate(new AccessibilityHelper());
     }
 
     @Override
@@ -231,6 +239,87 @@
         return CropBoundary.NONE;
     }
 
+    private class AccessibilityHelper extends ExploreByTouchHelper {
+
+        private static final int TOP_HANDLE_ID = 1;
+        private static final int BOTTOM_HANDLE_ID = 2;
+
+        AccessibilityHelper() {
+            super(CropView.this);
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+                return TOP_HANDLE_ID;
+            }
+            if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+                return BOTTOM_HANDLE_ID;
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+            virtualViewIds.add(TOP_HANDLE_ID);
+            virtualViewIds.add(BOTTOM_HANDLE_ID);
+        }
+
+        @Override
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            switch(virtualViewId) {
+                case TOP_HANDLE_ID:
+                    event.setContentDescription(
+                            getResources().getString(R.string.screenshot_top_boundary));
+                    break;
+                case BOTTOM_HANDLE_ID:
+                    event.setContentDescription(
+                            getResources().getString(R.string.screenshot_bottom_boundary));
+                    break;
+            }
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(int virtualViewId,
+                AccessibilityNodeInfo node) {
+            switch(virtualViewId) {
+                case TOP_HANDLE_ID:
+                    node.setContentDescription(
+                            getResources().getString(R.string.screenshot_top_boundary));
+                    setNodePositions(mTopCrop, node);
+                    break;
+                case BOTTOM_HANDLE_ID:
+                    node.setContentDescription(
+                            getResources().getString(R.string.screenshot_bottom_boundary));
+                    setNodePositions(mBottomCrop, node);
+                    break;
+            }
+
+            // TODO: need to figure out the full set of actions to support here.
+            node.addAction(
+                    AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+            node.setClickable(true);
+            node.setFocusable(true);
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(
+                int virtualViewId, int action, Bundle arguments) {
+            return false;
+        }
+
+        private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
+            int pixels = fractionToPixels(fraction);
+            Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
+                    getWidth(), (int) (pixels + mCropTouchMargin));
+            node.setBoundsInParent(rect);
+            int[] pos = new int[2];
+            getLocationOnScreen(pos);
+            rect.offset(pos[0], pos[1]);
+            node.setBoundsInScreen(rect);
+        }
+    }
+
     /**
      * Listen for crop motion events and state.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index d707bca0..9f182e1 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -28,6 +28,7 @@
 import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
 import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
 import android.os.Bundle
+import android.os.Handler
 import android.text.Html
 import android.util.Log
 import com.android.internal.app.AlertActivity
@@ -43,10 +44,13 @@
 
     companion object {
         private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+
+        private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
     }
 
     private var sensor = -1
     private lateinit var sensorUsePackageName: String
+    private var unsuppressImmediately = false
 
     private lateinit var sensorPrivacyManager: SensorPrivacyManager
     private lateinit var appOpsManager: AppOpsManager
@@ -118,6 +122,7 @@
         super.onStart()
 
         sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true)
+        unsuppressImmediately = false
     }
 
     override fun onClick(dialog: DialogInterface?, which: Int) {
@@ -131,16 +136,16 @@
                         }
 
                         override fun onDismissSucceeded() {
-                            sensorPrivacyManager
-                                    .setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                            setResult(RESULT_OK)
+                            disableSensorPrivacy()
                         }
                     })
                 } else {
-                    sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                    setResult(RESULT_OK)
+                    disableSensorPrivacy()
                 }
             }
+            BUTTON_NEGATIVE -> {
+                unsuppressImmediately = false
+            }
         }
 
         dismiss()
@@ -149,10 +154,24 @@
     override fun onStop() {
         super.onDestroy()
 
-        sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+        if (unsuppressImmediately) {
+            sensorPrivacyManager
+                    .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+        } else {
+            Handler(mainLooper).postDelayed({
+                sensorPrivacyManager
+                        .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+            }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS)
+        }
     }
 
     override fun onBackPressed() {
         // do not allow backing out
     }
+
+    private fun disableSensorPrivacy() {
+        sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+        unsuppressImmediately = true
+        setResult(RESULT_OK)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c70a93b..7e2d27a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -208,7 +208,6 @@
                 mLockScreenMode);
         updateIndication(false /* animate */);
         updateDisclosure();
-        updateOwnerInfo();
         if (mBroadcastReceiver == null) {
             // Update the disclosure proactively to avoid IPC on the critical path.
             mBroadcastReceiver = new BroadcastReceiver() {
@@ -261,18 +260,21 @@
     }
 
     /**
-     * Doesn't include owner information or disclosure which get triggered separately.
+     * Doesn't include disclosure which gets triggered separately.
      */
     private void updateIndications(boolean animate, int userId) {
+        updateOwnerInfo();
         updateBattery(animate);
         updateUserLocked(userId);
         updateTransient();
         updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
         updateAlignment();
+        updateLogoutView();
         updateResting();
     }
 
     private void updateDisclosure() {
+        // avoid calling this method since it has an IPC
         if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
             final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
             final CharSequence disclosure =  organizationName != null
@@ -291,7 +293,34 @@
         }
 
         if (isKeyguardLayoutEnabled()) {
-            updateIndication(false); // resting indication may need to update
+            updateResting();
+        }
+    }
+
+    private void updateOwnerInfo() {
+        if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
+            return;
+        }
+        String info = mLockPatternUtils.getDeviceOwnerInfo();
+        if (info == null) {
+            // Use the current user owner information if enabled.
+            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            if (ownerInfoEnabled) {
+                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+            }
+        }
+        if (info != null) {
+            mRotateTextViewController.updateIndication(
+                    INDICATION_TYPE_OWNER_INFO,
+                    new KeyguardIndication.Builder()
+                            .setMessage(info)
+                            .setTextColor(mInitialTextColorState)
+                            .build(),
+                    false);
+        } else {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
         }
     }
 
@@ -400,56 +429,34 @@
 
     private void updateLogoutView() {
         if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
             return;
         }
         final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-        String logoutString = shouldShowLogout ? mContext.getResources().getString(
-                    com.android.internal.R.string.global_action_logout) : null;
-        mRotateTextViewController.updateIndication(
-                INDICATION_TYPE_LOGOUT,
-                new KeyguardIndication.Builder()
-                        .setMessage(logoutString)
-                        .setTextColor(mInitialTextColorState)
-                        .setBackground(mContext.getDrawable(
-                                com.android.systemui.R.drawable.logout_button_background))
-                        .setClickListener((view) -> {
-                            int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
-                            try {
-                                mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
-                                mIActivityManager.stopUser(currentUserId, true /* force */, null);
-                            } catch (RemoteException re) {
-                                Log.e(TAG, "Failed to logout user", re);
-                            }
-                        })
-                .build(),
-                false);
-        updateIndication(false); // resting indication may need to update
-    }
-
-    private void updateOwnerInfo() {
-        if (!isKeyguardLayoutEnabled()) {
-            return;
-        }
-        String info = mLockPatternUtils.getDeviceOwnerInfo();
-        if (info == null) {
-            // Use the current user owner information if enabled.
-            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            if (ownerInfoEnabled) {
-                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
-            }
-        }
-        if (info != null) {
+        if (shouldShowLogout) {
             mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_OWNER_INFO,
+                    INDICATION_TYPE_LOGOUT,
                     new KeyguardIndication.Builder()
-                            .setMessage(info)
+                            .setMessage(mContext.getResources().getString(
+                                    com.android.internal.R.string.global_action_logout))
                             .setTextColor(mInitialTextColorState)
+                            .setBackground(mContext.getDrawable(
+                                    com.android.systemui.R.drawable.logout_button_background))
+                            .setClickListener((view) -> {
+                                int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+                                try {
+                                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                                    mIActivityManager.stopUser(currentUserId, true /* force */,
+                                            null);
+                                } catch (RemoteException re) {
+                                    Log.e(TAG, "Failed to logout user", re);
+                                }
+                            })
                             .build(),
                     false);
         } else {
-            updateIndication(false); // resting indication may need to update
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
         }
     }
 
@@ -1042,7 +1049,6 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (mVisible) {
-                updateOwnerInfo();
                 updateIndication(false);
             }
         }
@@ -1057,7 +1063,7 @@
         @Override
         public void onLogoutEnabledChanged() {
             if (mVisible) {
-                updateLogoutView();
+                updateIndication(false);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 8aadef8..2f9fa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -68,7 +68,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import androidx.constraintlayout.widget.ConstraintSet;
 
@@ -405,6 +404,7 @@
     // Used for two finger gesture as well as accessibility shortcut to QS.
     private boolean mQsExpandImmediate;
     private boolean mTwoFingerQsExpandPossible;
+    private String mHeaderDebugInfo;
 
     /**
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
@@ -3423,8 +3423,8 @@
         return mView.getHeight();
     }
 
-    public TextView getHeaderDebugInfo() {
-        return mView.findViewById(R.id.header_debug_info);
+    public void setHeaderDebugInfo(String text) {
+        if (DEBUG) mHeaderDebugInfo = text;
     }
 
     public void onThemeChanged() {
@@ -4087,6 +4087,8 @@
             p.setStrokeWidth(2);
             p.setStyle(Paint.Style.STROKE);
             canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+            p.setTextSize(24);
+            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
             p.setColor(Color.BLUE);
             canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
             p.setColor(Color.GREEN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 024a0b1..525f220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -29,7 +29,6 @@
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
-import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -162,11 +161,6 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
-        if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
-            mNotificationPanelDebugText.setVisibility(View.VISIBLE);
-        }
-
         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
                 Context.VR_SERVICE));
         if (vrManager != null) {
@@ -332,7 +326,7 @@
         // Begin old BaseStatusBar.userSwitched
         mHeadsUpManager.setUser(newUserId);
         // End old BaseStatusBar.userSwitched
-        if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+        if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
         mCommandQueue.animateCollapsePanels();
         if (mReinflateNotificationsOnUserSwitched) {
             updateNotificationsOnDensityOrFontScaleChanged();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 10332bd..0c69ffd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -100,7 +100,6 @@
         return new ImageWallpaper() {
             @Override
             public Engine onCreateEngine() {
-                onCreate();
                 return new GLEngine(mHandler) {
                     @Override
                     public Context getDisplayContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
new file mode 100644
index 0000000..b44fb8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard;
+
+import static android.graphics.Color.WHITE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardIndicationTest extends SysuiTestCase {
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutMessageOrIcon() {
+        new KeyguardIndication.Builder()
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutColor() {
+        new KeyguardIndication.Builder()
+                .setMessage("message")
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithEmptyMessage() {
+        new KeyguardIndication.Builder()
+                .setMessage("")
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test
+    public void testCreateIndicationWithMessage() {
+        final String text = "regular indication";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+    }
+
+    @Test
+    public void testCreateIndicationWithIcon() {
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    @Test
+    public void testCreateIndicationWithMessageAndIcon() {
+        final String text = "indication with msg and icon";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    final Drawable mDrawable = new Drawable() {
+        @Override
+        public void draw(@NonNull Canvas canvas) { }
+
+        @Override
+        public void setAlpha(int alpha) { }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) { }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    };
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 4ee2759..d79155c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,11 +16,18 @@
 
 package com.android.systemui.people;
 
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+
 import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +46,7 @@
 import android.app.NotificationManager;
 import android.app.Person;
 import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -46,13 +54,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.database.Cursor;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.ContactsContract;
 import android.provider.Settings;
@@ -60,6 +68,9 @@
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.systemui.R;
@@ -101,27 +112,45 @@
     private static final int TEST_COLUMN_INDEX = 1;
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
+    private static final String GAME_DESCRIPTION = "Playing a game!";
+    private static final String NAME = "username";
     private static final Person PERSON = new Person.Builder()
             .setName("name")
             .setKey("abc")
             .setUri(URI.toString())
             .setBot(false)
             .build();
+    private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+            new PeopleSpaceTile
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(0L)
+                    .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
-                    .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(123L)
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
+    private static final ConversationStatus GAME_STATUS =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+                    .setDescription(GAME_DESCRIPTION)
+                    .build();
+    private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+                    .setAvailability(AVAILABILITY_AVAILABLE)
+                    .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
             SHORTCUT_ID_1).setLongLabel(
-            "name").setPerson(PERSON)
+            NAME).setPerson(PERSON)
             .build();
     private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
             SHORTCUT_ID_1).setLongLabel(
-            "name")
+            NAME)
             .build();
     private final Notification mNotification1 = new Notification.Builder(mContext, "test")
             .setContentTitle("TEST_TITLE")
@@ -189,10 +218,12 @@
     @Mock
     private Context mMockContext;
     @Mock
+    private PackageManager mPackageManager;
+    @Mock
     private NotificationEntryManager mNotificationEntryManager;
 
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
@@ -212,6 +243,12 @@
                 isNull())).thenReturn(mMockCursor);
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
+        when(mMockContext.getString(R.string.basic_status)).thenReturn(
+                mContext.getString(R.string.basic_status));
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+                mContext.getString(R.string.over_timestamp));
+        when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
         when(mNotificationEntryManager.getVisibleNotifications())
                 .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
     }
@@ -621,6 +658,137 @@
                 any());
     }
 
+    @Test
+    public void testCreateRemoteViewsWithLastInteractionTime() {
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // No availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.GONE, availability.getVisibility());
+        // No new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        assertEquals(View.GONE, personIconWithStory.getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.status)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+        PeopleSpaceTile tileWithAvailabilityAndNewStory =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+                                new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_GAME).build())).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+                tileWithAvailabilityAndNewStory, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction over status.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.status)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(
+                                NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_BIRTHDAY).build())).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusTemplate, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has status text from backup text.
+        TextView statusContent = (TextView) result.findViewById(R.id.status);
+        assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithStatusTemplate() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(GAME_STATUS,
+                                NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusTemplate, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has status.
+        TextView statusContent = (TextView) result.findViewById(R.id.status);
+        assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithNotificationTemplate() {
+        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+                .setNotificationDataUri(null)
+                .setStatuses(Arrays.asList(GAME_STATUS,
+                        NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusAndNotification, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        TextView subtext = (TextView) result.findViewById(R.id.subtext);
+        assertTrue(subtext.getText().toString().contains("weeks ago"));
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.content);
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+    }
+
     private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
             boolean importantConversation, long lastInteractionTimestamp) throws Exception {
         ConversationChannelWrapper convo = new ConversationChannelWrapper();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index fd0715b..862e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -443,9 +443,18 @@
 
     @Test
     public void testGetNetworkLoggingMessage() {
-        assertEquals(null, mFooter.getNetworkLoggingMessage(false));
+        // Test network logging message on a device with a device owner.
+        // Network traffic may be monitored on the device.
+        assertEquals(null, mFooter.getNetworkLoggingMessage(true, false));
         assertEquals(mContext.getString(R.string.monitoring_description_management_network_logging),
-                     mFooter.getNetworkLoggingMessage(true));
+                mFooter.getNetworkLoggingMessage(true, true));
+
+        // Test network logging message on a device with a managed profile owner
+        // Network traffic may be monitored on the work profile.
+        assertEquals(null, mFooter.getNetworkLoggingMessage(false, false));
+        assertEquals(
+                mContext.getString(R.string.monitoring_description_managed_profile_network_logging),
+                mFooter.getNetworkLoggingMessage(false, true));
     }
 
     @Test
diff --git a/services/Android.bp b/services/Android.bp
index 61591c2..3154628 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -33,6 +33,7 @@
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
+        ":services.texttospeech-sources",
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
@@ -83,6 +84,7 @@
         "services.startop",
         "services.systemcaptions",
         "services.translation",
+        "services.texttospeech",
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index eff410c..809304b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2624,12 +2624,26 @@
             info.minWidth = value != null ? value.data : 0;
             value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
             info.minHeight = value != null ? value.data : 0;
+
             value = sa.peekValue(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth);
             info.minResizeWidth = value != null ? value.data : info.minWidth;
             value = sa.peekValue(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight);
             info.minResizeHeight = value != null ? value.data : info.minHeight;
+
+            value = sa.peekValue(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeWidth);
+            info.maxResizeWidth = value != null ? value.data : 0;
+            value = sa.peekValue(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeHeight);
+            info.maxResizeHeight = value != null ? value.data : 0;
+
+            info.targetCellWidth = sa.getInt(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellWidth, 0);
+            info.targetCellHeight = sa.getInt(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellHeight, 0);
+
             info.updatePeriodMillis = sa.getInt(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
             info.initialLayout = sa.getResourceId(
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f36910..0725bb2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,7 +127,7 @@
         "android.hardware.health-V2.0-java",
         "android.hardware.health-V2.1-java",
         "android.hardware.light-V1-java",
-        "android.hardware.tv.cec-V1.0-java",
+        "android.hardware.tv.cec-V1.1-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1-java",
         "android.hardware.biometrics.face-V1.0-java",
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 7f0b962..edaf6a90 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -105,8 +105,6 @@
 
     private static final String TAG = "SensorPrivacyService";
 
-    private static final int SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000;
-
     /** Version number indicating compatibility parsing the persisted file */
     private static final int CURRENT_PERSISTENCE_VERSION = 1;
     /** Version number indicating the persisted data needs upgraded to match new internal data
@@ -756,10 +754,7 @@
 
                     suppressPackageReminderTokens.add(token);
                 } else {
-                    mHandler.postDelayed(PooledLambda.obtainRunnable(
-                            SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
-                            this, key, token),
-                            SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS);
+                    mHandler.removeSuppressPackageReminderToken(key, token);
                 }
             }
         }
@@ -1110,6 +1105,13 @@
             }
             listeners.finishBroadcast();
         }
+
+        public void removeSuppressPackageReminderToken(Pair<String, UserHandle> key,
+                IBinder token) {
+            sendMessage(PooledLambda.obtainMessage(
+                    SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
+                    mSensorPrivacyServiceImpl, key, token));
+        }
     }
 
     private final class DeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 27210da..ed4e1d9 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -22,6 +22,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -29,6 +30,7 @@
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnManagementService;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -54,6 +56,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.LocationPermissionChecker;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -124,6 +127,7 @@
  *
  * @hide
  */
+// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
 public class VcnManagementService extends IVcnManagementService.Stub {
     @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
 
@@ -147,6 +151,9 @@
     @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
     @NonNull private final VcnContext mVcnContext;
 
+    /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */
+    @Nullable private LocationPermissionChecker mLocationPermissionChecker;
+
     @GuardedBy("mLock")
     @NonNull
     private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
@@ -169,6 +176,10 @@
     private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
         mContext = requireNonNull(context, "Missing context");
@@ -293,8 +304,8 @@
                 @NonNull ParcelUuid subscriptionGroup,
                 @NonNull VcnConfig config,
                 @NonNull TelephonySubscriptionSnapshot snapshot,
-                @NonNull VcnSafemodeCallback safemodeCallback) {
-            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
+                @NonNull VcnSafeModeCallback safeModeCallback) {
+            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safeModeCallback);
         }
 
         /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -302,6 +313,11 @@
             // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
+
+        /** Creates a new LocationPermissionChecker for the provided Context. */
+        public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
+            return new LocationPermissionChecker(context);
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -309,6 +325,7 @@
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkProvider(mNetworkProvider);
         mTelephonySubscriptionTracker.register();
+        mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext());
     }
 
     private void enforcePrimaryUser() {
@@ -440,12 +457,12 @@
         // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
         //                    VCN.
 
-        final VcnSafemodeCallbackImpl safemodeCallback =
-                new VcnSafemodeCallbackImpl(subscriptionGroup);
+        final VcnSafeModeCallbackImpl safeModeCallback =
+                new VcnSafeModeCallbackImpl(subscriptionGroup);
 
         final Vcn newInstance =
                 mDeps.newVcn(
-                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
+                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safeModeCallback);
         mVcns.put(subscriptionGroup, newInstance);
 
         // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -551,6 +568,14 @@
         }
     }
 
+    /** Get current VcnStatusCallbacks for testing purposes. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
+        }
+    }
+
     /** Binder death recipient used to remove a registered policy listener. */
     private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
         @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
@@ -672,22 +697,109 @@
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
 
-    /** Callback for signalling when a Vcn has entered Safemode. */
-    public interface VcnSafemodeCallback {
-        /** Called by a Vcn to signal that it has entered Safemode. */
-        void onEnteredSafemode();
+    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
+        @NonNull final ParcelUuid mSubGroup;
+        @NonNull final IVcnStatusCallback mCallback;
+        @NonNull final String mPkgName;
+        final int mUid;
+
+        private VcnStatusCallbackInfo(
+                @NonNull ParcelUuid subGroup,
+                @NonNull IVcnStatusCallback callback,
+                @NonNull String pkgName,
+                int uid) {
+            mSubGroup = subGroup;
+            mCallback = callback;
+            mPkgName = pkgName;
+            mUid = uid;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.e(TAG, "app died without unregistering VcnStatusCallback");
+            unregisterVcnStatusCallback(mCallback);
+        }
     }
 
-    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
-    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
+    /** Registers the provided callback for receiving VCN status updates. */
+    @Override
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subGroup,
+            @NonNull IVcnStatusCallback callback,
+            @NonNull String opPkgName) {
+        final int callingUid = mDeps.getBinderCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(subGroup, "subGroup must not be null");
+            requireNonNull(callback, "callback must not be null");
+            requireNonNull(opPkgName, "opPkgName must not be null");
+
+            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
+
+            final IBinder cbBinder = callback.asBinder();
+            final VcnStatusCallbackInfo cbInfo =
+                    new VcnStatusCallbackInfo(
+                            subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+
+            try {
+                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
+            } catch (RemoteException e) {
+                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
+                return;
+            }
+
+            synchronized (mLock) {
+                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
+                    throw new IllegalStateException(
+                            "Attempting to register a callback that is already in use");
+                }
+
+                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /** Unregisters the provided callback from receiving future VCN status updates. */
+    @Override
+    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(callback, "callback must not be null");
+
+            final IBinder cbBinder = callback.asBinder();
+            synchronized (mLock) {
+                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
+
+                if (cbInfo != null) {
+                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
+    /** Callback for signalling when a Vcn has entered safe mode. */
+    public interface VcnSafeModeCallback {
+        /** Called by a Vcn to signal that it has entered safe mode. */
+        void onEnteredSafeMode();
+    }
+
+    /** VcnSafeModeCallback is used by Vcns to notify VcnManagementService on entering safe mode. */
+    private class VcnSafeModeCallbackImpl implements VcnSafeModeCallback {
         @NonNull private final ParcelUuid mSubGroup;
 
-        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
+        private VcnSafeModeCallbackImpl(@NonNull final ParcelUuid subGroup) {
             mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
         }
 
         @Override
-        public void onEnteredSafemode() {
+        public void onEnteredSafeMode() {
             synchronized (mLock) {
                 // Ignore if this subscription group doesn't exist anymore
                 if (!mVcns.containsKey(mSubGroup)) {
@@ -695,6 +807,27 @@
                 }
 
                 notifyAllPolicyListenersLocked();
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (!mSubGroup.equals(cbInfo.mSubGroup)) {
+                        continue;
+                    }
+                    if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
+                            mSubGroup, cbInfo.mPkgName)) {
+                        continue;
+                    }
+
+                    if (!mLocationPermissionChecker.checkLocationPermission(
+                            cbInfo.mPkgName,
+                            "VcnStatusCallback" /* featureId */,
+                            cbInfo.mUid,
+                            null /* message */)) {
+                        continue;
+                    }
+
+                    Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 76c3467..26ce0d7 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -61,7 +61,7 @@
     private final Object mLock = new Object();
     private final Handler mHandler;
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, Settings> mSettings = new ArrayMap<>();
+    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
 
     public GameManagerService(Context context) {
         this(context, createServiceThread().getLooper());
@@ -99,7 +99,7 @@
                     synchronized (mLock) {
                         removeMessages(WRITE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
-                            Settings userSettings = mSettings.get(userId);
+                            GameManagerSettings userSettings = mSettings.get(userId);
                             userSettings.writePersistentDataLocked();
                         }
                     }
@@ -123,7 +123,7 @@
                         removeMessages(WRITE_SETTINGS, msg.obj);
                         removeMessages(REMOVE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
-                            final Settings userSettings = mSettings.get(userId);
+                            final GameManagerSettings userSettings = mSettings.get(userId);
                             mSettings.remove(userId);
                             userSettings.writePersistentDataLocked();
                         }
@@ -190,7 +190,7 @@
             if (!mSettings.containsKey(userId)) {
                 return GameManager.GAME_MODE_UNSUPPORTED;
             }
-            Settings userSettings = mSettings.get(userId);
+            GameManagerSettings userSettings = mSettings.get(userId);
             return userSettings.getGameModeLocked(packageName);
         }
     }
@@ -211,7 +211,7 @@
             if (!mSettings.containsKey(userId)) {
                 return;
             }
-            Settings userSettings = mSettings.get(userId);
+            GameManagerSettings userSettings = mSettings.get(userId);
             userSettings.setGameModeLocked(packageName, gameMode);
             final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
             msg.obj = userId;
@@ -235,7 +235,8 @@
                 return;
             }
 
-            Settings userSettings = new Settings(Environment.getDataSystemDeDirectory(userId));
+            GameManagerSettings userSettings =
+                    new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
             mSettings.put(userId, userSettings);
             userSettings.readPersistentDataLocked();
         }
diff --git a/services/core/java/com/android/server/app/Settings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
similarity index 98%
rename from services/core/java/com/android/server/app/Settings.java
rename to services/core/java/com/android/server/app/GameManagerSettings.java
index ab367fb..3e32380 100644
--- a/services/core/java/com/android/server/app/Settings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -41,7 +41,7 @@
  * Persists all GameService related settings.
  * @hide
  */
-public class Settings {
+public class GameManagerSettings {
 
     // The XML file follows the below format:
     // <?xml>
@@ -63,7 +63,7 @@
     // PackageName -> GameMode
     private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
 
-    Settings(File dataDir) {
+    GameManagerSettings(File dataDir) {
         mSystemDir = new File(dataDir, "system");
         mSystemDir.mkdirs();
         FileUtils.setPermissions(mSystemDir.toString(),
@@ -144,6 +144,7 @@
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
                     && type != XmlPullParser.END_DOCUMENT) {
+                // Do nothing
             }
             if (type != XmlPullParser.START_TAG) {
                 Slog.wtf(GameManagerService.TAG,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 06bcada..1643ec1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -50,6 +50,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.function.Predicate;
 
@@ -149,6 +150,12 @@
      *         returns {@code null}.
      */
     static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
+        HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(),
+                atomWriter);
+        if (controller != null) {
+            return controller;
+        }
+        HdmiLogger.warning("Unable to use cec@1.1");
         return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
     }
 
@@ -312,7 +319,7 @@
     }
 
     /**
-     * Return CEC version of the device.
+     * Return highest CEC version supported by this device.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
@@ -745,13 +752,202 @@
         boolean nativeIsConnected(int port);
     }
 
-    private static final class NativeWrapperImpl implements NativeWrapper,
+    private static final class NativeWrapperImpl11 implements NativeWrapper,
             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
-        private IHdmiCec mHdmiCec;
+        private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
+        @Nullable private HdmiCecCallback mCallback;
+
         private final Object mLock = new Object();
         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+        @Override
+        public String nativeInit() {
+            return (connectToHal() ? mHdmiCec.toString() : null);
+        }
+
+        boolean connectToHal() {
+            try {
+                mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true);
+                try {
+                    mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
+                } catch (RemoteException e) {
+                    HdmiLogger.error("Couldn't link to death : ", e);
+                }
+            } catch (RemoteException | NoSuchElementException e) {
+                HdmiLogger.error("Couldn't connect to cec@1.1", e);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void onValues(int result, short addr) {
+            if (result == Result.SUCCESS) {
+                synchronized (mLock) {
+                    mPhysicalAddress = new Short(addr).intValue();
+                }
+            }
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
+                HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
+                connectToHal();
+                // Reconnect the callback
+                if (mCallback != null) {
+                    setCallback(mCallback);
+                }
+            }
+        }
+
+        @Override
+        public void setCallback(HdmiCecCallback callback) {
+            mCallback = callback;
+            try {
+                mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
+            } catch (RemoteException e) {
+                HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+            }
+        }
+
+        @Override
+        public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+            android.hardware.tv.cec.V1_1.CecMessage message =
+                    new android.hardware.tv.cec.V1_1.CecMessage();
+            message.initiator = srcAddress;
+            message.destination = dstAddress;
+            message.body = new ArrayList<>(body.length);
+            for (byte b : body) {
+                message.body.add(b);
+            }
+            try {
+                return mHdmiCec.sendMessage_1_1(message);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to send CEC message : ", e);
+                return SendMessageResult.FAIL;
+            }
+        }
+
+        @Override
+        public int nativeAddLogicalAddress(int logicalAddress) {
+            try {
+                return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to add a logical address : ", e);
+                return Result.FAILURE_INVALID_ARGS;
+            }
+        }
+
+        @Override
+        public void nativeClearLogicalAddress() {
+            try {
+                mHdmiCec.clearLogicalAddress();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to clear logical address : ", e);
+            }
+        }
+
+        @Override
+        public int nativeGetPhysicalAddress() {
+            try {
+                mHdmiCec.getPhysicalAddress(this);
+                return mPhysicalAddress;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get physical address : ", e);
+                return INVALID_PHYSICAL_ADDRESS;
+            }
+        }
+
+        @Override
+        public int nativeGetVersion() {
+            try {
+                return mHdmiCec.getCecVersion();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get cec version : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
+        }
+
+        @Override
+        public int nativeGetVendorId() {
+            try {
+                return mHdmiCec.getVendorId();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get vendor id : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
+        }
+
+        @Override
+        public HdmiPortInfo[] nativeGetPortInfos() {
+            try {
+                ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
+                        mHdmiCec.getPortInfo();
+                HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
+                int i = 0;
+                for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
+                    hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+                            portInfo.type,
+                            portInfo.physicalAddress,
+                            portInfo.cecSupported,
+                            false,
+                            portInfo.arcSupported);
+                    i++;
+                }
+                return hdmiPortInfo;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get port information : ", e);
+                return null;
+            }
+        }
+
+        @Override
+        public void nativeSetOption(int flag, boolean enabled) {
+            try {
+                mHdmiCec.setOption(flag, enabled);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set option : ", e);
+            }
+        }
+
+        @Override
+        public void nativeSetLanguage(String language) {
+            try {
+                mHdmiCec.setLanguage(language);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set language : ", e);
+            }
+        }
+
+        @Override
+        public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+            try {
+                mHdmiCec.enableAudioReturnChannel(port, flag);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to enable/disable ARC : ", e);
+            }
+        }
+
+        @Override
+        public boolean nativeIsConnected(int port) {
+            try {
+                return mHdmiCec.isConnected(port);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get connection info : ", e);
+                return false;
+            }
+        }
+    }
+
+    private static final class NativeWrapperImpl implements NativeWrapper,
+            IHwBinder.DeathRecipient, getPhysicalAddressCallback {
+        private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
         @Nullable private HdmiCecCallback mCallback;
 
+        private final Object mLock = new Object();
+        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
         @Override
         public String nativeInit() {
             return (connectToHal() ? mHdmiCec.toString() : null);
@@ -765,8 +961,8 @@
                 } catch (RemoteException e) {
                     HdmiLogger.error("Couldn't link to death : ", e);
                 }
-            } catch (RemoteException e) {
-                HdmiLogger.error("Couldn't get tv.cec service : ", e);
+            } catch (RemoteException | NoSuchElementException e) {
+                HdmiLogger.error("Couldn't connect to cec@1.0", e);
                 return false;
             }
             return true;
@@ -776,7 +972,7 @@
         public void setCallback(@NonNull HdmiCecCallback callback) {
             mCallback = callback;
             try {
-                mHdmiCec.setCallback(callback);
+                mHdmiCec.setCallback(new HdmiCecCallback10(callback));
             } catch (RemoteException e) {
                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
             }
@@ -931,20 +1127,69 @@
         }
     }
 
-    final class HdmiCecCallback extends IHdmiCecCallback.Stub {
+    final class HdmiCecCallback {
+        public void onCecMessage(int initiator, int destination, byte[] body) {
+            runOnServiceThread(
+                    () -> handleIncomingCecCommand(initiator, destination, body));
+        }
+
+        public void onHotplugEvent(int portId, boolean connected) {
+            runOnServiceThread(() -> handleHotplug(portId, connected));
+        }
+    }
+
+    private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub {
+        private final HdmiCecCallback mHdmiCecCallback;
+
+        HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
+            mHdmiCecCallback = hdmiCecCallback;
+        }
+
         @Override
         public void onCecMessage(CecMessage message) throws RemoteException {
             byte[] body = new byte[message.body.size()];
             for (int i = 0; i < message.body.size(); i++) {
                 body[i] = message.body.get(i);
             }
-            runOnServiceThread(
-                    () -> handleIncomingCecCommand(message.initiator, message.destination, body));
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
         }
 
         @Override
         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
-            runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
+            mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
+        }
+    }
+
+    private static final class HdmiCecCallback11
+            extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
+        private final HdmiCecCallback mHdmiCecCallback;
+
+        HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
+            mHdmiCecCallback = hdmiCecCallback;
+        }
+
+        @Override
+        public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
+                throws RemoteException {
+            byte[] body = new byte[message.body.size()];
+            for (int i = 0; i < message.body.size(); i++) {
+                body[i] = message.body.get(i);
+            }
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+        }
+
+        @Override
+        public void onCecMessage(CecMessage message) throws RemoteException {
+            byte[] body = new byte[message.body.size()];
+            for (int i = 0; i < message.body.size(); i++) {
+                body[i] = message.body.get(i);
+            }
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+        }
+
+        @Override
+        public void onHotplugEvent(HotplugEvent event) throws RemoteException {
+            mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 04acd51..2ed8481 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -139,8 +139,12 @@
     @ServiceThreadOnly
     void toggleAndFollowTvPower() {
         assertRunOnServiceThread();
-        // Wake up Android framework to take over CEC control from the microprocessor.
-        mService.wakeUp();
+        if (mService.getPowerManager().isInteractive()) {
+            mService.pauseActiveMediaSessions();
+        } else {
+            // Wake up Android framework to take over CEC control from the microprocessor.
+            mService.wakeUp();
+        }
         mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int status) {
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c4c0f68..bd577f3 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -371,6 +371,9 @@
             int durationMs) {
         Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
                 + durationMs + "ms");
+        if (mServiceNameResolver == null) {
+            return;
+        }
         enforceCallingPermissionForManagement();
 
         Objects.requireNonNull(componentName);
@@ -404,6 +407,9 @@
         enforceCallingPermissionForManagement();
 
         synchronized (mLock) {
+            if (mServiceNameResolver == null) {
+                return false;
+            }
             final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
             if (!changed) {
                 if (verbose) {
@@ -434,6 +440,10 @@
     public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
         enforceCallingPermissionForManagement();
 
+        if (mServiceNameResolver == null) {
+            return false;
+        }
+
         synchronized (mLock) {
             return mServiceNameResolver.isDefaultServiceEnabled(userId);
         }
@@ -958,6 +968,10 @@
             public void onPackageModified(String packageName) {
                 if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
 
+                if (mServiceNameResolver == null) {
+                    return;
+                }
+
                 final int userId = getChangingUserId();
                 final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
                 if (serviceName == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 5eec315..6deb19a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1424,6 +1424,8 @@
             mAppForegroundHelper.onSystemReady();
             mLocationPowerSaveModeHelper.onSystemReady();
             mScreenInteractiveHelper.onSystemReady();
+            mDeviceStationaryHelper.onSystemReady();
+            mDeviceIdleHelper.onSystemReady();
 
             if (mEmergencyCallHelper != null) {
                 mEmergencyCallHelper.onSystemReady();
diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
new file mode 100644
index 0000000..85de4bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.contexthub;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+/**
+ * A class that manages a timer used to keep track of how much time is left before a
+ * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from
+ * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from
+ * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper.
+ *
+ * @hide
+ */
+public class AuthStateDenialTimer {
+    private static final long TIMEOUT_MS = SECONDS.toMillis(60);
+
+    private final ContextHubClientBroker mClient;
+    private final long mNanoAppId;
+    private final Handler mHandler;
+
+    /**
+     * Indicates when the timer should stop in the future.
+     */
+    private long mStopTimeInFuture;
+
+    /**
+     * boolean representing if the timer was cancelled
+     */
+    private boolean mCancelled = false;
+
+    public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) {
+        mClient = client;
+        mNanoAppId = nanoAppId;
+        mHandler = new CountDownHandler(looper);
+    }
+
+    /**
+     * Cancel the countdown.
+     */
+    public synchronized void cancel() {
+        mCancelled = true;
+        mHandler.removeMessages(MSG);
+    }
+
+    /**
+     * Start the countdown.
+     */
+    public synchronized void start() {
+        mCancelled = false;
+        mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS;
+        mHandler.sendMessage(mHandler.obtainMessage(MSG));
+    }
+
+    /**
+     * Called when the timer has expired.
+     */
+    public void onFinish() {
+        mClient.handleAuthStateTimerExpiry(mNanoAppId);
+    }
+
+    // Message type used to trigger the timer.
+    private static final int MSG = 1;
+
+    private class CountDownHandler extends Handler {
+
+        CountDownHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (AuthStateDenialTimer.this) {
+                if (mCancelled) {
+                    return;
+                }
+                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+                if (millisLeft <= 0) {
+                    onFinish();
+                } else {
+                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index d3c853d..6249a06 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -17,9 +17,13 @@
 package com.android.server.location.contexthub;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
 
 import android.Manifest;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -30,19 +34,21 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.server.location.ClientBrokerProto;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -52,14 +58,53 @@
  * notification callbacks. This class implements the IContextHubClient object, and the implemented
  * APIs must be thread-safe.
  *
+ * Additionally, this class is responsible for enforcing permissions usage and attribution are
+ * handled appropriately for a given client. In general, this works as follows:
+ *
+ * Client sending a message to a nanoapp:
+ * 1) When initially sending a message to nanoapps, clients are by default in a grace period state
+ *    which allows them to always send their first message to nanoapps. This is done to allow
+ *    clients (especially callback clients) to reset their conection to the nanoapp if they are
+ *    killed / restarted (e.g. following a permission revocation).
+ * 2) After the initial message is sent, a check of permissions state is performed. If the
+ *    client doesn't have permissions to communicate, it is placed into the denied grace period
+ *    state and notified so that it can clean up its communication before it is completely denied
+ *    access.
+ * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if
+ *    the client is denied authorization
+ *
+ * Client receiving a message from a nanoapp:
+ * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously.
+ *    If there has been no message between the two before, the auth state is assumed granted.
+ * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes
+ *    all permissions required to consume the message being sent. If both of those checks pass, then
+ *    the message is delivered. Otherwise, it's dropped.
+ *
+ * Client losing or gaining permissions (callback client):
+ * 1) Clients are killed when they lose permissions. This will cause callback clients to completely
+ *    disconnect from the service. When they are restarted, their initial message will still be
+ *    be allowed through and their permissions will be rechecked at that time.
+ * 2) If they gain a permission, the broker will notify them if that permission allows them to
+ *    communicate with a nanoapp again.
+ *
+ * Client losing or gaining permissions (PendingIntent client):
+ * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the
+ *    service when they are killed. In their case, they will receive notifications of the broker
+ *    that they have been denied required permissions or gain required permissions.
+ *
  * TODO: Consider refactoring this class via inheritance
  *
  * @hide
  */
 public class ContextHubClientBroker extends IContextHubClient.Stub
-        implements IBinder.DeathRecipient {
+        implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
     private static final String TAG = "ContextHubClientBroker";
 
+    /**
+     * Message used by noteOp when this client receives a message from a nanoapp.
+     */
+    private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+
     /*
      * The context of the service.
      */
@@ -119,6 +164,26 @@
      */
     private final String mPackage;
 
+    /**
+     * The PID associated with this client.
+     */
+    private final int mPid;
+
+    /**
+     * The UID associated with this client.
+     */
+    private final int mUid;
+
+    /**
+     * Manager used for noting permissions usage of this broker.
+     */
+    private final AppOpsManager mAppOpsManager;
+
+    /**
+     * Manager used to queue transactions to the context hub.
+     */
+    private final ContextHubTransactionManager mTransactionManager;
+
     /*
      * True if a PendingIntent has been cancelled.
      */
@@ -130,11 +195,44 @@
     private final boolean mHasAccessContextHubPermission;
 
     /*
-     * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging
-     * channel with, i.e. has sent or received messages from this particular nanoapp.
+     * Map containing all nanoapps this client has a messaging channel with and whether it is
+     * allowed to communicate over that channel. A channel is defined to have been opened if the
+     * client has sent or received messages from the particular nanoapp.
      */
-    private final Set<Long> mMessageChannelNanoappIdSet =
-            Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
+    private final Map<Long, Integer> mMessageChannelNanoappIdMap =
+            new ConcurrentHashMap<Long, Integer>();
+
+    /**
+     * Map containing all nanoapps that have active auth state denial timers.
+     */
+    private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
+            new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+
+    /**
+     * Callback used to obtain the latest set of nanoapp permissions and verify this client has
+     * each nanoapps permissions granted.
+     */
+    private final IContextHubTransactionCallback mQueryPermsCallback =
+            new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
+                    Log.e(TAG, "Permissions query failed, but still received nanoapp state");
+                } else if (nanoAppStateList != null) {
+                    for (NanoAppState state : nanoAppStateList) {
+                        if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
+                            List<String> permissions = state.getNanoAppPermissions();
+                            updateNanoAppAuthState(state.getNanoAppId(),
+                                    hasPermissions(permissions), false /* gracePeriodExpired */);
+                        }
+                    }
+                }
+            }
+        };
 
     /*
      * Helper class to manage registered PendingIntent requests from the client.
@@ -182,40 +280,57 @@
         }
     }
 
-    /* package */ ContextHubClientBroker(
-            Context context, IContextHubWrapper contextHubProxy,
+    private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
-            short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
+            long nanoAppId, String packageName) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
         mAttachedContextHubInfo = contextHubInfo;
         mHostEndPointId = hostEndPointId;
         mCallbackInterface = callback;
-        mPendingIntentRequest = new PendingIntentRequest();
-        mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+        if (pendingIntent == null) {
+            mPendingIntentRequest = new PendingIntentRequest();
+        } else {
+            mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
+        }
+        mPackage = packageName;
         mAttributionTag = attributionTag;
+        mTransactionManager = transactionManager;
 
+        mPid = Binder.getCallingPid();
+        mUid = Binder.getCallingUid();
         mHasAccessContextHubPermission = context.checkCallingPermission(
                 Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+        startMonitoringOpChanges();
+    }
+
+    /* package */ ContextHubClientBroker(
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, String packageName) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback,
+                attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */,
+                packageName);
     }
 
     /* package */ ContextHubClientBroker(
             Context context, IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
             short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
-            String attributionTag) {
-        mContext = context;
-        mContextHubProxy = contextHubProxy;
-        mClientManager = clientManager;
-        mAttachedContextHubInfo = contextHubInfo;
-        mHostEndPointId = hostEndPointId;
-        mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
-        mPackage = pendingIntent.getCreatorPackage();
-        mAttributionTag = attributionTag;
+            String attributionTag, ContextHubTransactionManager transactionManager) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId,
+                null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId,
+                pendingIntent.getCreatorPackage());
+    }
 
-        mHasAccessContextHubPermission = context.checkCallingPermission(
-                Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+    private void startMonitoringOpChanges() {
+        mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this);
     }
 
     /**
@@ -229,18 +344,44 @@
     public int sendMessageToNanoApp(NanoAppMessage message) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
+        int authState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            // Default to the granted auth state. The true auth state will be checked async if it's
+            // not denied.
+            authState = mMessageChannelNanoappIdMap.getOrDefault(
+                    message.getNanoAppId(), AUTHORIZATION_GRANTED);
+            if (authState == AUTHORIZATION_DENIED) {
+                return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+            }
+        }
+
         int result;
         if (isRegistered()) {
-            mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+            // Even though the auth state is currently not denied, query the nanoapp permissions
+            // async and verify that the host app currently holds all the requisite permissions.
+            // This can't be done synchronously due to the async query that needs to be performed to
+            // obtain the nanoapp permissions.
+            boolean initialNanoappMessage = false;
+            synchronized (mMessageChannelNanoappIdMap) {
+                if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
+                    mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
+                    initialNanoappMessage = true;
+                }
+            }
+
+            if (initialNanoappMessage) {
+                // Only check permissions the first time a nanoapp is queried since nanoapp
+                // permissions don't currently change at runtime. If the host permission changes
+                // later, that'll be checked by onOpChanged.
+                checkNanoappPermsAsync();
+            }
+
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
 
             int contextHubId = mAttachedContextHubInfo.getId();
             try {
-                // TODO(166846988): Fill in host permissions before sending a message.
-                result = mContextHubProxy.sendMessageToHub(
-                        contextHubId, messageToNanoApp,
-                        new ArrayList<String>() /* hostPermissions */);
+                result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
                         + contextHubId + ")", e);
@@ -275,6 +416,19 @@
         onClientExit();
     }
 
+    @Override
+    public void onOpChanged(String op, String packageName) {
+        if (packageName.equals(mPackage)) {
+            if (!mMessageChannelNanoappIdMap.isEmpty()) {
+                checkNanoappPermsAsync();
+            }
+        }
+    }
+
+    /* package */ String getPackageName() {
+        return mPackage;
+    }
+
     /**
      * Used to override the attribution tag with a newer value if a PendingIntent broker is
      * retrieved.
@@ -308,15 +462,39 @@
      * Sends a message to the client associated with this object.
      *
      * @param message the message that came from a nanoapp
+     * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
+     * message
+     * @param messagePermissions permissions required to consume the message being delivered. These
+     * permissions are what will be attributed to the client through noteOp.
      */
-    /* package */ void sendMessageToClient(NanoAppMessage message) {
-        mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+    /* package */ void sendMessageToClient(
+            NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        long nanoAppId = message.getNanoAppId();
+
+        int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+
+        // If in the grace period, the host may not receive any messages containing permissions
+        // covered data.
+        if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " in grace period and napp msg has permissions");
+            return;
+        }
+
+        if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+                || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " doesn't have permission");
+            return;
+        }
+
         invokeCallback(callback -> callback.onMessageFromNanoApp(message));
 
         Supplier<Intent> supplier =
-                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
                         .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
-        sendPendingIntent(supplier, message.getNanoAppId());
+        sendPendingIntent(supplier, nanoAppId);
     }
 
     /**
@@ -325,6 +503,10 @@
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
     /* package */ void onNanoAppLoaded(long nanoAppId) {
+        // Check the latest state to see if the loaded nanoapp's permissions changed such that the
+        // host app can communicate with it again.
+        checkNanoappPermsAsync();
+
         invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
         sendPendingIntent(
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId);
@@ -392,6 +574,43 @@
     }
 
     /**
+     * Checks that this client has all of the provided permissions.
+     *
+     * @param permissions list of permissions to check
+     * @return true if the client has all of the permissions granted
+     */
+    /* package */ boolean hasPermissions(List<String> permissions) {
+        for (String permission : permissions) {
+            if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attributes the provided permissions to the package of this client.
+     *
+     * @param permissions list of permissions covering data the client is about to receive
+     * @param noteMessage message that should be noted alongside permissions attribution to
+     * facilitate debugging
+     * @return true if client has ability to use all of the provided permissions
+     */
+    /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+        for (String permission : permissions) {
+            int opCode = mAppOpsManager.permissionToOpCode(permission);
+            if (opCode != AppOpsManager.OP_NONE) {
+                if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
      * @return true if the client is a PendingIntent client that has been cancelled.
      */
     /* package */ boolean isPendingIntentCancelled() {
@@ -399,6 +618,101 @@
     }
 
     /**
+     * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
+     * period.
+     */
+    /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+        AuthStateDenialTimer timer;
+        synchronized (mMessageChannelNanoappIdMap) {
+            timer = mNappToAuthTimerMap.remove(nanoAppId);
+        }
+
+        if (timer != null) {
+            updateNanoAppAuthState(
+                    nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+        }
+    }
+
+    /**
+     * Verifies this client has the permissions to communicate with all of the nanoapps it has
+     * communicated with in the past.
+     */
+    private void checkNanoappPermsAsync() {
+        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+        mTransactionManager.addTransaction(transaction);
+    }
+
+    /**
+     * Updates the latest authentication state for this client to be able to communicate with the
+     * given nanoapp.
+     */
+    private void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
+        updateNanoAppAuthState(
+                nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */);
+    }
+
+    /* package */ void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired,
+            boolean forceDenied) {
+        int curAuthState;
+        int newAuthState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
+                    nanoAppId, AUTHORIZATION_GRANTED);
+            newAuthState = curAuthState;
+            // The below logic ensures that only the following transitions are possible:
+            // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
+            // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires
+            // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again
+            // any state -> DENIED if "forceDenied" is true
+            if (forceDenied) {
+                newAuthState = AUTHORIZATION_DENIED;
+            } else if (gracePeriodExpired) {
+                if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                    newAuthState = AUTHORIZATION_DENIED;
+                }
+            } else {
+                if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) {
+                    newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD;
+                } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) {
+                    newAuthState = AUTHORIZATION_GRANTED;
+                }
+            }
+
+            if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId);
+                if (timer != null) {
+                    timer.cancel();
+                }
+            } else if (curAuthState == AUTHORIZATION_GRANTED) {
+                AuthStateDenialTimer timer =
+                        new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper());
+                mNappToAuthTimerMap.put(nanoAppId, timer);
+                timer.start();
+            }
+
+            if (curAuthState != newAuthState) {
+                mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState);
+            }
+        }
+        if (curAuthState != newAuthState) {
+            // Don't send the callback in the synchronized block or it could end up in a deadlock.
+            sendAuthStateCallback(nanoAppId, newAuthState);
+        }
+    }
+
+    private void sendAuthStateCallback(long nanoAppId, int authState) {
+        invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState));
+
+        Supplier<Intent> supplier =
+                () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId)
+                        .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState);
+        sendPendingIntent(supplier, nanoAppId);
+    }
+
+    /**
      * Helper function to invoke a specified client callback, if the connection is open.
      *
      * @param consumer the consumer specifying the callback to invoke
@@ -507,6 +821,20 @@
             mClientManager.unregisterClient(mHostEndPointId);
             mRegistered = false;
         }
+        mAppOpsManager.stopWatchingMode(this);
+    }
+
+    private String authStateToString(@ContextHubManager.AuthorizationState int state) {
+        switch (state) {
+            case AUTHORIZATION_DENIED:
+                return "DENIED";
+            case AUTHORIZATION_DENIED_GRACE_PERIOD:
+                return "DENIED_GRACE_PERIOD";
+            case AUTHORIZATION_GRANTED:
+                return "GRANTED";
+            default:
+                return "UNKNOWN";
+        }
     }
 
     /**
@@ -545,11 +873,14 @@
         } else {
             out += "package: " + mPackage;
         }
-        if (mMessageChannelNanoappIdSet.size() > 0) {
+        if (mMessageChannelNanoappIdMap.size() > 0) {
             out += " messageChannelNanoappSet: (";
-            Iterator<Long> it = mMessageChannelNanoappIdSet.iterator();
+            Iterator<Map.Entry<Long, Integer>> it =
+                    mMessageChannelNanoappIdMap.entrySet().iterator();
             while (it.hasNext()) {
-                out += "0x" + Long.toHexString(it.next());
+                Map.Entry<Long, Integer> entry = it.next();
+                out += "0x" + Long.toHexString(entry.getKey()) + " auth state: "
+                        + authStateToString(entry.getValue());
                 if (it.hasNext()) {
                     out += ",";
                 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 0351edb..e3522f6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -36,6 +36,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -136,8 +137,7 @@
         }
     }
 
-    /* package */ ContextHubClientManager(
-            Context context, IContextHubWrapper contextHubProxy) {
+    /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
     }
@@ -155,13 +155,15 @@
      */
     /* package */ IContextHubClient registerClient(
             ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
-            String attributionTag) {
+            String attributionTag, ContextHubTransactionManager transactionManager,
+            String packageName) {
         ContextHubClientBroker broker;
         synchronized (this) {
             short hostEndPointId = getHostEndPointId();
             broker = new ContextHubClientBroker(
                     mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                    hostEndPointId, clientCallback, attributionTag);
+                    hostEndPointId, clientCallback, attributionTag, transactionManager,
+                    packageName);
             mHostEndPointIdToClientMap.put(hostEndPointId, broker);
             mRegistrationRecordDeque.add(
                     new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -194,7 +196,7 @@
      */
     /* package */ IContextHubClient registerClient(
             ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
-            String attributionTag) {
+            String attributionTag, ContextHubTransactionManager transactionManager) {
         ContextHubClientBroker broker;
         String registerString = "Regenerated";
         synchronized (this) {
@@ -204,7 +206,8 @@
                 short hostEndPointId = getHostEndPointId();
                 broker = new ContextHubClientBroker(
                         mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                        hostEndPointId, pendingIntent, nanoAppId, attributionTag);
+                        hostEndPointId, pendingIntent, nanoAppId, attributionTag,
+                        transactionManager);
                 mHostEndPointIdToClientMap.put(hostEndPointId, broker);
                 registerString = "Registered";
                 mRegistrationRecordDeque.add(
@@ -224,9 +227,14 @@
      * Handles a message sent from a nanoapp.
      *
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
-     * @param message      the message send by a nanoapp
+     * @param message the message send by a nanoapp
+     * @param nanoappPermissions the set of permissions the nanoapp holds
+     * @param messagePermissions the set of permissions that should be used for attributing
+     * permissions when this message is consumed by a client
      */
-    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+    /* package */ void onMessageFromNanoApp(
+            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
         NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
 
         if (DEBUG_LOG_ENABLED) {
@@ -234,11 +242,19 @@
         }
 
         if (clientMessage.isBroadcastMessage()) {
-            broadcastMessage(contextHubId, clientMessage);
+            // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
+            // requirements.
+            if (!messagePermissions.isEmpty()) {
+                Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName);
+            }
+
+            broadcastMessage(
+                    contextHubId, clientMessage, nanoappPermissions, messagePermissions);
         } else {
             ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
             if (proxy != null) {
-                proxy.sendMessageToClient(clientMessage);
+                proxy.sendMessageToClient(
+                        clientMessage, nanoappPermissions, messagePermissions);
             } else {
                 Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
                         + message.hostEndPoint + ")");
@@ -303,6 +319,21 @@
     }
 
     /**
+     * Runs a command for each client that is attached to a hub with the given ID.
+     *
+     * @param contextHubId the ID of the hub
+     * @param callback     the command to invoke for the client
+     */
+    /* package */ void forEachClientOfHub(
+            int contextHubId, Consumer<ContextHubClientBroker> callback) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            if (broker.getAttachedContextHubId() == contextHubId) {
+                callback.accept(broker);
+            }
+        }
+    }
+
+    /**
      * Returns an available host endpoint ID.
      *
      * @returns an available host endpoint ID
@@ -333,22 +364,12 @@
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
      * @param message      the message send by a nanoapp
      */
-    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
-        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
-    }
-
-    /**
-     * Runs a command for each client that is attached to a hub with the given ID.
-     *
-     * @param contextHubId the ID of the hub
-     * @param callback     the command to invoke for the client
-     */
-    private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
-        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
-            if (broker.getAttachedContextHubId() == contextHubId) {
-                callback.accept(broker);
-            }
-        }
+    private void broadcastMessage(
+            int contextHubId, NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        forEachClientOfHub(contextHubId,
+                client -> client.sendMessageToClient(
+                        message, nanoappPermissions, messagePermissions));
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2eafe6a..0737db7 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -50,6 +50,8 @@
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -64,6 +66,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -98,6 +101,7 @@
     private final Context mContext;
 
     private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+    private final List<String> mSupportedContextHubPerms;
     private final List<ContextHubInfo> mContextHubInfoList;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
@@ -140,7 +144,7 @@
         public void handleClientMsg(ContextHubMsg message) {
             handleClientMessageCallback(mContextHubId, message,
                     Collections.emptyList() /* nanoappPermissions */,
-                    Collections.emptyList() /* messageContentPermissions */);
+                    Collections.emptyList() /* messagePermissions */);
         }
 
         @Override
@@ -167,9 +171,9 @@
 
         @Override
         public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
-                ArrayList<String> messageContentPermissions) {
+                ArrayList<String> messagePermissions) {
             handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
-                    messageContentPermissions);
+                    messagePermissions);
         }
 
         @Override
@@ -187,14 +191,11 @@
             mClientManager = null;
             mDefaultClientMap = Collections.emptyMap();
             mContextHubIdToInfoMap = Collections.emptyMap();
+            mSupportedContextHubPerms = Collections.emptyList();
             mContextHubInfoList = Collections.emptyList();
             return;
         }
 
-        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
-        mTransactionManager = new ContextHubTransactionManager(
-                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
-
         Pair<List<ContextHub>, List<String>> hubInfo;
         try {
             hubInfo = mContextHubWrapper.getHubs();
@@ -202,16 +203,21 @@
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
         }
+
         mContextHubIdToInfoMap = Collections.unmodifiableMap(
                 ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
+        mSupportedContextHubPerms = hubInfo.second;
         mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
+        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
+        mTransactionManager = new ContextHubTransactionManager(
+                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
         for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
             ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
             IContextHubClient client = mClientManager.registerClient(
                     contextHubInfo, createDefaultClientCallback(contextHubId),
-                    null /* attributionTag */);
+                    null /* attributionTag */, mTransactionManager, mContext.getPackageName());
             defaultClientMap.put(contextHubId, client);
 
             try {
@@ -362,6 +368,12 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new ContextHubShellCommand(mContext, this).exec(this, in, out, err, args, callback, result);
+    }
+
+    @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
@@ -611,13 +623,15 @@
     /**
      * Handles a unicast or broadcast message from a nanoapp.
      *
-     * @param contextHubId the ID of the hub the message came from
-     * @param message      the message contents
+     * @param contextHubId   the ID of the hub the message came from
+     * @param message        the message contents
+     * @param reqPermissions the permissions required to consume this message
      */
     private void handleClientMessageCallback(
             int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
-            List<String> messageContentPermissions) {
-        mClientManager.onMessageFromNanoApp(contextHubId, message);
+            List<String> messagePermissions) {
+        mClientManager.onMessageFromNanoApp(
+                contextHubId, message, nanoappPermissions, messagePermissions);
     }
 
     /**
@@ -723,6 +737,7 @@
      * @param contextHubId   the ID of the hub this client is attached to
      * @param clientCallback the client interface to register with the service
      * @param attributionTag an optional attribution tag within the given package
+     * @param packageName    the name of the package creating this client
      * @return the generated client interface, null if registration was unsuccessful
      * @throws IllegalArgumentException if contextHubId is not a valid ID
      * @throws IllegalStateException    if max number of clients have already registered
@@ -731,7 +746,7 @@
     @Override
     public IContextHubClient createClient(
             int contextHubId, IContextHubClientCallback clientCallback,
-            @Nullable String attributionTag) throws RemoteException {
+            @Nullable String attributionTag, String packageName) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -741,7 +756,8 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
+        return mClientManager.registerClient(
+                contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
     }
 
     /**
@@ -766,7 +782,7 @@
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
         return mClientManager.registerClient(
-                contextHubInfo, pendingIntent, nanoAppId, attributionTag);
+                contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
     }
 
     /**
@@ -907,6 +923,8 @@
         for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
             pw.println(hubInfo);
         }
+        pw.println("Supported permissions: "
+                + Arrays.toString(mSupportedContextHubPerms.toArray()));
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
@@ -923,6 +941,16 @@
         // dump eventLog
     }
 
+    /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) {
+        mClientManager.forEachClientOfHub(contextHubId, client -> {
+            if (client.getPackageName().equals(packageName)) {
+                client.updateNanoAppAuthState(
+                        nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */,
+                        true /* forceDenied */);
+            }
+        });
+    }
+
     private void dump(ProtoOutputStream proto) {
         mContextHubIdToInfoMap.values().forEach(hubInfo -> {
             long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
new file mode 100644
index 0000000..5ec85e6
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.contexthub;
+
+import android.content.Context;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for ContextHubService.
+ *
+ * Use with {@code adb shell cmd contexthub ...}.
+ *
+ * @hide
+ */
+public class ContextHubShellCommand extends ShellCommand {
+
+    // Internal service impl -- must perform security checks before touching.
+    private final ContextHubService mInternal;
+
+    public ContextHubShellCommand(Context context, ContextHubService service) {
+        mInternal = service;
+
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_CONTEXT_HUB, "ContextHubShellCommand");
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if ("deny".equals(cmd)) {
+            return runDisableAuth();
+        }
+
+        return handleDefaultCommands(cmd);
+    }
+
+    private int runDisableAuth() {
+        int contextHubId = Integer.decode(getNextArgRequired());
+        String packageName = getNextArgRequired();
+        long nanoAppId = Long.decode(getNextArgRequired());
+
+        mInternal.denyClientAuthState(contextHubId, packageName, nanoAppId);
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("ContextHub commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  deny [contextHubId] [packageName] [nanoAppId]");
+        pw.println("    Immediately transitions the package's authentication state to denied so");
+        pw.println("    can no longer communciate with the nanoapp.");
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c1d63dd..3a5c220 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -106,9 +106,8 @@
     /**
      * Calls the appropriate sendMessageToHub function depending on the HAL version.
      */
-    public abstract int sendMessageToHub(
-            int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-            ArrayList<String> hostPermissions) throws RemoteException;
+    public abstract int sendMessageToHub(int hubId,
+            android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
 
     /**
      * @return A valid instance of Contexthub HAL 1.0.
@@ -181,9 +180,8 @@
             mHub.registerCallback(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             return mHub.sendMessageToHub(hubId, message);
         }
 
@@ -236,9 +234,8 @@
             mHub.registerCallback(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             return mHub.sendMessageToHub(hubId, message);
         }
 
@@ -307,13 +304,11 @@
             mHub.registerCallback_1_2(hubId, callback);
         }
 
-        public int sendMessageToHub(
-                int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
-                ArrayList<String> hostPermissions) throws RemoteException {
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
             android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
                     new android.hardware.contexthub.V1_2.ContextHubMsg();
             newMessage.msg_1_0 = message;
-            newMessage.permissions = hostPermissions;
             return mHub.sendMessageToHub_1_2(hubId, newMessage);
         }
 
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
index 6a89079..736b654 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -21,50 +21,80 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Binder;
 import android.os.PowerManager;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 
+import java.util.Objects;
+
 /**
  * Provides accessors and listeners for device stationary state.
  */
 public class SystemDeviceIdleHelper extends DeviceIdleHelper {
 
     private final Context mContext;
-    private final PowerManager mPowerManager;
 
+    private PowerManager mPowerManager;
+
+    private boolean mSystemReady;
+    private boolean mRegistrationRequired;
     private @Nullable BroadcastReceiver mReceiver;
 
     public SystemDeviceIdleHelper(Context context) {
         mContext = context;
-        mPowerManager = context.getSystemService(PowerManager.class);
+    }
+
+    public synchronized void onSystemReady() {
+        mSystemReady = true;
+        mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+        onRegistrationStateChanged();
     }
 
     @Override
-    protected void registerInternal() {
-        if (mReceiver == null) {
-            mReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    notifyDeviceIdleChanged();
-                }
-            };
+    protected synchronized void registerInternal() {
+        mRegistrationRequired = true;
+        onRegistrationStateChanged();
+    }
 
-            mContext.registerReceiver(mReceiver,
-                    new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
-                    FgThread.getHandler());
+    @Override
+    protected synchronized void unregisterInternal() {
+        mRegistrationRequired = false;
+        onRegistrationStateChanged();
+    }
+
+    private void onRegistrationStateChanged() {
+        if (!mSystemReady) {
+            return;
         }
-    }
 
-    @Override
-    protected void unregisterInternal() {
-        if (mReceiver != null) {
-            mContext.unregisterReceiver(mReceiver);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mRegistrationRequired && mReceiver == null) {
+                BroadcastReceiver receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        notifyDeviceIdleChanged();
+                    }
+                };
+                mContext.registerReceiver(receiver,
+                        new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                        FgThread.getHandler());
+                mReceiver = receiver;
+            } else if (!mRegistrationRequired && mReceiver != null) {
+                BroadcastReceiver receiver = mReceiver;
+                mReceiver = null;
+                mContext.unregisterReceiver(receiver);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
     @Override
     public boolean isDeviceIdle() {
+        Preconditions.checkState(mPowerManager != null);
         return mPowerManager.isDeviceIdleMode();
     }
 }
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
index 6f0e681..9874ecf 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.location.injector;
 
+import android.os.Binder;
+
+import com.android.internal.util.Preconditions;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 
@@ -26,19 +29,35 @@
  */
 public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
 
-    private final DeviceIdleInternal mDeviceIdle;
+    private DeviceIdleInternal mDeviceIdle;
 
-    public SystemDeviceStationaryHelper() {
+    public SystemDeviceStationaryHelper() {}
+
+    public void onSystemReady() {
         mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
     }
 
     @Override
     public void addListener(DeviceIdleInternal.StationaryListener listener) {
-        mDeviceIdle.registerStationaryListener(listener);
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.registerStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
     public void removeListener(DeviceIdleInternal.StationaryListener listener) {
-        mDeviceIdle.unregisterStationaryListener(listener);
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.unregisterStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
index 0d7bcb0..03ade5f 100644
--- a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
@@ -68,7 +68,7 @@
         mReady = true;
     }
 
-    private void onScreenInteractiveChanged(boolean interactive) {
+    void onScreenInteractiveChanged(boolean interactive) {
         if (interactive == mIsInteractive) {
             return;
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 905733c..dbd1211 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1341,7 +1341,9 @@
         }
 
         public void forgetAllPackageInfos(final int userId) {
-            for (int i = 0, n = mCache.size(); i < n; i++) {
+            // Iterate in reverse order since removing the package in all users will remove the
+            // package from the cache.
+            for (int i = mCache.size() - 1; i >= 0; i--) {
                 removePackageUser(mCache.valueAt(i), userId);
             }
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index fb183f5..799ab46 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -244,7 +244,11 @@
     @NonNull
     Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        // Always update the overlays of newly added packages.
+        updatedTargets.add(new PackageAndUser(pkgName, userId));
+        updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
+        return updatedTargets;
     }
 
     @NonNull
@@ -282,7 +286,7 @@
     private Set<PackageAndUser> removeOverlaysForUser(
             @NonNull final Predicate<OverlayInfo> condition, final int userId) {
         final List<OverlayInfo> overlays = mSettings.removeIf(
-                io -> userId == io.userId && condition.test(io) );
+                io -> userId == io.userId && condition.test(io));
         Set<PackageAndUser> targets = Collections.emptySet();
         for (int i = 0, n = overlays.size(); i < n; i++) {
             final OverlayInfo info = overlays.get(i);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06e84d..d3a56c6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,6 +22,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
 import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1170,6 +1171,8 @@
                 ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
             } else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
                 ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
+            } else if (cacheFlags == FLAG_CACHE_PEOPLE_TILE_SHORTCUTS) {
+                ret = ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
             }
             Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 753f22d..24c27be 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1522,6 +1522,14 @@
     }
 
     @Override
+    public boolean isUserForeground() {
+        int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+        int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
+        // TODO(b/179163496): should return true for profile users of the current user as well
+        return currentUser == callingUserId;
+    }
+
+    @Override
     public String getUserName() {
         if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
             throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 6d9cb75..2a95416 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -47,7 +47,8 @@
     private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR;
 
     private final ReentrantLock mLock = new ReentrantLock();
-    private File mDataStorageDir;
+    private final File mDataStorageDir;
+    private final String mDataStorageFilename;
     private final FileRotator mFileRotator;
 
     private static class DataElement {
@@ -168,6 +169,7 @@
     public PowerStatsDataStorage(Context context, File dataStoragePath,
             String dataStorageFilename) {
         mDataStorageDir = dataStoragePath;
+        mDataStorageFilename = dataStorageFilename;
 
         if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) {
             Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath());
@@ -177,33 +179,35 @@
             // filename, so any files that don't match the current version number can be deleted.
             File[] files = mDataStorageDir.listFiles();
             for (int i = 0; i < files.length; i++) {
-                // Meter and model files are stored in the same directory.
+                // Meter, model, and residency files are stored in the same directory.
                 //
                 // The format of filenames on disk is:
                 //    log.powerstats.meter.version.timestamp
                 //    log.powerstats.model.version.timestamp
+                //    log.powerstats.residency.version.timestamp
                 //
                 // The format of dataStorageFilenames is:
                 //    log.powerstats.meter.version
                 //    log.powerstats.model.version
+                //    log.powerstats.residency.version
                 //
-                // A PowerStatsDataStorage object is created for meter and model data.  Strip off
-                // the version and check that the current file we're checking starts with the stem
-                // (log.powerstats.meter or log.powerstats.model). If the stem matches and the
-                // version number is different, delete the old file.
-                int versionDot = dataStorageFilename.lastIndexOf('.');
-                String beforeVersionDot = dataStorageFilename.substring(0, versionDot);
+                // A PowerStatsDataStorage object is created for meter, model, and residency data.
+                // Strip off the version and check that the current file we're checking starts with
+                // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency).
+                // If the stem matches and the version number is different, delete the old file.
+                int versionDot = mDataStorageFilename.lastIndexOf('.');
+                String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
                 // Check that the stems match.
                 if (files[i].getName().startsWith(beforeVersionDot)) {
                     // Check that the version number matches.  If not, delete the old file.
-                    if (!files[i].getName().startsWith(dataStorageFilename)) {
+                    if (!files[i].getName().startsWith(mDataStorageFilename)) {
                         files[i].delete();
                     }
                 }
             }
 
             mFileRotator = new FileRotator(mDataStorageDir,
-                                           dataStorageFilename,
+                                           mDataStorageFilename,
                                            ROTATE_AGE_MILLIS,
                                            DELETE_AGE_MILLIS);
         }
@@ -242,4 +246,19 @@
     public void read(DataElementReadCallback callback) throws IOException {
         mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE);
     }
+
+    /**
+     * Deletes all stored log data.
+     */
+    public void deleteLogs() {
+        File[] files = mDataStorageDir.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            int versionDot = mDataStorageFilename.lastIndexOf('.');
+            String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+            // Check that the stems before the version match.
+            if (files[i].getName().startsWith(beforeVersionDot)) {
+                files[i].delete();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 37fc5a0..c4f29ea 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -41,14 +42,17 @@
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 /**
- * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
- * Messages are sent to its message handler to request that energy data be logged, at which time it
- * queries the PowerStats HAL and logs the data to on-device storage.  The on-device storage is
- * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile
- * with a file descriptor that points to the output file.
+ * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device
+ * storage.  Messages are sent to its message handler to request that energy data be logged, at
+ * which time it queries the PowerStats HAL and logs the data to on-device storage.  The on-device
+ * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or
+ * writeResidencyDataToFile with a file descriptor that points to the output file.
  */
 public final class PowerStatsLogger extends Handler {
     private static final String TAG = PowerStatsLogger.class.getSimpleName();
@@ -61,6 +65,10 @@
     private final PowerStatsDataStorage mPowerStatsModelStorage;
     private final PowerStatsDataStorage mPowerStatsResidencyStorage;
     private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private File mDataStoragePath;
+    private boolean mDeleteMeterDataOnBoot;
+    private boolean mDeleteModelDataOnBoot;
+    private boolean mDeleteResidencyDataOnBoot;
 
     @Override
     public void handleMessage(Message msg) {
@@ -230,16 +238,99 @@
         pos.flush();
     }
 
-    public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
-            String modelFilename, String residencyFilename,
+    private boolean dataChanged(String cachedFilename, byte[] dataCurrent) {
+        boolean dataChanged = false;
+
+        if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) {
+            final File cachedFile = new File(mDataStoragePath, cachedFilename);
+
+            if (cachedFile.exists()) {
+                // Get the byte array for the cached data.
+                final byte[] dataCached = new byte[(int) cachedFile.length()];
+
+                // Get the cached data from file.
+                try {
+                    final FileInputStream fis = new FileInputStream(cachedFile.getPath());
+                    fis.read(dataCached);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to read cached data from file");
+                }
+
+                // If the cached and current data are different, delete the data store.
+                dataChanged = !Arrays.equals(dataCached, dataCurrent);
+            } else {
+                // Either the cached file was somehow deleted, or this is the first
+                // boot of the device and we're creating the file for the first time.
+                // In either case, delete the log files.
+                dataChanged = true;
+            }
+        }
+
+        return dataChanged;
+    }
+
+    private void updateCacheFile(String cacheFilename, byte[] data) {
+        try {
+            final AtomicFile atomicCachedFile =
+                    new AtomicFile(new File(mDataStoragePath, cacheFilename));
+            final FileOutputStream fos = atomicCachedFile.startWrite();
+            fos.write(data);
+            atomicCachedFile.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write current data to cached file");
+        }
+    }
+
+    public boolean getDeleteMeterDataOnBoot() {
+        return mDeleteMeterDataOnBoot;
+    }
+
+    public boolean getDeleteModelDataOnBoot() {
+        return mDeleteModelDataOnBoot;
+    }
+
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mDeleteResidencyDataOnBoot;
+    }
+
+    public PowerStatsLogger(Context context, File dataStoragePath,
+            String meterFilename, String meterCacheFilename,
+            String modelFilename, String modelCacheFilename,
+            String residencyFilename, String residencyCacheFilename,
             IPowerStatsHALWrapper powerStatsHALWrapper) {
         super(Looper.getMainLooper());
         mPowerStatsHALWrapper = powerStatsHALWrapper;
-        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mDataStoragePath = dataStoragePath;
+
+        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             meterFilename);
-        mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             modelFilename);
-        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             residencyFilename);
+
+        final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
+        final byte[] channelBytes = ChannelUtils.getProtoBytes(channels);
+        mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes);
+        if (mDeleteMeterDataOnBoot) {
+            mPowerStatsMeterStorage.deleteLogs();
+            updateCacheFile(meterCacheFilename, channelBytes);
+        }
+
+        final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo();
+        final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes);
+        if (mDeleteModelDataOnBoot) {
+            mPowerStatsModelStorage.deleteLogs();
+            updateCacheFile(modelCacheFilename, energyConsumerBytes);
+        }
+
+        final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo();
+        final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities);
+        mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes);
+        if (mDeleteResidencyDataOnBoot) {
+            mPowerStatsResidencyStorage.deleteLogs();
+            updateCacheFile(residencyCacheFilename, powerEntityBytes);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index b7285d5..bb52c1d 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -61,8 +61,12 @@
     private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
     private static final String RESIDENCY_FILENAME =
             "log.powerstats.residency." + DATA_STORAGE_VERSION;
+    private static final String METER_CACHE_FILENAME = "meterCache";
+    private static final String MODEL_CACHE_FILENAME = "modelCache";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
 
     private final Injector mInjector;
+    private File mDataStoragePath;
 
     private Context mContext;
     @Nullable
@@ -98,6 +102,18 @@
             return RESIDENCY_FILENAME;
         }
 
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
         IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
             return PowerStatsHALWrapper.getPowerStatsHalImpl();
         }
@@ -112,10 +128,15 @@
         }
 
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            return new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
         }
 
         BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@@ -187,14 +208,31 @@
         mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
+    @VisibleForTesting
+    public boolean getDeleteMeterDataOnBoot() {
+        return mPowerStatsLogger.getDeleteMeterDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteModelDataOnBoot() {
+        return mPowerStatsLogger.getDeleteModelDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mPowerStatsLogger.getDeleteResidencyDataOnBoot();
+    }
+
     private void onBootCompleted() {
         if (getPowerStatsHal().isInitialized()) {
             if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers");
+            mDataStoragePath = mInjector.createDataStoragePath();
 
             // Only start logger and triggers if initialization is successful.
-            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
-                mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
-                mInjector.createModelFilename(), mInjector.createResidencyFilename(),
+            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
+                mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
+                mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
+                mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
                 getPowerStatsHal());
             mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
             mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index bd003d3..11b22a5 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -49,6 +49,12 @@
     private static final String TAG = ProtoStreamUtils.class.getSimpleName();
 
     static class PowerEntityUtils {
+        public static byte[] getProtoBytes(PowerEntity[] powerEntity) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(powerEntity, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(PowerEntity[] powerEntity,
                 ProtoOutputStream pos) {
             if (powerEntity == null) return;
@@ -260,6 +266,12 @@
     }
 
     static class ChannelUtils {
+        public static byte[] getProtoBytes(Channel[] channel) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(channel, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) {
             if (channel == null) return;
 
@@ -396,6 +408,12 @@
     }
 
     static class EnergyConsumerUtils {
+        public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(energyConsumer, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(EnergyConsumer[] energyConsumer,
                 ProtoOutputStream pos) {
             if (energyConsumer == null) return;
@@ -410,6 +428,72 @@
             }
         }
 
+        public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException {
+            final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
+            List<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>();
+
+            while (true) {
+                try {
+                    int nextField = pis.nextField();
+                    EnergyConsumer energyConsumer = new EnergyConsumer();
+
+                    if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) {
+                        long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER);
+                        energyConsumerList.add(unpackEnergyConsumerProto(pis));
+                        pis.end(token);
+                    } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
+                        return energyConsumerList.toArray(
+                            new EnergyConsumer[energyConsumerList.size()]);
+                    } else {
+                        Slog.e(TAG, "Unhandled field in proto: "
+                                + ProtoUtils.currentFieldToString(pis));
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in proto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
+        private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis)
+                throws IOException {
+            final EnergyConsumer energyConsumer = new EnergyConsumer();
+
+            while (true) {
+                try {
+                    switch (pis.nextField()) {
+                        case (int) EnergyConsumerProto.ID:
+                            energyConsumer.id = pis.readInt(EnergyConsumerProto.ID);
+                            break;
+
+                        case (int) EnergyConsumerProto.ORDINAL:
+                            energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL);
+                            break;
+
+                        case (int) EnergyConsumerProto.TYPE:
+                            energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE);
+                            break;
+
+                        case (int) EnergyConsumerProto.NAME:
+                            energyConsumer.name = pis.readString(EnergyConsumerProto.NAME);
+                            break;
+
+                        case ProtoInputStream.NO_MORE_FIELDS:
+                            return energyConsumer;
+
+                        default:
+                            Slog.e(TAG, "Unhandled field in EnergyConsumerProto: "
+                                    + ProtoUtils.currentFieldToString(pis));
+                            break;
+
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
         public static void print(EnergyConsumer[] energyConsumer) {
             if (energyConsumer == null) return;
 
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3726407..02a597e 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -30,7 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 
 import java.util.Collections;
@@ -86,18 +86,18 @@
     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
 
     /**
-     * Causes this VCN to immediately enter Safemode.
+     * Causes this VCN to immediately enter safe mode.
      *
-     * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
-     * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
+     * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
+     * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
      */
-    private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;
+    private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final Dependencies mDeps;
     @NonNull private final VcnNetworkRequestListener mRequestListener;
-    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
+    @NonNull private final VcnSafeModeCallback mVcnSafeModeCallback;
 
     @NonNull
     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,13 +125,13 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
+            @NonNull VcnSafeModeCallback vcnSafeModeCallback) {
         this(
                 vcnContext,
                 subscriptionGroup,
                 config,
                 snapshot,
-                vcnSafemodeCallback,
+                vcnSafeModeCallback,
                 new Dependencies());
     }
 
@@ -141,13 +141,13 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
+            @NonNull VcnSafeModeCallback vcnSafeModeCallback,
             @NonNull Dependencies deps) {
         super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mVcnSafemodeCallback =
-                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
+        mVcnSafeModeCallback =
+                Objects.requireNonNull(vcnSafeModeCallback, "Missing vcnSafeModeCallback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
         mRequestListener = new VcnNetworkRequestListener();
 
@@ -216,8 +216,8 @@
             case MSG_CMD_TEARDOWN:
                 handleTeardown();
                 break;
-            case MSG_CMD_ENTER_SAFEMODE:
-                handleEnterSafemode();
+            case MSG_CMD_ENTER_SAFE_MODE:
+                handleEnterSafeMode();
                 break;
             default:
                 Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
@@ -243,10 +243,10 @@
         mIsActive.set(false);
     }
 
-    private void handleEnterSafemode() {
+    private void handleEnterSafeMode() {
         handleTeardown();
 
-        mVcnSafemodeCallback.onEnteredSafemode();
+        mVcnSafeModeCallback.onEnteredSafeMode();
     }
 
     private void handleNetworkRequested(
@@ -335,14 +335,14 @@
     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public interface VcnGatewayStatusCallback {
-        /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
-        void onEnteredSafemode();
+        /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
+        void onEnteredSafeMode();
     }
 
     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
         @Override
-        public void onEnteredSafemode() {
-            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
+        public void onEnteredSafeMode() {
+            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
         }
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 12590eb..59cb6ac 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -418,13 +418,13 @@
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
 
     /**
-     * Sent when this VcnGatewayConnection has entered Safemode.
+     * Sent when this VcnGatewayConnection has entered safe mode.
      *
-     * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+     * <p>A VcnGatewayConnection enters safe mode when it takes over {@link
      * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
      *
      * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
-     * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+     * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down
      * its VcnGatewayConnectin(s).
      *
      * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
@@ -432,7 +432,7 @@
      *
      * @param arg1 The "all" token; this signal is always honored.
      */
-    private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+    private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
@@ -551,7 +551,7 @@
     @Nullable private WakeupMessage mTeardownTimeoutAlarm;
     @Nullable private WakeupMessage mDisconnectRequestAlarm;
     @Nullable private WakeupMessage mRetryTimeoutAlarm;
-    @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
+    @Nullable private WakeupMessage mSafeModeTimeoutAlarm;
 
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
@@ -638,7 +638,7 @@
         cancelTeardownTimeoutAlarm();
         cancelDisconnectRequestAlarm();
         cancelRetryTimeoutAlarm();
-        cancelSafemodeAlarm();
+        cancelSafeModeAlarm();
 
         mUnderlyingNetworkTracker.teardown();
     }
@@ -928,27 +928,27 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setSafemodeAlarm() {
+    void setSafeModeAlarm() {
         // Only schedule a NEW alarm if none is already set.
-        if (mSafemodeTimeoutAlarm != null) {
+        if (mSafeModeTimeoutAlarm != null) {
             return;
         }
 
-        final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
-        mSafemodeTimeoutAlarm =
+        final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+        mSafeModeTimeoutAlarm =
                 createScheduledAlarm(
                         SAFEMODE_TIMEOUT_ALARM,
                         delayedMessage,
                         TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
     }
 
-    private void cancelSafemodeAlarm() {
-        if (mSafemodeTimeoutAlarm != null) {
-            mSafemodeTimeoutAlarm.cancel();
-            mSafemodeTimeoutAlarm = null;
+    private void cancelSafeModeAlarm() {
+        if (mSafeModeTimeoutAlarm != null) {
+            mSafeModeTimeoutAlarm.cancel();
+            mSafeModeTimeoutAlarm = null;
         }
 
-        removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
+        removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
     }
 
     private void sessionLost(int token, @Nullable Exception exception) {
@@ -1125,7 +1125,7 @@
                 Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
             }
 
-            cancelSafemodeAlarm();
+            cancelSafeModeAlarm();
         }
 
         @Override
@@ -1153,7 +1153,7 @@
         @Override
         protected void exitState() {
             // Safe to blindly set up, as it is cancelled and cleared on entering this state
-            setSafemodeAlarm();
+            setSafeModeAlarm();
         }
     }
 
@@ -1245,9 +1245,9 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1331,9 +1331,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1399,7 +1399,7 @@
 
             // Validated connection, clear failed attempt counter
             mFailedAttempts = 0;
-            cancelSafemodeAlarm();
+            cancelSafeModeAlarm();
         }
 
         protected void applyTransform(
@@ -1517,9 +1517,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1575,7 +1575,7 @@
         protected void exitState() {
             // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
             // while in ConnectedState (which cancels the previous alarm)
-            setSafemodeAlarm();
+            setSafeModeAlarm();
         }
     }
 
@@ -1623,9 +1623,9 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
-                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
-                    mGatewayStatusCallback.onEnteredSafemode();
-                    mSafemodeTimeoutAlarm = null;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
                     break;
                 default:
                     logUnhandledMessage(msg);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 370d921..5697564 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -31,7 +31,6 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.ILocalWallpaperColorConsumer;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
@@ -60,7 +59,6 @@
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -87,10 +85,7 @@
 import android.service.wallpaper.WallpaperService;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.EventLog;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -110,6 +105,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.WindowManagerInternal;
@@ -140,8 +136,6 @@
     private static final String TAG = "WallpaperManagerService";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LIVE = true;
-    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
-            new RectF(0, 0, 1, 1);
 
     public static class Lifecycle extends SystemService {
         private IWallpaperManagerService mService;
@@ -872,12 +866,6 @@
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
     private int mCurrentUserId = UserHandle.USER_NULL;
     private boolean mInAmbientMode;
-    private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
-            new ArrayMap<>();
-    private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
-            mLocalColorAreaCallbacks = new ArrayMap<>();
-    private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
-    private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
 
     static class WallpaperData {
 
@@ -1288,32 +1276,6 @@
         }
 
         @Override
-        public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
-                int displayId) {
-            forEachDisplayConnector(displayConnector -> {
-                if (displayConnector.mDisplayId == displayId) {
-                    RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
-                    ArrayMap<IBinder, Integer> callbackDisplayIds;
-                    synchronized (mLock) {
-                        callbacks = mLocalColorAreaCallbacks.get(area);
-                        callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
-                    }
-                    if (callbacks == null) return;
-                    callbacks.broadcast(c -> {
-                        try {
-                            int targetDisplayId =
-                                    callbackDisplayIds.get(c.asBinder());
-                            if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
-                        } catch (RemoteException e) {
-                            e.printStackTrace();
-                        }
-                    });
-                }
-            });
-        }
-
-
-        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 Slog.w(TAG, "Wallpaper service gone: " + name);
@@ -1475,15 +1437,6 @@
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to request wallpaper colors", e);
                 }
-
-                ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
-                if (areas != null && areas.size() != 0) {
-                    try {
-                        connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to register local colors areas", e);
-                    }
-                }
             }
         }
 
@@ -2387,115 +2340,6 @@
         return true;
     }
 
-    private IWallpaperEngine getEngine(int which, int userId, int displayId) {
-        WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
-        if (wallpaperData == null) return null;
-        IWallpaperEngine engine = null;
-        synchronized (mLock) {
-            for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
-                int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
-                int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
-                if (id != displayId && currentWhich != which) continue;
-                engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
-                break;
-            }
-        }
-        return engine;
-    }
-
-    @Override
-    public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
-            @NonNull List<RectF> regions, int which, int userId, int displayId)
-            throws RemoteException {
-        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
-            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
-        }
-        IWallpaperEngine engine = getEngine(which, userId, displayId);
-        if (engine == null) return;
-        ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
-        synchronized (mLock) {
-            ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
-            if (areas == null) areas = new ArraySet<>(regions.size());
-            areas.addAll(regions);
-            mLocalColorCallbackAreas.put(callback.asBinder(), areas);
-        }
-        for (int i = 0; i < regions.size(); i++) {
-            if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
-                continue;
-            }
-            RemoteCallbackList callbacks;
-            synchronized (mLock) {
-                callbacks = mLocalColorAreaCallbacks.get(
-                        regions.get(i));
-                if (callbacks == null) {
-                    callbacks = new RemoteCallbackList();
-                    mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
-                }
-                mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
-                ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
-                if (displayAreas == null) {
-                    displayAreas = new ArraySet<>(1);
-                    mLocalColorDisplayIdAreas.put(displayId, displayAreas);
-                }
-                displayAreas.add(regions.get(i));
-            }
-            validAreas.add(regions.get(i));
-            callbacks.register(callback);
-        }
-        engine.addLocalColorsAreas(validAreas);
-    }
-
-    @Override
-    public void removeOnLocalColorsChangedListener(
-            @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
-            int displayId) throws RemoteException {
-        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
-            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
-        }
-        final UserHandle callingUser = Binder.getCallingUserHandle();
-        if (callingUser.getIdentifier() != userId) {
-            throw new SecurityException("calling user id does not match");
-        }
-        final long identity = Binder.clearCallingIdentity();
-        ArrayList<RectF> removeAreas = new ArrayList<>();
-        ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
-                callbacksToRemove = new ArrayList<>();
-        try {
-            synchronized (mLock) {
-                ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
-                mLocalColorCallbackDisplayId.remove(callback.asBinder());
-                if (areas == null) areas = new ArraySet<>();
-                for (RectF area : areas) {
-                    RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
-                    if (callbacks == null) continue;
-                    callbacksToRemove.add(new Pair<>(callbacks, callback));
-                    if (callbacks.getRegisteredCallbackCount() == 0) {
-                        mLocalColorAreaCallbacks.remove(area);
-                        removeAreas.add(area);
-                    }
-                    ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
-                    if (displayAreas != null) {
-                        displayAreas.remove(area);
-                    }
-                }
-            }
-            for (int i = 0; i < callbacksToRemove.size(); i++) {
-                Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
-                        pair = callbacksToRemove.get(i);
-                pair.first.unregister(pair.second);
-            }
-        } catch (Exception e) {
-            // ignore any exception
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        if (removeAreas.size() == 0) return;
-        IWallpaperEngine engine = getEngine(which, userId, displayId);
-        if (engine == null) return;
-        engine.removeLocalColorsAreas(removeAreas);
-    }
-
     @Override
     public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
             throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6fbeaa4..bb5e8bf 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,7 +847,6 @@
         mWmService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction();
-            mWmService.mSyncEngine.onSurfacePlacement();
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
@@ -861,6 +860,7 @@
 
         // Send any pending task-info changes that were queued-up during a layout deferment
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        mWmService.mSyncEngine.onSurfacePlacement();
         mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9772c2e..9250894 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -247,6 +247,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.permission.AdminPermissionControlParams;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.provider.CalendarContract;
@@ -422,10 +423,12 @@
         DELEGATION_CERT_SELECTION,
     };
 
-    // Subset of delegations that can only be delegated by Device Owner.
-    private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
-            DELEGATION_NETWORK_LOGGING,
-    });
+    // Subset of delegations that can only be delegated by Device Owner or Profile Owner of a
+    // managed profile.
+    private static final List<String> DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS =
+            Arrays.asList(new String[]{
+                    DELEGATION_NETWORK_LOGGING,
+            });
 
     // Subset of delegations that only one single package within a given user can hold
     private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
@@ -531,8 +534,10 @@
 
     /**
      * Strings logged with {@link
-     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
-     * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
+     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
+     * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
      */
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -5884,10 +5889,10 @@
         }
         // Retrieve the user ID of the calling process.
         final int userId = caller.getUserId();
-        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         // Ensure calling process is device/profile owner.
-        if (hasDoDelegation) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
+            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                    || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
         } else {
             Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
         }
@@ -6448,7 +6453,7 @@
     private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
         boolean success = false;
         try {
-            if (getCurrentForegroundUser() == userId) {
+            if (getCurrentForegroundUserId() == userId) {
                 mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
             }
 
@@ -7889,6 +7894,14 @@
             updateDeviceOwnerLocked();
             setDeviceOwnershipSystemPropertyLocked();
 
+            //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this
+            // hard-coded default value setting.
+            if (isAdb(caller)) {
+                activeAdmin.mAdminCanGrantSensorsPermissions = true;
+                mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true);
+                saveSettingsLocked(userId);
+            }
+
             mInjector.binderWithCleanCallingIdentity(() -> {
                 // Restrict adding a managed profile when a device owner is set on the device.
                 // That is to prevent the co-existence of a managed profile and a device owner
@@ -7908,7 +7921,7 @@
             Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
 
             if (mInjector.userManagerIsHeadlessSystemUserMode()) {
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 Slog.i(LOG_TAG, "setDeviceOwner(): setting " + admin
                         + " as profile owner on user " + currentForegroundUser);
                 // Sets profile owner on current foreground user since
@@ -9039,7 +9052,7 @@
         return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
     }
 
-    private @UserIdInt int getCurrentForegroundUser() {
+    private @UserIdInt int getCurrentForegroundUserId() {
         try {
             return mInjector.getIActivityManager().getCurrentUser().id;
         } catch (RemoteException e) {
@@ -9048,6 +9061,25 @@
         return UserHandle.USER_NULL;
     }
 
+    @Override
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        checkIsDeviceOwner(getCallerIdentity());
+
+        int userId = mInjector.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId());
+
+        boolean isAffiliated;
+        synchronized (getLockObject()) {
+            isAffiliated = isUserAffiliatedWithDeviceLocked(userId);
+        }
+
+        if (!isAffiliated) return Collections.emptyList();
+
+        List<UserHandle> users = new ArrayList<>(1);
+        users.add(UserHandle.of(userId));
+
+        return users;
+    }
+
     protected int getProfileParentId(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -12641,9 +12673,12 @@
                 if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                    AdminPermissionControlParams permissionParams =
+                            new AdminPermissionControlParams(packageName, permission, grantState,
+                                    canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
                             .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
-                                    packageName, permission, grantState, mContext.getMainExecutor(),
+                                    permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
                                             callback.sendResult(null);
@@ -12859,7 +12894,7 @@
                     return CODE_NONSYSTEM_USER_EXISTS;
                 }
 
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 if (callingUserId != currentForegroundUser
                         && mInjector.userManagerIsHeadlessSystemUserMode()
                         && currentForegroundUser == UserHandle.USER_SYSTEM) {
@@ -12955,6 +12990,11 @@
         return CODE_OK;
     }
 
+    private void checkIsDeviceOwner(CallerIdentity caller) {
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+                + " is not device owner");
+    }
+
     private ComponentName getOwnerComponent(String packageName, int userId) {
         if (isDeviceOwnerPackage(packageName, userId)) {
             return mOwners.getDeviceOwnerComponent();
@@ -14151,9 +14191,10 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isDeviceOwner(caller)
-                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+                && (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14175,6 +14216,8 @@
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
                     .setInt(enabled ? 1 : 0)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .write();
         }
     }
@@ -14331,9 +14374,10 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller)
-                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+                &&  (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
         if (mOwners.hasDeviceOwner()) {
             checkAllUsersAreAffiliatedWithDevice();
@@ -14347,6 +14391,8 @@
                     .createEvent(DevicePolicyEnums.RETRIEVE_NETWORK_LOGS)
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .write();
 
             final long currentTime = System.currentTimeMillis();
@@ -15445,7 +15491,7 @@
     private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException {
         //TODO(b/175285301): Explicitly get the user's identity to check.
         int lockTaskFeatures =
-                getUserData(getCurrentForegroundUser()).mLockTaskFeatures;
+                getUserData(getCurrentForegroundUserId()).mLockTaskFeatures;
         return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
     }
 
@@ -16701,6 +16747,8 @@
     @Override
     public boolean canUsbDataSignalingBeDisabled() {
         return mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+                mInjector.getUsbManager() != null
+                        && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3
+        );
     }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2b09d12..dd2dd81 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -326,6 +326,8 @@
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
             "com.android.server.systemcaptions.SystemCaptionsManagerService";
+    private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
+            "com.android.server.texttospeech.TextToSpeechManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
     private static final String IOT_SERVICE_CLASS =
@@ -1713,6 +1715,7 @@
             startAttentionService(context, t);
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
+            startTextToSpeechManagerService(context, t);
 
             // System Speech Recognition Service
             if (deviceHasConfigString(context,
@@ -2918,6 +2921,13 @@
         t.traceEnd();
     }
 
+    private void startTextToSpeechManagerService(@NonNull Context context,
+            @NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("StartTextToSpeechManagerService");
+        mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+        t.traceEnd();
+    }
+
     private void startContentCaptureService(@NonNull Context context,
             @NonNull TimingsTraceAndSlog t) {
         // First check if it was explicitly enabled by DeviceConfig
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 5453de1..9666337 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.people;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -29,6 +30,7 @@
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -184,6 +186,20 @@
         }
 
         @Override
+        public boolean isConversation(String packageName, int userId, String shortcutId) {
+            enforceHasReadPeopleDataPermission();
+            handleIncomingUser(userId);
+            return mDataManager.isConversation(packageName, userId, shortcutId);
+        }
+
+        private void enforceHasReadPeopleDataPermission() throws SecurityException {
+            if (getContext().checkCallingPermission(Manifest.permission.READ_PEOPLE_DATA)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Caller doesn't have READ_PEOPLE_DATA permission.");
+            }
+        }
+
+        @Override
         public long getLastInteraction(String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get last interaction");
             return mDataManager.getLastInteraction(packageName, userId, shortcutId);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 444f9c6..1048dfd 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -58,6 +58,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -354,6 +355,14 @@
         });
     }
 
+    /** Returns whether {@code shortcutId} is backed by Conversation. */
+    public boolean isConversation(String packageName, int userId, String shortcutId) {
+        ConversationChannel channel = getConversation(packageName, userId, shortcutId);
+        return channel != null
+                && channel.getShortcutInfo() != null
+                && !TextUtils.isEmpty(channel.getShortcutInfo().getLabel());
+    }
+
     /**
      * Returns the last notification interaction with the specified conversation. If the
      * conversation can't be found or no interactions have been recorded, returns 0L.
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/values.xml
similarity index 82%
rename from services/tests/servicestests/res/values/strings.xml
rename to services/tests/servicestests/res/values/values.xml
index 1f07ad5..0404ebc 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/values.xml
@@ -36,4 +36,12 @@
     <string name="module_2_name" translatable="false">module_2_name</string>
 
     <string name="widget_description">widget description string</string>
+    <dimen name="widget_min_width">80dp</dimen>
+    <dimen name="widget_min_height">40dp</dimen>
+    <dimen name="widget_min_resize_width">40dp</dimen>
+    <dimen name="widget_min_resize_height">20dp</dimen>
+    <dimen name="widget_max_resize_width">160dp</dimen>
+    <dimen name="widget_max_resize_height">80dp</dimen>
+    <integer name="widget_target_cell_width">2</integer>
+    <integer name="widget_target_cell_height">1</integer>
 </resources>
diff --git a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
index 72f025d..de06191 100644
--- a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
+++ b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
@@ -16,12 +16,18 @@
   -->
 
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
-  android:minWidth="40dp"
-  android:minHeight="40dp"
+  android:minWidth="@dimen/widget_min_width"
+  android:minHeight="@dimen/widget_min_height"
+  android:minResizeWidth="@dimen/widget_min_resize_width"
+  android:minResizeHeight="@dimen/widget_min_resize_height"
+  android:maxResizeWidth="@dimen/widget_max_resize_width"
+  android:maxResizeHeight="@dimen/widget_max_resize_height"
+  android:targetCellWidth="@integer/widget_target_cell_width"
+  android:targetCellHeight="@integer/widget_target_cell_height"
   android:updatePeriodMillis="86400000"
   android:previewImage="@drawable/icon1"
   android:previewLayout="@layout/widget_preview"
   android:resizeMode="horizontal|vertical"
   android:description="@string/widget_description"
   android:widgetCategory="home_screen">
-</appwidget-provider>
\ No newline at end of file
+</appwidget-provider>
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 738f008..6d4189e 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -90,7 +90,7 @@
         writeGameServiceXml();
     }
 
-    private void verifyGameServiceSettingsData(Settings settings) {
+    private void verifyGameServiceSettingsData(GameManagerSettings settings) {
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
@@ -107,7 +107,7 @@
         /* write out files and read */
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
-        Settings settings = new Settings(context.getFilesDir());
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertThat(settings.readPersistentDataLocked(), is(true));
         verifyGameServiceSettingsData(settings);
     }
@@ -118,7 +118,7 @@
         // write out files and read
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
-        Settings settings = new Settings(context.getFilesDir());
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertThat(settings.readPersistentDataLocked(), is(true));
 
         // write out, read back in and verify the same
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 78eb2df..ff8fedc 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -103,6 +103,26 @@
         assertEquals(info.loadDescription(mTestContext), "widget description string");
     }
 
+    public void testParseSizeConfiguration() {
+        AppWidgetProviderInfo info =
+                mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
+
+        assertThat(info.minWidth).isEqualTo(getDimensionResource(R.dimen.widget_min_width));
+        assertThat(info.minHeight).isEqualTo(getDimensionResource(R.dimen.widget_min_height));
+        assertThat(info.minResizeWidth)
+                .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_width));
+        assertThat(info.minResizeHeight)
+                .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_height));
+        assertThat(info.maxResizeWidth)
+                .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_width));
+        assertThat(info.maxResizeHeight)
+                .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_height));
+        assertThat(info.targetCellWidth)
+                .isEqualTo(getIntegerResource(R.integer.widget_target_cell_width));
+        assertThat(info.targetCellHeight)
+                .isEqualTo(getIntegerResource(R.integer.widget_target_cell_height));
+    }
+
     public void testRequestPinAppWidget_otherProvider() {
         ComponentName otherProvider = null;
         for (AppWidgetProviderInfo provider : mManager.getInstalledProviders()) {
@@ -325,6 +345,14 @@
         latch.await();
     }
 
+    private int getDimensionResource(int resId) {
+        return mTestContext.getResources().getDimensionPixelSize(resId);
+    }
+
+    private int getIntegerResource(int resId) {
+        return mTestContext.getResources().getInteger(resId);
+    }
+
     private class TestContext extends ContextWrapper {
 
         public TestContext() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index bb57a69..fcbd897 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -15,12 +15,9 @@
  */
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.tv.cec.V1_0.CecMessage;
-import android.hardware.tv.cec.V1_0.HotplugEvent;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.os.RemoteException;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -99,7 +96,7 @@
 
     @Override
     public int nativeGetVersion() {
-        return 0;
+        return HdmiControlManager.HDMI_CEC_VERSION_2_0;
     }
 
     @Override
@@ -139,20 +136,15 @@
         if (mCallback == null) {
             return;
         }
-        CecMessage message = new CecMessage();
-        message.initiator = hdmiCecMessage.getSource();
-        message.destination = hdmiCecMessage.getDestination();
-        ArrayList<Byte> body = new ArrayList<>();
-        body.add((byte) hdmiCecMessage.getOpcode());
+        int source = hdmiCecMessage.getSource();
+        int destination = hdmiCecMessage.getDestination();
+        byte[] body = new byte[hdmiCecMessage.getParams().length + 1];
+        int i = 0;
+        body[i++] = (byte) hdmiCecMessage.getOpcode();
         for (byte param : hdmiCecMessage.getParams()) {
-            body.add(param);
+            body[i++] = param;
         }
-        message.body = body;
-        try {
-            mCallback.onCecMessage(message);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending CEC message", e);
-        }
+        mCallback.onCecMessage(source, destination, body);
     }
 
     public void onHotplugEvent(int port, boolean connected) {
@@ -160,15 +152,7 @@
             return;
         }
 
-        HotplugEvent hotplugEvent = new HotplugEvent();
-        hotplugEvent.portId = port;
-        hotplugEvent.connected = connected;
-
-        try {
-            mCallback.onHotplugEvent(hotplugEvent);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending hotplug event", e);
-        }
+        mCallback.onHotplugEvent(port, connected);
     }
 
     public List<HdmiCecMessage> getResultMessages() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 5342486..b11ac24 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -24,6 +24,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -34,6 +36,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.sysprop.HdmiProperties;
@@ -1552,6 +1555,31 @@
     }
 
     @Test
+    public void toggleAndFollowTvPower_isInteractive() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+        mActiveMediaSessionsPaused = false;
+        mWokenUp = false;
+
+        mHdmiControlService.toggleAndFollowTvPower();
+
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+        assertThat(mWokenUp).isFalse();
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_isNotInteractive() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        mActiveMediaSessionsPaused = false;
+        mWokenUp = false;
+
+        mHdmiControlService.toggleAndFollowTvPower();
+
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+        assertThat(mWokenUp).isTrue();
+    }
+
+
+    @Test
     public void shouldHandleTvPowerKey_CecDisabled() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 6e57896..50d9f61 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -601,6 +601,22 @@
     }
 
     @Test
+    public void testIsConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isFalse();
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isTrue();
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID + "1")).isFalse();
+    }
+
+    @Test
     public void testNotificationChannelCreated() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         mDataManager.onUserUnlocked(USER_ID_SECONDARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 23fcf70..4d0beef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -103,7 +103,6 @@
 import android.util.Xml;
 
 import com.android.frameworks.servicestests.R;
-import com.android.internal.util.FastXmlSerializer;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
@@ -111,7 +110,6 @@
 import org.mockito.ArgumentCaptor;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -141,6 +139,7 @@
 
     private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
     private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+    private static final int CACHE_OWNER_2 = LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
     @Override
     protected void tearDown() throws Exception {
@@ -1531,7 +1530,8 @@
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
                     makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
-                    makeLongLivedShortcut("s4"))));
+                    makeLongLivedShortcut("s4"), makeLongLivedShortcut("s5"),
+                    makeLongLivedShortcut("s6"))));
         });
 
         // Pin s2
@@ -1545,28 +1545,30 @@
             mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
                     HANDLE_USER_0, CACHE_OWNER_0);
-            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4", "s5"),
                     HANDLE_USER_0, CACHE_OWNER_1);
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+                    HANDLE_USER_0, CACHE_OWNER_2);
         });
 
         setCaller(CALLING_PACKAGE_1);
 
         // Get dynamic shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2", "s3", "s4");
+                "s1", "s2", "s3", "s4", "s5", "s6");
         // Get pinned shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
                 "s2");
         // Get cached shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         // Remove a dynamic cached shortcut
-        mManager.removeDynamicShortcuts(list("s4"));
+        mManager.removeDynamicShortcuts(list("s4", "s5"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2", "s3");
+                "s1", "s2", "s3", "s6");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1574,15 +1576,21 @@
         });
         // s2 still cached by owner1. s4 wasn't cached by owner0 so didn't get removed.
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         // uncache a non-dynamic shortcut. Should be removed.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
                     HANDLE_USER_0, CACHE_OWNER_1);
         });
+
+        // uncache s6 by its only owner. s5 still cached by owner1
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+                    HANDLE_USER_0, CACHE_OWNER_2);
+        });
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2");
+                "s2", "s5");
 
         // Cache another shortcut
         runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -1590,14 +1598,14 @@
                     HANDLE_USER_0, CACHE_OWNER_0);
         });
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s3");
+                "s2", "s3", "s5");
 
         // Remove a dynamic cached pinned long lived shortcut
         mManager.removeLongLivedShortcuts(list("s2"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s3");
+                "s1", "s3", "s6");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s3");
+                "s3", "s5");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
                 "s2");
     }
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 03e60af..ddbe81c 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.powerstats;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,6 +35,9 @@
 
 import com.android.server.SystemService;
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
+import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
+import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
 import com.android.server.powerstats.nano.PowerEntityProto;
 import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
 import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
@@ -52,6 +56,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -63,15 +68,18 @@
 public class PowerStatsServiceTest {
     private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
     private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
-    private static final String METER_FILENAME = "metertest";
-    private static final String MODEL_FILENAME = "modeltest";
-    private static final String RESIDENCY_FILENAME = "residencytest";
+    private static final String METER_FILENAME = "log.powerstats.metertest.0";
+    private static final String MODEL_FILENAME = "log.powerstats.modeltest.0";
+    private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0";
     private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
     private static final String CHANNEL_NAME = "channelname";
     private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
     private static final String POWER_ENTITY_NAME = "powerentityinfo";
     private static final String STATE_NAME = "stateinfo";
     private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
+    private static final String METER_CACHE_FILENAME = "meterCacheTest";
+    private static final String MODEL_CACHE_FILENAME = "modelCacheTest";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest";
     private static final int ENERGY_METER_COUNT = 8;
     private static final int ENERGY_CONSUMER_COUNT = 2;
     private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
@@ -90,12 +98,12 @@
         private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
         @Override
         File createDataStoragePath() {
-            mDataStorageDir = null;
-
-            try {
-                mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
-            } catch (IOException e) {
-                fail("Could not create temp directory.");
+            if (mDataStorageDir == null) {
+                try {
+                    mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
+                } catch (IOException e) {
+                    fail("Could not create temp directory.");
+                }
             }
 
             return mDataStorageDir;
@@ -117,16 +125,36 @@
         }
 
         @Override
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        @Override
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        @Override
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
+        @Override
         IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
             return mTestPowerStatsHALWrapper;
         }
 
         @Override
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
             return mPowerStatsLogger;
         }
 
@@ -665,4 +693,315 @@
         // input buffer had only length and no data.
         assertTrue(pssProto.stateResidencyResult.length == 0);
     }
+
+    @Test
+    public void testDataStorageDeletedMeterMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Generate random array of bytes to emulate cached meter data.  Store to file.
+        Random rd = new Random();
+        byte[] bytes = new byte[100];
+        rd.nextBytes(bytes);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached meter data is just random bytes it won't match the data read from the HAL.
+        // This mismatch of cached and current HAL data should force a delete.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached meter data was updated to new HAL output.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytesExpected = ChannelUtils.getProtoBytes(channels);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedModelMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached energy consumer data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached energy consumer data is just random bytes it won't match the data read from
+        // the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached energy consumer data was updated to new HAL output.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedResidencyMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached power entity info data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached power entity info data is just random bytes it won't match the data read
+        // from the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertFalse(residencyFile.exists());
+
+        // Verify cached power entity data was updated to new HAL output.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageNotDeletedNoCachedData() throws IOException {
+        // Create the directory where log files will be stored.
+        mInjector.createDataStoragePath();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // This test mimics the device's first boot where there is no cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since there is no cached data on the first boot any log files that happen to exist
+        // should be deleted.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertFalse(residencyFile.exists());
+    }
+
+    @Test
+    public void testDataStorageNotDeletedAllDataMatches() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // All cached data created above should match current data read in PowerStatsService so we
+        // expect the data not to be deleted.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were not deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+    }
 }
diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp
new file mode 100644
index 0000000..bacc932
--- /dev/null
+++ b/services/texttospeech/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.texttospeech-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.texttospeech",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.texttospeech-sources"],
+    libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
new file mode 100644
index 0000000..f805904
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.texttospeech;
+
+import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.speech.tts.ITextToSpeechService;
+import android.speech.tts.ITextToSpeechSession;
+import android.speech.tts.ITextToSpeechSessionCallback;
+import android.speech.tts.TextToSpeech;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}.
+ * Creates {@link TtsClient} interface object with direct connection to
+ * {@link android.speech.tts.TextToSpeechService} provider.
+ *
+ * @see ITextToSpeechSession
+ * @see TextToSpeech
+ */
+final class TextToSpeechManagerPerUserService extends
+        AbstractPerUserSystemService<TextToSpeechManagerPerUserService,
+                TextToSpeechManagerService> {
+
+    private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName();
+
+    TextToSpeechManagerPerUserService(
+            @NonNull TextToSpeechManagerService master,
+            @NonNull Object lock, @UserIdInt int userId) {
+        super(master, lock, userId);
+    }
+
+    void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) {
+        TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback);
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    @NonNull
+    protected ServiceInfo newServiceInfoLocked(
+            @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        try {
+            return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+    }
+
+    private static class TextToSpeechSessionConnection extends
+            ServiceConnector.Impl<ITextToSpeechService> {
+
+        private final String mEngine;
+        private final ITextToSpeechSessionCallback mCallback;
+        private final DeathRecipient mUnbindOnDeathHandler;
+
+        static void start(Context context, @UserIdInt int userId, String engine,
+                ITextToSpeechSessionCallback callback) {
+            new TextToSpeechSessionConnection(context, userId, engine, callback).start();
+        }
+
+        private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine,
+                ITextToSpeechSessionCallback callback) {
+            super(context,
+                    new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
+                    Context.BIND_AUTO_CREATE,
+                    userId,
+                    ITextToSpeechService.Stub::asInterface);
+            mEngine = engine;
+            mCallback = callback;
+            mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported");
+        }
+
+        private void start() {
+            Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine);
+
+            connect()
+                    .thenAccept(
+                            serviceBinder -> {
+                                if (serviceBinder != null) {
+                                    Slog.d(TAG,
+                                            "Connected successfully to TTS engine: " + mEngine);
+                                    try {
+                                        mCallback.onConnected(new ITextToSpeechSession.Stub() {
+                                            @Override
+                                            public void disconnect() {
+                                                unbindEngine("client disconnection request");
+                                            }
+                                        }, serviceBinder.asBinder());
+
+                                        mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0);
+                                    } catch (RemoteException ex) {
+                                        Slog.w(TAG, "Error notifying the client on connection", ex);
+
+                                        unbindEngine(
+                                                "failed communicating with the client - process "
+                                                        + "is dead");
+                                    }
+                                } else {
+                                    Slog.w(TAG, "Failed to obtain TTS engine binder");
+                                    runSessionCallbackMethod(
+                                            () -> mCallback.onError("Failed creating TTS session"));
+                                }
+                            })
+                    .exceptionally(ex -> {
+                        Slog.w(TAG, "TTS engine binding error", ex);
+                        runSessionCallbackMethod(
+                                () -> mCallback.onError(
+                                        "Failed creating TTS session: " + ex.getCause()));
+
+                        return null;
+                    });
+        }
+
+        @Override // from ServiceConnector.Impl
+        protected void onServiceConnectionStatusChanged(
+                ITextToSpeechService service, boolean connected) {
+            if (!connected) {
+                Slog.w(TAG, "Disconnected from TTS engine");
+                runSessionCallbackMethod(mCallback::onDisconnected);
+
+                try {
+                    mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0);
+                } catch (NoSuchElementException ex) {
+                    Slog.d(TAG, "The death recipient was not linked.");
+                }
+            }
+        }
+
+        @Override // from ServiceConnector.Impl
+        protected long getAutoDisconnectTimeoutMs() {
+            return PERMANENT_BOUND_TIMEOUT_MS;
+        }
+
+        private void unbindEngine(String reason) {
+            Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason);
+            unbind();
+        }
+    }
+
+    static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) {
+        try {
+            callbackRunnable.runOrThrow();
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed running callback method", ex);
+        }
+    }
+
+    interface ThrowingRunnable {
+        void runOrThrow() throws RemoteException;
+    }
+}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
new file mode 100644
index 0000000..9015563
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.texttospeech;
+
+import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+import android.speech.tts.ITextToSpeechManager;
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+import com.android.server.infra.AbstractMasterSystemService;
+
+
+/**
+ * A service that  allows secured synthesizing of text to speech audio. Upon request creates a
+ * session
+ * that is managed by {@link TextToSpeechManagerPerUserService}.
+ *
+ * @see ITextToSpeechManager
+ */
+public final class TextToSpeechManagerService extends
+        AbstractMasterSystemService<TextToSpeechManagerService,
+                TextToSpeechManagerPerUserService> {
+
+    private static final String TAG = TextToSpeechManagerService.class.getSimpleName();
+
+    public TextToSpeechManagerService(@NonNull Context context) {
+        super(context, /* serviceNameResolver= */ null,
+                /* disallowProperty = */null);
+    }
+
+    @Override // from SystemService
+    public void onStart() {
+        publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE,
+                new TextToSpeechManagerServiceStub());
+    }
+
+    @Override
+    protected TextToSpeechManagerPerUserService newServiceLocked(
+            @UserIdInt int resolvedUserId, boolean disabled) {
+        return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId);
+    }
+
+    private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub {
+        @Override
+        public void createSession(String engine,
+                ITextToSpeechSessionCallback sessionCallback) {
+            synchronized (mLock) {
+                TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
+                        UserHandle.getCallingUserId());
+                if (perUserService != null) {
+                    perUserService.createSessionLocked(engine, sessionCallback);
+                } else {
+                    runSessionCallbackMethod(
+                            () -> sessionCallback.onError("Service is not available for user"));
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl
similarity index 69%
rename from core/java/android/app/ILocalWallpaperColorConsumer.aidl
rename to telecomm/java/android/telecom/CallScreeningService.aidl
index 28b11ec..87b5138 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/telecomm/java/android/telecom/CallScreeningService.aidl
@@ -11,17 +11,12 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.telecom;
 
 /**
- * @hide
+ * {@hide}
  */
-oneway interface ILocalWallpaperColorConsumer {
-    void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+parcelable CallScreeningService.ParcelableCallResponse;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 7988b03..deeb433 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -30,12 +31,18 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -132,7 +139,10 @@
                                 .createFromParcelableCall((ParcelableCall) args.arg2);
                         onScreenCall(callDetails);
                         if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) {
-                            mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
+                            mCallScreeningAdapter.onScreeningResponse(
+                                    callDetails.getTelecomCallId(),
+                                    new ComponentName(getPackageName(), getClass().getName()),
+                                    null);
                         }
                     } catch (RemoteException e) {
                         Log.w(this, "Exception when screening call: " + e);
@@ -157,10 +167,11 @@
 
     private ICallScreeningAdapter mCallScreeningAdapter;
 
-    /*
-     * Information about how to respond to an incoming call.
+    /**
+     * Parcelable version of {@link CallResponse} used to do IPC.
+     * @hide
      */
-    public static class CallResponse {
+    public static class ParcelableCallResponse implements Parcelable {
         private final boolean mShouldDisallowCall;
         private final boolean mShouldRejectCall;
         private final boolean mShouldSilenceCall;
@@ -168,13 +179,168 @@
         private final boolean mShouldSkipNotification;
         private final boolean mShouldScreenCallViaAudioProcessing;
 
+        private final int mCallComposerAttachmentsToShow;
+
+        private ParcelableCallResponse(
+                boolean shouldDisallowCall,
+                boolean shouldRejectCall,
+                boolean shouldSilenceCall,
+                boolean shouldSkipCallLog,
+                boolean shouldSkipNotification,
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
+            mShouldDisallowCall = shouldDisallowCall;
+            mShouldRejectCall = shouldRejectCall;
+            mShouldSilenceCall = shouldSilenceCall;
+            mShouldSkipCallLog = shouldSkipCallLog;
+            mShouldSkipNotification = shouldSkipNotification;
+            mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+        }
+
+        protected ParcelableCallResponse(Parcel in) {
+            mShouldDisallowCall = in.readBoolean();
+            mShouldRejectCall = in.readBoolean();
+            mShouldSilenceCall = in.readBoolean();
+            mShouldSkipCallLog = in.readBoolean();
+            mShouldSkipNotification = in.readBoolean();
+            mShouldScreenCallViaAudioProcessing = in.readBoolean();
+            mCallComposerAttachmentsToShow = in.readInt();
+        }
+
+        public CallResponse toCallResponse() {
+            return new CallResponse.Builder()
+                    .setDisallowCall(mShouldDisallowCall)
+                    .setRejectCall(mShouldRejectCall)
+                    .setSilenceCall(mShouldSilenceCall)
+                    .setSkipCallLog(mShouldSkipCallLog)
+                    .setSkipNotification(mShouldSkipNotification)
+                    .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing)
+                    .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow)
+                    .build();
+        }
+
+        public boolean shouldDisallowCall() {
+            return mShouldDisallowCall;
+        }
+
+        public boolean shouldRejectCall() {
+            return mShouldRejectCall;
+        }
+
+        public boolean shouldSilenceCall() {
+            return mShouldSilenceCall;
+        }
+
+        public boolean shouldSkipCallLog() {
+            return mShouldSkipCallLog;
+        }
+
+        public boolean shouldSkipNotification() {
+            return mShouldSkipNotification;
+        }
+
+        public boolean shouldScreenCallViaAudioProcessing() {
+            return mShouldScreenCallViaAudioProcessing;
+        }
+
+        public int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        public static final Creator<ParcelableCallResponse> CREATOR =
+                new Creator<ParcelableCallResponse>() {
+                    @Override
+                    public ParcelableCallResponse createFromParcel(Parcel in) {
+                        return new ParcelableCallResponse(in);
+                    }
+
+                    @Override
+                    public ParcelableCallResponse[] newArray(int size) {
+                        return new ParcelableCallResponse[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBoolean(mShouldDisallowCall);
+            dest.writeBoolean(mShouldRejectCall);
+            dest.writeBoolean(mShouldSilenceCall);
+            dest.writeBoolean(mShouldSkipCallLog);
+            dest.writeBoolean(mShouldSkipNotification);
+            dest.writeBoolean(mShouldScreenCallViaAudioProcessing);
+            dest.writeInt(mCallComposerAttachmentsToShow);
+        }
+    }
+
+    /**
+     * Information about how to respond to an incoming call. Call screening apps can construct an
+     * instance of this class using {@link CallResponse.Builder}.
+     */
+    public static class CallResponse {
+        /**
+         * Bit flag indicating whether to show the picture attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1;
+
+        /**
+         * Bit flag indicating whether to show the location attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1;
+
+        /**
+         * Bit flag indicating whether to show the subject attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2;
+
+        /**
+         * Bit flag indicating whether to show the priority attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true,
+                value = {
+                        CALL_COMPOSER_ATTACHMENT_PICTURE,
+                        CALL_COMPOSER_ATTACHMENT_LOCATION,
+                        CALL_COMPOSER_ATTACHMENT_SUBJECT,
+                        CALL_COMPOSER_ATTACHMENT_PRIORITY
+                }
+        )
+        public @interface CallComposerAttachmentType {}
+
+        private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4;
+
+        private final boolean mShouldDisallowCall;
+        private final boolean mShouldRejectCall;
+        private final boolean mShouldSilenceCall;
+        private final boolean mShouldSkipCallLog;
+        private final boolean mShouldSkipNotification;
+        private final boolean mShouldScreenCallViaAudioProcessing;
+        private final int mCallComposerAttachmentsToShow;
+
         private CallResponse(
                 boolean shouldDisallowCall,
                 boolean shouldRejectCall,
                 boolean shouldSilenceCall,
                 boolean shouldSkipCallLog,
                 boolean shouldSkipNotification,
-                boolean shouldScreenCallViaAudioProcessing) {
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
             if (!shouldDisallowCall
                     && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
                 throw new IllegalStateException("Invalid response state for allowed call.");
@@ -190,6 +356,7 @@
             mShouldSkipNotification = shouldSkipNotification;
             mShouldSilenceCall = shouldSilenceCall;
             mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
         }
 
         /*
@@ -237,6 +404,49 @@
             return mShouldScreenCallViaAudioProcessing;
         }
 
+        /**
+         * @return A bitmask of call composer attachments that should be shown to the user.
+         */
+        public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        /** @hide */
+        public ParcelableCallResponse toParcelable() {
+            return new ParcelableCallResponse(
+                    mShouldDisallowCall,
+                    mShouldRejectCall,
+                    mShouldSilenceCall,
+                    mShouldSkipCallLog,
+                    mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow
+            );
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            CallResponse that = (CallResponse) o;
+            return mShouldDisallowCall == that.mShouldDisallowCall &&
+                    mShouldRejectCall == that.mShouldRejectCall &&
+                    mShouldSilenceCall == that.mShouldSilenceCall &&
+                    mShouldSkipCallLog == that.mShouldSkipCallLog &&
+                    mShouldSkipNotification == that.mShouldSkipNotification &&
+                    mShouldScreenCallViaAudioProcessing
+                            == that.mShouldScreenCallViaAudioProcessing &&
+                    mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall,
+                    mShouldSkipCallLog, mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow);
+        }
+
         public static class Builder {
             private boolean mShouldDisallowCall;
             private boolean mShouldRejectCall;
@@ -244,6 +454,7 @@
             private boolean mShouldSkipCallLog;
             private boolean mShouldSkipNotification;
             private boolean mShouldScreenCallViaAudioProcessing;
+            private int mCallComposerAttachmentsToShow = -1;
 
             /**
              * Sets whether the incoming call should be blocked.
@@ -329,6 +540,38 @@
                 return this;
             }
 
+            /**
+             * Sets the call composer attachments that should be shown to the user.
+             *
+             * Attachments that are not shown will not be passed to the in-call UI responsible for
+             * displaying the call to the user.
+             *
+             * If this method is not called on a {@link Builder}, all attachments will be shown,
+             * except pictures, which will only be shown to users if the call is from a contact.
+             *
+             * Setting attachments to show will have no effect if the call screening service does
+             * not belong to the same package as the system dialer (as returned by
+             * {@link TelecomManager#getSystemDialerPackage()}).
+             *
+             * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show.
+             */
+            public @NonNull Builder setCallComposerAttachmentsToShow(
+                    @CallComposerAttachmentType int callComposerAttachmentsToShow) {
+                // If the argument is less than zero (meaning unset), no-op since the conversion
+                // to/from the parcelable version may call with that value.
+                if (callComposerAttachmentsToShow < 0) {
+                    return this;
+                }
+
+                if ((callComposerAttachmentsToShow
+                        & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) {
+                    throw new IllegalArgumentException("Attachment types must match the ones"
+                            + " defined in CallResponse");
+                }
+                mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+                return this;
+            }
+
             public CallResponse build() {
                 return new CallResponse(
                         mShouldDisallowCall,
@@ -336,7 +579,8 @@
                         mShouldSilenceCall,
                         mShouldSkipCallLog,
                         mShouldSkipNotification,
-                        mShouldScreenCallViaAudioProcessing);
+                        mShouldScreenCallViaAudioProcessing,
+                        mCallComposerAttachmentsToShow);
             }
        }
     }
@@ -423,21 +667,12 @@
     public final void respondToCall(@NonNull Call.Details callDetails,
             @NonNull CallResponse response) {
         try {
-            if (response.getDisallowCall()) {
-                mCallScreeningAdapter.disallowCall(
-                        callDetails.getTelecomCallId(),
-                        response.getRejectCall(),
-                        !response.getSkipCallLog(),
-                        !response.getSkipNotification(),
-                        new ComponentName(getPackageName(), getClass().getName()));
-            } else if (response.getSilenceCall()) {
-                mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
-            } else if (response.getShouldScreenCallViaAudioProcessing()) {
-                mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
-            } else {
-                mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
-            }
+            mCallScreeningAdapter.onScreeningResponse(
+                    callDetails.getTelecomCallId(),
+                    new ComponentName(getPackageName(), getClass().getName()),
+                    response.toParcelable());
         } catch (RemoteException e) {
+            Log.e(this, e, "Got remote exception when returning response");
         }
     }
 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 089a948..942a54e 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3390,11 +3390,20 @@
      *                  {@code true}, {@link #onDisconnect()} will be called soon after
      *                  this is called.
      * @param isInContacts Indicates whether the caller is in the user's contacts list.
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { }
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) { }
 
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 170ed3e..966ece3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -759,6 +759,8 @@
 
         @Override
         public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+                CallScreeningService.ParcelableCallResponse callScreeningResponse,
+                boolean isResponseFromSystemDialer,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED);
             try {
@@ -766,7 +768,9 @@
                 args.arg1 = callId;
                 args.arg2 = isBlocked;
                 args.arg3 = isInContacts;
-                args.arg4 = Log.createSubsession();
+                args.arg4 = callScreeningResponse;
+                args.arg5 = isResponseFromSystemDialer;
+                args.arg6 = Log.createSubsession();
                 mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget();
             } finally {
                 Log.endSession();
@@ -1437,12 +1441,16 @@
                 case MSG_ON_CALL_FILTERING_COMPLETED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        Log.continueSession((Session) args.arg4,
+                        Log.continueSession((Session) args.arg6,
                                 SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED);
                         String callId = (String) args.arg1;
                         boolean isBlocked = (boolean) args.arg2;
                         boolean isInContacts = (boolean) args.arg3;
-                        onCallFilteringCompleted(callId, isBlocked, isInContacts);
+                        CallScreeningService.ParcelableCallResponse callScreeningResponse =
+                                (CallScreeningService.ParcelableCallResponse) args.arg4;
+                        boolean isResponseFromSystemDialer = (boolean) args.arg5;
+                        onCallFilteringCompleted(callId, isBlocked, isInContacts,
+                                callScreeningResponse, isResponseFromSystemDialer);
                     } finally {
                         args.recycle();
                         Log.endSession();
@@ -2458,11 +2466,16 @@
         }
     }
 
-    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) {
-        Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts);
+    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+            CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
+        Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId,
+                isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer);
         Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted");
         if (connection != null) {
-            connection.onCallFilteringCompleted(isBlocked, isInContacts);
+            connection.onCallFilteringCompleted(isBlocked, isInContacts,
+                    callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(),
+                    isResponseFromSystemDialer);
         }
     }
 
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index feb2ca5..6c6097a 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1204,15 +1204,25 @@
      * the results of a contacts lookup for the remote party.
      * @param isBlocked Whether call filtering indicates that the call should be blocked
      * @param isInContacts Whether the remote party is in the user's contacts
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
         Log.startSession("RC.oCFC", getActiveOwnerInfo());
         try {
             if (mConnected) {
                 mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts,
+                        callScreeningResponse.toParcelable(), isResponseFromSystemDialer,
                         null /*Session.Info*/);
             }
         } catch (RemoteException ignored) {
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index 83c8f62..0f2e178 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
+import android.telecom.CallScreeningService;
 
 /**
  * Internal remote callback interface for call screening services.
@@ -26,16 +27,6 @@
  * {@hide}
  */
 oneway interface ICallScreeningAdapter {
-    void allowCall(String callId);
-
-    void silenceCall(String callId);
-
-    void screenCallFurther(String callId);
-
-    void disallowCall(
-            String callId,
-            boolean shouldReject,
-            boolean shouldAddToCallLog,
-            boolean shouldShowNotification,
-            in ComponentName componentName);
+    void onScreeningResponse(String callId, in ComponentName componentName,
+            in CallScreeningService.ParcelableCallResponse response);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 301c2bb..7599e18 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
 import android.telecom.PhoneAccountHandle;
@@ -119,7 +120,8 @@
     void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
 
     void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
-            in Session.Info sessionInfo);
+            in CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer, in Session.Info sessionInfo);
 
     void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
 
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 070fd799..09c07d3 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.RcsFeature;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -467,7 +468,7 @@
      * poll on the network unless there are contacts being queried with stale information.
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
      * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
@@ -484,7 +485,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestCapabilities(@NonNull List<Uri> contactNumbers,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
@@ -557,7 +559,7 @@
      *
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
      * enabled or else this operation will fail with
@@ -571,7 +573,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestAvailability(@NonNull Uri contactNumber,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 6fbde50..15d19a4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -125,6 +125,7 @@
     public static final String PROVISIONING_URL_KEY = "provisioningUrl";
     public static final String BANDWIDTH_SOURCE_MODEM_KEY = "modem";
     public static final String BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY = "carrier_config";
+    public static final String BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY = "bandwidth_estimator";
     public static final String RAT_NAME_LTE = "LTE";
     public static final String RAT_NAME_NR_NSA = "NR_NSA";
     public static final String RAT_NAME_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index d79225f..ec12040 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -28,6 +29,7 @@
 import com.android.telephony.Rlog;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -253,13 +255,48 @@
         }
 
         if ((b & 0x0f) <= 0x09) {
-            ret +=  (b & 0xf);
+            ret += (b & 0xf);
         }
 
         return ret;
     }
 
     /**
+     * Encodes a string to be formatted like the EF[ADN] alpha identifier.
+     *
+     * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on
+     * the relevant specs.
+     *
+     * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if
+     * there are characters that are not supported by it.
+     *
+     * @return the encoded string including the prefix byte necessary to identify the encoding.
+     * @see #adnStringFieldToString(byte[], int, int)
+     */
+    @NonNull
+    public static byte[] stringToAdnStringField(@NonNull String alphaTag) {
+        int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0);
+        if (septets != -1) {
+            byte[] ret = new byte[septets];
+            GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length);
+            return ret;
+        }
+
+        // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to
+        // validate that the string contains only valid UCS-2 characters. Since the read path
+        // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine
+        // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's
+        // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction.
+        byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE);
+        byte[] ret = new byte[alphaTagBytes.length + 1];
+        // 0x80 tags the remaining bytes as UCS-2
+        ret[0] = (byte) 0x80;
+        System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length);
+
+        return ret;
+    }
+
+    /**
      * Decodes a string field that's formatted like the EF[ADN] alpha
      * identifier
      *
@@ -309,7 +346,7 @@
                     ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
                 } catch (UnsupportedEncodingException ex) {
                     Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
-                          ex);
+                            ex);
                 }
 
                 if (ret != null) {
@@ -342,7 +379,7 @@
                 len = length - 4;
 
             base = (char) (((data[offset + 2] & 0xFF) << 8) |
-                            (data[offset + 3] & 0xFF));
+                    (data[offset + 3] & 0xFF));
             offset += 4;
             isucs2 = true;
         }
@@ -366,7 +403,7 @@
                     count++;
 
                 ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
-                           offset, count));
+                        offset, count));
 
                 offset += count;
                 len -= count;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2a693eb..1358410 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -66,6 +66,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
@@ -169,6 +171,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -192,6 +195,7 @@
 import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkTestResultParcelable;
+import android.net.OemNetworkPreferences;
 import android.net.ProxyInfo;
 import android.net.QosCallbackException;
 import android.net.QosFilter;
@@ -300,6 +304,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -359,6 +364,7 @@
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
     private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+    private static final int TEST_PACKAGE_UID = 123;
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -418,6 +424,7 @@
     @Mock EthernetManager mEthernetManager;
     @Mock NetworkPolicyManager mNetworkPolicyManager;
     @Mock KeyStore mKeyStore;
+    @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -9422,4 +9429,264 @@
         }
         fail("TOO_MANY_REQUESTS never thrown");
     }
+
+    private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
+            throws PackageManager.NameNotFoundException {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    private void mockHasSystemFeature(@NonNull final String featureName,
+            @NonNull final boolean hasFeature) {
+        when(mPackageManager.hasSystemFeature(eq(featureName)))
+                .thenReturn(hasFeature);
+    }
+
+    private UidRange getNriFirstUidRange(
+            @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+        return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
+    }
+
+    private OemNetworkPreferences createDefaultOemNetworkPreferences(
+            @OemNetworkPreferences.OemNetworkPreference final int preference)
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        return new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, preference)
+                .build();
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError()
+            throws PackageManager.NameNotFoundException {
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        assertThrows(IllegalArgumentException.class,
+                () -> mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest)));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaid()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 3;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertTrue(mRequests.get(2).isRequest());
+        assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities(
+                mRequests.get(2).networkCapabilities));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 2;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+        assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertNotNull(nris);
+        assertEquals(expectedNumOfNris, nris.size());
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCorrectlySetsUids()
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final List<ConnectivityService.NetworkRequestInfo> nris =
+                new ArrayList<>(
+                        mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
+                                pref));
+
+        // Sort by uid to access nris by index
+        nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start));
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start);
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop);
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfAppUids = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfAppUids,
+                nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size());
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(NullPointerException.class,
+                () -> mService.setOemNetworkPreference(
+                        null,
+                        null));
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceFailsForNonAutomotive()
+            throws PackageManager.NameNotFoundException, RemoteException {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(UnsupportedOperationException.class,
+                () -> mService.setOemNetworkPreference(
+                        createDefaultOemNetworkPreferences(networkPref),
+                        mOnSetOemNetworkPreferenceListener));
+    }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7dada9d..1a90fc3 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -26,24 +26,30 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager.VcnStatusCallback;
 import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
+import android.os.ParcelUuid;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.util.UUID;
 import java.util.concurrent.Executor;
 
 public class VcnManagerTest {
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
     private static final Executor INLINE_EXECUTOR = Runnable::run;
 
     private IVcnManagementService mMockVcnManagementService;
     private VcnUnderlyingNetworkPolicyListener mMockPolicyListener;
+    private VcnStatusCallback mMockStatusCallback;
 
     private Context mContext;
     private VcnManager mVcnManager;
@@ -52,6 +58,7 @@
     public void setUp() {
         mMockVcnManagementService = mock(IVcnManagementService.class);
         mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class);
+        mMockStatusCallback = mock(VcnStatusCallback.class);
 
         mContext = getContext();
         mVcnManager = new VcnManager(mContext, mMockVcnManagementService);
@@ -132,4 +139,60 @@
     public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception {
         mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null);
     }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        ArgumentCaptor<IVcnStatusCallback> captor =
+                ArgumentCaptor.forClass(IVcnStatusCallback.class);
+        verify(mMockVcnManagementService)
+                .registerVcnStatusCallback(eq(SUB_GROUP), captor.capture(), any());
+
+        IVcnStatusCallback callbackWrapper = captor.getValue();
+        callbackWrapper.onEnteredSafeMode();
+        verify(mMockStatusCallback).onEnteredSafeMode();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception {
+        mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullExecutor() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verify(mMockVcnManagementService).unregisterVcnStatusCallback(any());
+    }
+
+    @Test
+    public void testUnregisterUnknownVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verifyNoMoreInteractions(mMockVcnManagementService);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testUnregisterNullVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(null);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index c290bff..124ec30 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -26,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -42,9 +43,11 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -52,6 +55,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkCapabilities.Transport;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
@@ -70,7 +74,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.internal.util.LocationPermissionChecker;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
+import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -147,14 +153,17 @@
             mock(PersistableBundleUtils.LockingReadWriteHelper.class);
     private final TelephonySubscriptionTracker mSubscriptionTracker =
             mock(TelephonySubscriptionTracker.class);
+    private final LocationPermissionChecker mLocationPermissionChecker =
+            mock(LocationPermissionChecker.class);
 
-    private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
-            ArgumentCaptor.forClass(VcnSafemodeCallback.class);
+    private final ArgumentCaptor<VcnSafeModeCallback> mSafeModeCallbackCaptor =
+            ArgumentCaptor.forClass(VcnSafeModeCallback.class);
 
     private final VcnManagementService mVcnMgmtSvc;
 
     private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
             mock(IVcnUnderlyingNetworkPolicyListener.class);
+    private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class);
     private final IBinder mMockIBinder = mock(IBinder.class);
 
     public VcnManagementServiceTest() throws Exception {
@@ -171,6 +180,7 @@
 
         doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
+        doReturn(mMockContext).when(mVcnContext).getContext();
         doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
         doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
         doReturn(mVcnContext)
@@ -188,6 +198,9 @@
         doReturn(mConfigReadWriteHelper)
                 .when(mMockDeps)
                 .newPersistableBundleLockingReadWriteHelper(any());
+        doReturn(mLocationPermissionChecker)
+                .when(mMockDeps)
+                .newLocationPermissionChecker(eq(mMockContext));
 
         // Setup VCN instance generation
         doAnswer((invocation) -> {
@@ -206,6 +219,7 @@
         mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
 
         doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();
+        doReturn(mMockIBinder).when(mMockStatusCallback).asBinder();
 
         // Make sure the profiles are loaded.
         mTestLooper.dispatchAll();
@@ -707,24 +721,138 @@
         verify(mMockPolicyListener).onPolicyChanged();
     }
 
-    @Test
-    public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
-        TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+    private void verifyVcnSafeModeCallback(
+            @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
+            throws Exception {
         verify(mMockDeps)
                 .newVcn(
                         eq(mVcnContext),
-                        eq(TEST_UUID_1),
+                        eq(subGroup),
                         eq(TEST_VCN_CONFIG),
                         eq(snapshot),
-                        mSafemodeCallbackCaptor.capture());
+                        mSafeModeCallbackCaptor.capture());
 
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
-        safemodeCallback.onEnteredSafemode();
+        VcnSafeModeCallback safeModeCallback = mSafeModeCallbackCaptor.getValue();
+        safeModeCallback.onEnteredSafeMode();
 
-        assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
         verify(mMockPolicyListener).onPolicyChanged();
     }
+
+    @Test
+    public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+
+        verifyVcnSafeModeCallback(TEST_UUID_1, snapshot);
+    }
+
+    private void triggerVcnStatusCallbackOnEnteredSafeMode(
+            @NonNull ParcelUuid subGroup,
+            @NonNull String pkgName,
+            int uid,
+            boolean hasPermissionsforSubGroup,
+            boolean hasLocationPermission)
+            throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+
+        doReturn(hasPermissionsforSubGroup)
+                .when(snapshot)
+                .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
+
+        doReturn(hasLocationPermission)
+                .when(mLocationPermissionChecker)
+                .checkLocationPermission(eq(pkgName), any(), eq(uid), any());
+
+        mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
+
+        // Trigger systemReady() to set up LocationPermissionChecker
+        mVcnMgmtSvc.systemReady();
+
+        verifyVcnSafeModeCallback(subGroup, snapshot);
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, times(1)).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                false /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                false /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        assertNotNull(cbInfo);
+        assertEquals(TEST_UUID_1, cbInfo.mSubGroup);
+        assertEquals(mMockStatusCallback, cbInfo.mCallback);
+        assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
+        assertEquals(TEST_UID, cbInfo.mUid);
+        verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackDuplicate() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+        verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testRegisterVcnStatusCallbackInvalidPackage() {
+        doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallbackNeverRegistered() {
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index e715480..b62a0b8 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -75,8 +75,8 @@
     }
 
     @Test
-    public void testEnterStateDoesNotCancelSafemodeAlarm() {
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    public void testEnterStateDoesNotCancelSafeModeAlarm() {
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -144,7 +144,7 @@
     @Test
     public void testChildOpenedRegistersNetwork() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         final VcnChildSessionConfiguration mMockChildSessionConfig =
                 mock(VcnChildSessionConfiguration.class);
@@ -188,17 +188,17 @@
             assertTrue(nc.hasCapability(cap));
         }
 
-        // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+        // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is
         // canceled
         mGatewayConnection.mNetworkAgent.onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
-        verify(mSafemodeTimeoutAlarm).cancel();
+        verify(mSafeModeTimeoutAlarm).cancel();
     }
 
     @Test
     public void testChildSessionClosedTriggersDisconnect() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         getChildSessionCallback().onClosed();
         mTestLooper.dispatchAll();
@@ -206,14 +206,14 @@
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
-        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
-        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+        // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
     }
 
     @Test
     public void testIkeSessionClosedTriggersDisconnect() throws Exception {
         // Verify scheduled but not canceled when entering ConnectedState
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
 
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
@@ -221,7 +221,7 @@
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
 
-        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
-        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+        // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 07282c9..17ae19e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 49ce54d..9ea641f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -79,7 +79,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
-        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -100,6 +100,6 @@
 
         assertNull(mGatewayConnection.getCurrentState());
         verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
-        verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+        verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 22eab2a..7385204 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -82,7 +82,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 6c26075..5b0850b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -93,7 +93,7 @@
     }
 
     @Test
-    public void testSafemodeTimeoutNotifiesCallback() {
-        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
+    public void testSafeModeTimeoutNotifiesCallback() {
+        verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index ac9ec06..a660735 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -110,7 +110,7 @@
     @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
     @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
     @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
-    @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
+    @NonNull protected final WakeupMessage mSafeModeTimeoutAlarm;
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
@@ -131,7 +131,7 @@
         mTeardownTimeoutAlarm = mock(WakeupMessage.class);
         mDisconnectRequestAlarm = mock(WakeupMessage.class);
         mRetryTimeoutAlarm = mock(WakeupMessage.class);
-        mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
+        mSafeModeTimeoutAlarm = mock(WakeupMessage.class);
 
         mIpSecSvc = mock(IpSecService.class);
         setupIpSecManager(mContext, mIpSecSvc);
@@ -154,7 +154,7 @@
         setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
         setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
         setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
-        setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+        setUpWakeupMessage(mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
 
         doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
     }
@@ -259,23 +259,23 @@
                 expectCanceled);
     }
 
-    protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+    protected Runnable verifySafeModeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
         return verifyWakeupMessageSetUpAndGetCallback(
                 VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
-                mSafemodeTimeoutAlarm,
+                mSafeModeTimeoutAlarm,
                 TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
                 expectCanceled);
     }
 
-    protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
-        // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+    protected void verifySafeModeTimeoutNotifiesCallback(@NonNull State expectedState) {
+        // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
         // state)
         final Runnable delayedEvent =
-                verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+                verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
         delayedEvent.run();
         mTestLooper.dispatchAll();
 
-        verify(mGatewayStatusCallback).onEnteredSafemode();
+        verify(mGatewayStatusCallback).onEnteredSafeMode();
         assertEquals(expectedState, mGatewayConnection.getCurrentState());
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 66cbf84..8e142c0 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -34,7 +34,7 @@
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
@@ -56,7 +56,7 @@
     private VcnContext mVcnContext;
     private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     private VcnNetworkProvider mVcnNetworkProvider;
-    private VcnSafemodeCallback mVcnSafemodeCallback;
+    private VcnSafeModeCallback mVcnSafeModeCallback;
     private Vcn.Dependencies mDeps;
 
     private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor;
@@ -72,7 +72,7 @@
         mVcnContext = mock(VcnContext.class);
         mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
         mVcnNetworkProvider = mock(VcnNetworkProvider.class);
-        mVcnSafemodeCallback = mock(VcnSafemodeCallback.class);
+        mVcnSafeModeCallback = mock(VcnSafeModeCallback.class);
         mDeps = mock(Vcn.Dependencies.class);
 
         mTestLooper = new TestLooper();
@@ -104,7 +104,7 @@
                         TEST_SUB_GROUP,
                         mConfig,
                         mSubscriptionSnapshot,
-                        mVcnSafemodeCallback,
+                        mVcnSafeModeCallback,
                         mDeps);
     }
 
@@ -148,7 +148,7 @@
     }
 
     @Test
-    public void testGatewayEnteringSafemodeNotifiesVcn() {
+    public void testGatewayEnteringSafeModeNotifiesVcn() {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
         for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, capability);
@@ -168,16 +168,17 @@
                         any(),
                         mGatewayStatusCallbackCaptor.capture());
 
-        // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
+        // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
         // all Gateways
         final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
-        statusCallback.onEnteredSafemode();
+        statusCallback.onEnteredSafeMode();
         mTestLooper.dispatchAll();
 
+        assertFalse(mVcn.isActive());
         for (final VcnGatewayConnection gatewayConnection : gatewayConnections) {
             verify(gatewayConnection).teardownAsynchronously();
         }
         verify(mVcnNetworkProvider).unregisterListener(requestListener);
-        verify(mVcnSafemodeCallback).onEnteredSafemode();
+        verify(mVcnSafeModeCallback).onEnteredSafeMode();
     }
 }