Merge "New API: DevicePolicyManager.listForegroundAffiliatedUsers()" into sc-dev
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 925f332..5f9743a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12207,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);
@@ -12429,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";
@@ -12535,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
@@ -21635,6 +21641,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();
@@ -21743,6 +21750,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();
   }
@@ -26795,6 +26808,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 {
@@ -31587,6 +31640,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();
@@ -37038,6 +37092,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();
@@ -37072,6 +37127,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);
@@ -37169,6 +37225,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
@@ -39208,16 +39265,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 84e6f22..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";
@@ -345,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
   }
@@ -8959,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[]>);
@@ -8990,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);
@@ -10756,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);
@@ -10933,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);
   }
 
@@ -13446,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
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/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/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
new file mode 100644
index 0000000..a7386718
--- /dev/null
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+/** @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/permission/AdminPermissionControlParams.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl
new file mode 100644
index 0000000..35e63d4
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+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/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index f3a7856..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -262,7 +262,7 @@
         @WorkerThread
         public static int getEncodedNameLength(
                 @NonNull ContentResolver resolver, @NonNull String name) {
-            name = Objects.requireNonNull(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)) {
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fe4106d..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"
@@ -2463,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 -->
     <!-- ================================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd048f3..cc23f77 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>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1ebcf77..e43aa3d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3138,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/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 385d465..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -445,6 +445,7 @@
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
         "hwui/Bitmap.cpp",
+        "hwui/BlurDrawLooper.cpp",
         "hwui/Canvas.cpp",
         "hwui/ImageDecoder.cpp",
         "hwui/MinikinSkia.cpp",
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/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/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/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index dbd25ce..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" />
 
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/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
index 739738a..9ea7aa3 100644
--- a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
@@ -21,7 +21,7 @@
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
-        android:id="@+id/people_tile"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <include layout="@layout/punctuation_layout"/>
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 bb4a20e..3300495 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -21,7 +21,7 @@
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
-        android:id="@+id/people_tile"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <RelativeLayout
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/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/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index d65d169..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;
 
 /**
@@ -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,7 +158,7 @@
          * Build the KeyguardIndication.
          */
         public KeyguardIndication build() {
-            if (mMessage == null && mIcon == null) {
+            if (TextUtils.isEmpty(mMessage) && mIcon == null) {
                 throw new IllegalStateException("message or icon must be set");
             }
             if (mTextColor == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2e599de..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,9 +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())
-                    || newIndication.getIcon() != null);
+        final boolean hasNewIndication = newIndication != null;
         if (!hasNewIndication) {
             mIndicationMessages.remove(type);
             mIndicationQueue.removeIf(x -> x == type);
@@ -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/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/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/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/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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5de5686..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;
@@ -533,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";
@@ -7891,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
@@ -12662,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);
@@ -14177,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()) {
@@ -14201,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();
         }
     }
@@ -14357,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();
@@ -14373,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();
@@ -16727,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/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/telecomm/java/android/telecom/CallScreeningService.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl
new file mode 100644
index 0000000..87b5138
--- /dev/null
+++ b/telecomm/java/android/telecom/CallScreeningService.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+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/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();
     }
 }