Merge "Have the DataConnectionStat started from BSS" into sc-dev
diff --git a/Android.bp b/Android.bp
index 7099291..9374c01 100644
--- a/Android.bp
+++ b/Android.bp
@@ -581,6 +581,7 @@
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
+        "android.hardware.vibrator-V2-java",
         "android.security.apc-java",
         "android.security.authorization-java",
         "android.security.usermanager-java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
index 34ba753b3..862d8b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
@@ -25,7 +25,9 @@
 public interface JobCompletedListener {
     /**
      * Callback for when a job is completed.
+     *
+     * @param stopReason      The stop reason provided to JobParameters.
      * @param needsReschedule Whether the implementing class should reschedule this job.
      */
-    void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule);
+    void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule);
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 8bb03e9..515cb74 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1745,11 +1745,12 @@
      * A job just finished executing. We fetch the
      * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
      * whether we want to reschedule we re-add it to the controllers.
-     * @param jobStatus Completed job.
+     *
+     * @param jobStatus       Completed job.
      * @param needsReschedule Whether the implementing class should reschedule this job.
      */
     @Override
-    public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
+    public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) {
         if (DEBUG) {
             Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
         }
@@ -1767,6 +1768,11 @@
         // we stop it.
         final JobStatus rescheduledJob = needsReschedule
                 ? getRescheduleJobForFailureLocked(jobStatus) : null;
+        if (rescheduledJob != null
+                && (stopReason == JobParameters.REASON_TIMEOUT
+                || stopReason == JobParameters.REASON_PREEMPT)) {
+            rescheduledJob.disallowRunInBatterySaverAndDoze();
+        }
 
         // Do not write back immediately if this is a periodic job. The job may get lost if system
         // shuts down before it is added back.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index c9a1843..9673449 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -381,8 +381,8 @@
     }
 
     boolean isWithinExecutionGuaranteeTime() {
-        return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis
-                < sElapsedRealtimeClock.millis();
+        return sElapsedRealtimeClock.millis()
+                < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
     }
 
     @GuardedBy("mLock")
@@ -850,11 +850,12 @@
         }
         applyStoppedReasonLocked(reason);
         completedJob = mRunningJob;
-        mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
+        final int stopReason = mParams.getStopReason();
+        mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.getSourceUid(), null, completedJob.getBatteryName(),
                 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
-                mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(),
+                stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
                 completedJob.hasChargingConstraint(),
                 completedJob.hasBatteryNotLowConstraint(),
                 completedJob.hasStorageNotLowConstraint(),
@@ -865,7 +866,7 @@
                 completedJob.hasContentTriggerConstraint());
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
-                    mParams.getStopReason());
+                    stopReason);
         } catch (RemoteException e) {
             // Whatever.
         }
@@ -884,7 +885,7 @@
         service = null;
         mAvailable = true;
         removeOpTimeOutLocked();
-        mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
+        mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index eaf8f4d..aa8d98c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -954,7 +954,7 @@
                     appBucket, sourceTag,
                     elapsedRuntimes.first, elapsedRuntimes.second,
                     lastSuccessfulRunTime, lastFailedRunTime,
-                    (rtcIsGood) ? null : rtcRuntimes, internalFlags);
+                    (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
             return js;
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 192f5e6..79ef321 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -64,7 +64,7 @@
      * when the app is temp whitelisted or in the foreground.
      */
     private final ArraySet<JobStatus> mAllowInIdleJobs;
-    private final SparseBooleanArray mForegroundUids;
+    private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
     private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
     private final DeviceIdleJobsDelayHandler mHandler;
     private final PowerManager mPowerManager;
@@ -77,7 +77,6 @@
     private int[] mDeviceIdleWhitelistAppIds;
     private int[] mPowerSaveTempWhitelistAppIds;
 
-    // onReceive
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -120,6 +119,10 @@
         }
     };
 
+    /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */
+    private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) ->
+            jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid());
+
     public DeviceIdleJobsController(JobSchedulerService service) {
         super(service);
 
@@ -133,7 +136,6 @@
                 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
         mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
         mAllowInIdleJobs = new ArraySet<>();
-        mForegroundUids = new SparseBooleanArray();
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -156,14 +158,9 @@
                 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
                 mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
             } else {
-                // When coming out of doze, process all foreground uids immediately, while others
-                // will be processed after a delay of 3 seconds.
-                for (int i = 0; i < mForegroundUids.size(); i++) {
-                    if (mForegroundUids.valueAt(i)) {
-                        mService.getJobStore().forEachJobForSourceUid(
-                                mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
-                    }
-                }
+                // When coming out of doze, process all foreground uids and EJs immediately,
+                // while others will be processed after a delay of 3 seconds.
+                mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor);
                 mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 5bdeb38..bad8dc1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -91,6 +91,12 @@
     static final int CONSTRAINT_WITHIN_EXPEDITED_QUOTA = 1 << 23;    // Implicit constraint
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
 
+    // The following set of dynamic constraints are for specific use cases (as explained in their
+    // relative naming and comments). Right now, they apply different constraints, which is fine,
+    // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
+    // set may accidentally remove a constraint applied by another dynamic set.
+    // TODO: properly handle overlapping dynamic constraint sets
+
     /**
      * The additional set of dynamic constraints that must be met if the job's effective bucket is
      * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't
@@ -103,6 +109,13 @@
                     | CONSTRAINT_IDLE;
 
     /**
+     * The additional set of dynamic constraints that must be met if this is an expedited job that
+     * had a long enough run while the device was Dozing or in battery saver.
+     */
+    private static final int DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS =
+            CONSTRAINT_DEVICE_NOT_DOZING | CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+    /**
      * Standard media URIs that contain the media files that might be important to the user.
      * @see #mHasMediaBackupExemption
      */
@@ -426,7 +439,8 @@
     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
             int sourceUserId, int standbyBucket, String tag, int numFailures,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
-            long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
+            long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
+            int dynamicConstraints) {
         this.job = job;
         this.callingUid = callingUid;
         this.standbyBucket = standbyBucket;
@@ -487,6 +501,7 @@
         }
         this.requiredConstraints = requiredConstraints;
         mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
+        addDynamicConstraints(dynamicConstraints);
         mReadyNotDozing = canRunInDoze();
         if (standbyBucket == RESTRICTED_INDEX) {
             addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
@@ -521,7 +536,7 @@
                 jobStatus.getSourceTag(), jobStatus.getNumFailures(),
                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
-                jobStatus.getInternalFlags());
+                jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
         mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
         if (jobStatus.mPersistedUtcTimes != null) {
             if (DEBUG) {
@@ -543,12 +558,12 @@
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime,
             Pair<Long, Long> persistedExecutionTimesUTC,
-            int innerFlags) {
+            int innerFlags, int dynamicConstraints) {
         this(job, callingUid, sourcePkgName, sourceUserId,
                 standbyBucket,
                 sourceTag, 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
-                lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
+                lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
 
         // Only during initial inflation do we record the UTC-timebase execution bounds
         // read from the persistent store.  If we ever have to recreate the JobStatus on
@@ -572,7 +587,8 @@
                 rescheduling.getStandbyBucket(),
                 rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
                 newLatestRuntimeElapsedMillis,
-                lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
+                lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
+                rescheduling.mDynamicConstraints);
     }
 
     /**
@@ -609,7 +625,7 @@
                 standbyBucket, tag, 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
-                /*innerFlags=*/ 0);
+                /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
     }
 
     public void enqueueWorkLocked(JobWorkItem work) {
@@ -1083,12 +1099,15 @@
      * in Doze.
      */
     public boolean canRunInDoze() {
-        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob();
+        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+                || (shouldTreatAsExpeditedJob()
+                && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
     }
 
     boolean canRunInBatterySaver() {
         return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
-                || shouldTreatAsExpeditedJob();
+                || (shouldTreatAsExpeditedJob()
+                && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
     }
 
     boolean shouldIgnoreNetworkBlocking() {
@@ -1245,6 +1264,14 @@
     }
 
     /**
+     * Add additional constraints to prevent this job from running when doze or battery saver are
+     * active.
+     */
+    public void disallowRunInBatterySaverAndDoze() {
+        addDynamicConstraints(DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS);
+    }
+
+    /**
      * Indicates that this job cannot run without the specified constraints. This is evaluated
      * separately from the job's explicitly requested constraints and MUST be satisfied before
      * the job can run if the app doesn't have quota.
diff --git a/core/api/current.txt b/core/api/current.txt
index 8ef2230..a06f994 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12948,6 +12948,27 @@
 
 }
 
+package android.content.pm.verify.domain {
+
+  public final class DomainVerificationManager {
+    method @Nullable public android.content.pm.verify.domain.DomainVerificationUserState getDomainVerificationUserState(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+  }
+
+  public final class DomainVerificationUserState implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+    method @NonNull public String getPackageName();
+    method @NonNull public android.os.UserHandle getUser();
+    method @NonNull public boolean isLinkHandlingAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserState> CREATOR;
+    field public static final int DOMAIN_STATE_NONE = 0; // 0x0
+    field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
+    field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
+  }
+
+}
+
 package android.content.res {
 
   public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -26727,7 +26748,22 @@
 
   public class VcnManager {
     method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
     method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+    method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+    field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
+    field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
+    field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
+    field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
+    field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
+  }
+
+  public abstract static class VcnManager.VcnStatusCallback {
+    ctor public VcnManager.VcnStatusCallback();
+    method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable);
+    method public abstract void onVcnStatusChanged(int);
   }
 
 }
@@ -50979,6 +51015,7 @@
     method public void onDisplayHashError(int);
     method public void onDisplayHashResult(@NonNull android.view.displayhash.DisplayHash);
     field public static final int DISPLAY_HASH_ERROR_INVALID_BOUNDS = -2; // 0xfffffffe
+    field public static final int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; // 0xfffffffb
     field public static final int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3; // 0xfffffffd
     field public static final int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; // 0xfffffffc
     field public static final int DISPLAY_HASH_ERROR_UNKNOWN = -1; // 0xffffffff
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8f067c2..f017243 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2789,13 +2789,12 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
   }
 
-  public interface DomainVerificationManager {
+  public final class DomainVerificationManager {
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
     method public static boolean isStateModifiable(int);
     method public static boolean isStateVerified(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
     method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -2812,18 +2811,8 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR;
   }
 
-  public final class DomainVerificationUserSelection implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+  public final class DomainVerificationUserState implements android.os.Parcelable {
     method @NonNull public java.util.UUID getIdentifier();
-    method @NonNull public String getPackageName();
-    method @NonNull public android.os.UserHandle getUser();
-    method @NonNull public boolean isLinkHandlingAllowed();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
-    field public static final int DOMAIN_STATE_NONE = 0; // 0x0
-    field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
-    field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
   }
 
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e16e40b..43c14a9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -71,7 +71,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutManager;
 import android.content.pm.verify.domain.DomainVerificationManager;
-import android.content.pm.verify.domain.DomainVerificationManagerImpl;
 import android.content.pm.verify.domain.IDomainVerificationManager;
 import android.content.res.Resources;
 import android.content.rollback.RollbackManagerFrameworkInitializer;
@@ -1422,7 +1421,6 @@
                     }
                 });
 
-        // TODO(b/159952358): Only register this service for the domain verification agent?
         registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class,
                 new CachedServiceFetcher<DomainVerificationManager>() {
                     @Override
@@ -1432,7 +1430,7 @@
                                 Context.DOMAIN_VERIFICATION_SERVICE);
                         IDomainVerificationManager service =
                                 IDomainVerificationManager.Stub.asInterface(binder);
-                        return new DomainVerificationManagerImpl(context, service);
+                        return new DomainVerificationManager(context, service);
                     }
                 });
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b919bfc..0635bd0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2997,6 +2997,7 @@
      */
     // TODO(b/173541467): should it throw SecurityException if caller is not admin?
     public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        throwIfParentInstance("isSafeOperation");
         if (mService == null) return false;
 
         try {
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 22492cc..94a4fde0 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -403,7 +403,7 @@
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
                 mOperationType);
-        if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
+        if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) {
             return;
         }
 
@@ -911,7 +911,7 @@
         }
 
         FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
-        if (!bs.isFullBackupContentEnabled()) {
+        if (!bs.isFullRestoreEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
                         "onRestoreFile \"" + destination.getCanonicalPath()
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 829b6cd..9b543b5 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -99,6 +99,8 @@
     public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer";
     public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION =
             "fakeClientSideEncryption";
+    private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES
+            = "disableIfNoEncryptionCapabilities";
 
     /**
      * When  this change is enabled, include / exclude rules specified via
@@ -307,6 +309,10 @@
         // lazy initialized, only when needed
         private StorageVolume[] mVolumes = null;
 
+        // Properties the transport must have (e.g. encryption) for the operation to go ahead.
+        @Nullable private Integer mRequiredTransportFlags;
+        @Nullable private Boolean mIsUsingNewScheme;
+
         /**
          * Parse out the semantic domains into the correct physical location.
          */
@@ -453,6 +459,35 @@
             }
         }
 
+        boolean isFullBackupEnabled(int transportFlags) {
+            try {
+                if (isUsingNewScheme()) {
+                    int requiredTransportFlags = getRequiredTransportFlags();
+                    // All bits that are set in requiredTransportFlags must be set in
+                    // transportFlags.
+                    return (transportFlags & requiredTransportFlags) == requiredTransportFlags;
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.w(TAG, "Failed to interpret the backup scheme: " + e);
+                return false;
+            }
+
+            return isFullBackupContentEnabled();
+        }
+
+        boolean isFullRestoreEnabled() {
+            try {
+                if (isUsingNewScheme()) {
+                    return true;
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.w(TAG, "Failed to interpret the backup scheme: " + e);
+                return false;
+            }
+
+            return isFullBackupContentEnabled();
+        }
+
         boolean isFullBackupContentEnabled() {
             if (mFullBackupContent < 0) {
                 // android:fullBackupContent="false", bail.
@@ -491,10 +526,30 @@
             return mExcludes;
         }
 
+        private synchronized int getRequiredTransportFlags()
+                throws IOException, XmlPullParserException {
+            if (mRequiredTransportFlags == null) {
+                maybeParseBackupSchemeLocked();
+            }
+
+            return mRequiredTransportFlags;
+        }
+
+        private synchronized boolean isUsingNewScheme()
+                throws IOException, XmlPullParserException {
+            if (mIsUsingNewScheme == null) {
+                maybeParseBackupSchemeLocked();
+            }
+
+            return mIsUsingNewScheme;
+        }
+
         private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
             // This not being null is how we know that we've tried to parse the xml already.
             mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>();
             mExcludes = new ArraySet<PathWithRequiredFlags>();
+            mRequiredTransportFlags = 0;
+            mIsUsingNewScheme = false;
 
             if (mFullBackupContent == 0 && mDataExtractionRules == 0) {
                 // No scheme specified via either new or legacy config, will copy everything.
@@ -535,12 +590,14 @@
                 }
                 if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) {
                     // Found configuration in the new config, we will use it.
+                    mIsUsingNewScheme = true;
                     return;
                 }
             }
 
             if (operationType == OperationType.MIGRATION
                     && CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) {
+                mIsUsingNewScheme = true;
                 return;
             }
 
@@ -584,13 +641,24 @@
                     continue;
                 }
 
-                // TODO(b/180523028): Parse required attributes for rules (e.g. encryption).
+                parseRequiredTransportFlags(parser, configSection);
                 parseRules(parser, excludes, includes, Optional.of(0), configSection);
             }
 
             logParsingResults(excludes, includes);
         }
 
+        private void parseRequiredTransportFlags(XmlPullParser parser,
+                @ConfigSection String configSection) {
+            if (ConfigSection.CLOUD_BACKUP.equals(configSection)) {
+                String encryptionAttribute = parser.getAttributeValue(/* namespace */ null,
+                        FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES);
+                if ("true".equals(encryptionAttribute)) {
+                    mRequiredTransportFlags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+                }
+            }
+        }
+
         @VisibleForTesting
         public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
                                                    Set<PathWithRequiredFlags> excludes,
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 102c98f..17bdd42 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -60,6 +60,10 @@
     /**
      * Device profile: watch.
      *
+     * If specified, the current request may have a modified UI to highlight that the device being
+     * set up is a specific kind of device, and some extra permissions may be granted to the app
+     * as a result.
+     *
      * @see AssociationRequest.Builder#setDeviceProfile
      */
     public static final String DEVICE_PROFILE_WATCH =
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7b62f3b..d79b66c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -7017,7 +7017,7 @@
      * domain to an application, use
      * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
      * passing in all of the domains returned inside
-     * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}.
+     * {@link DomainVerificationManager#getDomainVerificationUserState(String)}.
      *
      * @hide
      */
diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java
index b050f5d..5bf2c09 100644
--- a/core/java/android/content/pm/verify/domain/DomainOwner.java
+++ b/core/java/android/content/pm/verify/domain/DomainOwner.java
@@ -66,16 +66,7 @@
      * @param packageName
      *   Package name of that owns the domain.
      * @param overrideable
-     *   Whether or not this owner can be automatically overridden. If all owners for a domain are
-     *   overrideable, then calling
-     *   {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
-     *   Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
-     *   of the owners are non-overrideable, then
-     *   {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
-     *   boolean)} must be called with false to disable all of the other owners before this domain can
-     *   be taken by a new owner through
-     *   {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
-     *   Set, boolean)}.
+     *   Whether or not this owner can be automatically overridden.
      */
     @DataClass.Generated.Member
     public DomainOwner(
@@ -98,16 +89,9 @@
     }
 
     /**
-     * Whether or not this owner can be automatically overridden. If all owners for a domain are
-     * overrideable, then calling
-     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
-     * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
-     * of the owners are non-overrideable, then
-     * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
-     * boolean)} must be called with false to disable all of the other owners before this domain can
-     * be taken by a new owner through
-     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
-     * Set, boolean)}.
+     * Whether or not this owner can be automatically overridden.
+     *
+     * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
      */
     @DataClass.Generated.Member
     public boolean isOverrideable() {
@@ -205,7 +189,7 @@
     };
 
     @DataClass.Generated(
-            time = 1614119379978L,
+            time = 1614721802044L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java",
             inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final  boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)")
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
index 8095875..7c335b1 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -94,7 +94,7 @@
 
     private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
         return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
-                DomainVerificationUserSelection.class.getClassLoader());
+                DomainVerificationUserState.class.getClassLoader());
     }
 
 
@@ -105,8 +105,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
-    // /DomainVerificationInfo.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -321,7 +320,7 @@
     };
 
     @DataClass.Generated(
-            time = 1613002530369L,
+            time = 1614721812023L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
             inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate  void parcelHostToStateMap(android.os.Parcel,int)\nprivate  java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 11402af..f7c81bcf 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -25,6 +25,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 
 import java.util.List;
@@ -32,55 +34,63 @@
 import java.util.UUID;
 
 /**
- * System service to access the domain verification APIs.
+ * System service to access domain verification APIs.
  *
- * Allows the approved domain verification
- * agent on the device (the sole holder of
- * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status
- * of domains declared by applications in their AndroidManifest.xml, to allow them to open those
- * links inside the app when selected by the user. This is done through querying
- * {@link #getDomainVerificationInfo(String)} and calling
- * {@link #setDomainVerificationStatus(UUID, Set, int)}.
- *
- * Also allows the domain preference settings (holder of
- * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the
- * preferences of the user, when they have chosen to explicitly allow an application to open links.
- * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling
- * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
- * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
- *
- * @hide
+ * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
  */
-@SystemApi
 @SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
-public interface DomainVerificationManager {
+public final class DomainVerificationManager {
 
     /**
-     * Extra field name for a {@link DomainVerificationRequest} for the requested packages.
-     * Passed to an the domain verification agent that handles
+     * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed
+     * to an the domain verification agent that handles
      * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+     *
+     * @hide
      */
-    String EXTRA_VERIFICATION_REQUEST =
+    @SystemApi
+    public static final String EXTRA_VERIFICATION_REQUEST =
             "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
 
     /**
      * No response has been recorded by either the system or any verification agent.
+     *
+     * @hide
      */
-    int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
-
-    /** The verification agent has explicitly verified the domain at some point. */
-    int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+    @SystemApi
+    public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
 
     /**
-     * The first available custom response code. This and any greater integer, along with
-     * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values
-     * will be treated as if the domain is unverified.
+     * The verification agent has explicitly verified the domain at some point.
+     *
+     * @hide
      */
-    int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+    @SystemApi
+    public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
 
-    /** @hide */
+    /**
+     * The first available custom response code. This and any greater integer, along with {@link
+     * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be
+     * treated as if the domain is unverified.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int STATE_FIRST_VERIFIER_DEFINED =
+            DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+    /**
+     * @hide
+     */
     @NonNull
-    static String stateToDebugString(@DomainVerificationState.State int state) {
+    public static String stateToDebugString(@DomainVerificationState.State int state) {
         switch (state) {
             case DomainVerificationState.STATE_NO_RESPONSE:
                 return "none";
@@ -104,10 +114,13 @@
     }
 
     /**
-     * Checks if a state considers the corresponding domain to be successfully verified. The
-     * domain verification agent may use this to determine whether or not to re-verify a domain.
+     * Checks if a state considers the corresponding domain to be successfully verified. The domain
+     * verification agent may use this to determine whether or not to re-verify a domain.
+     *
+     * @hide
      */
-    static boolean isStateVerified(@DomainVerificationState.State int state) {
+    @SystemApi
+    public static boolean isStateVerified(@DomainVerificationState.State int state) {
         switch (state) {
             case DomainVerificationState.STATE_SUCCESS:
             case DomainVerificationState.STATE_APPROVED:
@@ -126,10 +139,13 @@
     /**
      * Checks if a state is modifiable by the domain verification agent. This is useful as the
      * platform may add new state codes in newer versions, and older verification agents can use
-     * this method to determine if a state can be changed without having to be aware of what the
-     * new state means.
+     * this method to determine if a state can be changed without having to be aware of what the new
+     * state means.
+     *
+     * @hide
      */
-    static boolean isStateModifiable(@DomainVerificationState.State int state) {
+    @SystemApi
+    public static boolean isStateModifiable(@DomainVerificationState.State int state) {
         switch (state) {
             case DomainVerificationState.STATE_NO_RESPONSE:
             case DomainVerificationState.STATE_SUCCESS:
@@ -147,11 +163,12 @@
     }
 
     /**
-     * For determine re-verify policy. This is hidden from the domain verification agent so that
-     * no behavior is made based on the result.
+     * For determine re-verify policy. This is hidden from the domain verification agent so that no
+     * behavior is made based on the result.
+     *
      * @hide
      */
-    static boolean isStateDefault(@DomainVerificationState.State int state) {
+    public static boolean isStateDefault(@DomainVerificationState.State int state) {
         switch (state) {
             case DomainVerificationState.STATE_NO_RESPONSE:
             case DomainVerificationState.STATE_MIGRATED:
@@ -168,14 +185,72 @@
     }
 
     /**
+     * @hide
+     */
+    public static final int ERROR_INVALID_DOMAIN_SET = 1;
+    /**
+     * @hide
+     */
+    public static final int ERROR_NAME_NOT_FOUND = 2;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"ERROR_"}, value = {
+            ERROR_INVALID_DOMAIN_SET,
+            ERROR_NAME_NOT_FOUND,
+    })
+    private @interface Error {
+    }
+
+    private final Context mContext;
+
+    private final IDomainVerificationManager mDomainVerificationManager;
+
+
+    /**
+     * System service to access the domain verification APIs.
+     * <p>
+     * Allows the approved domain verification agent on the device (the sole holder of {@link
+     * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of
+     * domains declared by applications in their AndroidManifest.xml, to allow them to open those
+     * links inside the app when selected by the user. This is done through querying {@link
+     * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID,
+     * Set, int)}.
+     * <p>
+     * Also allows the domain preference settings (holder of
+     * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION})
+     * to update the preferences of the user, when they have chosen to explicitly allow an
+     * application to open links. This is done through querying
+     * {@link #getDomainVerificationUserState(String)} and calling
+     * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+     * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+     *
+     * @hide
+     */
+    public DomainVerificationManager(Context context,
+            IDomainVerificationManager domainVerificationManager) {
+        mContext = context;
+        mDomainVerificationManager = domainVerificationManager;
+    }
+
+    /**
      * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
      * usually a heavy workload and should be done infrequently.
      *
      * @return the current snapshot of package names with valid autoVerify URLs.
+     * @hide
      */
+    @SystemApi
     @NonNull
     @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
-    List<String> getValidVerificationPackageNames();
+    public List<String> queryValidVerificationPackageNames() {
+        try {
+            return mDomainVerificationManager.queryValidVerificationPackageNames();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Retrieves the domain verification state for a given package.
@@ -183,61 +258,106 @@
      * @return the data for the package, or null if it does not declare any autoVerify domains
      * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
      *                               and should not be re-tried except on a time scheduled basis.
+     * @hide
      */
+    @SystemApi
     @Nullable
     @RequiresPermission(anyOf = {
             android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
             android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
     })
-    DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
-            throws NameNotFoundException;
+    public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+            throws NameNotFoundException {
+        try {
+            return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
 
     /**
-     * Change the verification status of the {@param domains} of the package associated with
-     * {@param domainSetId}.
+     * Change the verification status of the {@param domains} of the package associated with {@param
+     * domainSetId}.
      *
      * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
      * @param domains     List of host names to change the state of.
      * @param state       See {@link DomainVerificationInfo#getHostToStateMap()}.
      * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
      *                                  invalid. This usually means the work being processed by the
-     *                                  verification agent is outdated and a new request should
-     *                                  be scheduled, if one has not already been done as part of
-     *                                  the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}
-     *                                  broadcast.
+     *                                  verification agent is outdated and a new request should be
+     *                                  scheduled, if one has not already been done as part of the
+     *                                  {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
      * @throws NameNotFoundException    If the ID is known to be good, but the package is
-     *                                  unavailable. This may be because the package is
-     *                                  installed on a volume that is no longer mounted. This
-     *                                  error is unrecoverable until the package is available
-     *                                  again, and should not be re-tried except on a time
-     *                                  scheduled basis.
+     *                                  unavailable. This may be because the package is installed on
+     *                                  a volume that is no longer mounted. This error is
+     *                                  unrecoverable until the package is available again, and
+     *                                  should not be re-tried except on a time scheduled basis.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
-    void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
-            @DomainVerificationState.State int state) throws NameNotFoundException;
+    public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+            @DomainVerificationState.State int state) throws NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+                    new DomainSet(domains), state);
+        } catch (Exception e) {
+            Exception converted = rethrow(e, domainSetId);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
 
     /**
-     * TODO(b/178525735): This documentation is incorrect in the context of UX changes.
-     * Change whether the given {@param packageName} is allowed to automatically open verified
-     * HTTP/HTTPS domains. The final state is determined along with the verification status for the
-     * specific domain being opened and other system state. An app with this enabled is not
-     * guaranteed to be the sole link handler for its domains.
+     * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web
+     * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with
+     * the verification status for the specific domain being opened and other system state. An app
+     * with this enabled is not guaranteed to be the sole link handler for its domains.
+     * <p>
+     * By default, all apps are allowed to open links. Users must disable them explicitly.
      *
-     * By default, all apps are allowed to open verified links. Users must disable them explicitly.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
-    void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed)
-            throws NameNotFoundException;
+    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+            boolean allowed) throws NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+                    allowed, mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
 
     /**
      * Update the recorded user selection for the given {@param domains} for the given {@param
      * domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
      * and will never be reset by the system short of an app data clear.
-     *
+     * <p>
      * This state is stored per device user. If another user needs to be changed, the appropriate
-     * permissions must be acquired and
-     * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
-     *
+     * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should
+     * be used.
+     * <p>
      * Enabling an unverified domain will allow an application to open it, but this can only occur
      * if no other app on the device is approved for a higher approval level. This can queried
      * using {@link #getOwnersForDomain(String)}.
@@ -255,33 +375,55 @@
      * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
      *                                  invalid.
      * @throws NameNotFoundException    If the ID is known to be good, but the package is
-     *                                  unavailable. This may be because the package is
-     *                                  installed on a volume that is no longer mounted. This
-     *                                  error is unrecoverable until the package is available
-     *                                  again, and should not be re-tried except on a time
-     *                                  scheduled basis.
+     *                                  unavailable. This may be because the package is installed on
+     *                                  a volume that is no longer mounted. This error is
+     *                                  unrecoverable until the package is available again, and
+     *                                  should not be re-tried except on a time scheduled basis.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
-    void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
-            @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException;
+    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
+                    new DomainSet(domains), enabled, mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, domainSetId);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
 
     /**
      * Retrieve the user selection data for the given {@param packageName} and the current user.
-     * It is the responsibility of the caller to ensure that the
-     * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls.
-     *
-     * This state is stored per device user. If another user needs to be accessed, the appropriate
-     * permissions must be acquired and
-     * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
      *
      * @param packageName The app to query state for.
-     * @return the user selection verification data for the given package for the current user,
-     * or null if the package does not declare any HTTP/HTTPS domains.
+     * @return the user selection verification data for the given package for the current user, or
+     * null if the package does not declare any HTTP/HTTPS domains.
      */
     @Nullable
-    @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
-    DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
-            throws NameNotFoundException;
+    public DomainVerificationUserState getDomainVerificationUserState(
+            @NonNull String packageName) throws NameNotFoundException {
+        try {
+            return mDomainVerificationManager.getDomainVerificationUserState(packageName,
+                    mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
 
     /**
      * For the given domain, return all apps which are approved to open it in a
@@ -291,21 +433,65 @@
      *
      * By default the list will be returned ordered from lowest to highest
      * priority.
+     *
+     * @hide
      */
+    @SystemApi
     @NonNull
     @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
-    List<DomainOwner> getOwnersForDomain(@NonNull String domain);
+    public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
+        try {
+            return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
+        return rethrow(exception, domainSetId, null);
+    }
+
+    private Exception rethrow(Exception exception, @Nullable String packageName) {
+        return rethrow(exception, null, packageName);
+    }
+
+    private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
+            @Nullable String packageName) {
+        if (exception instanceof ServiceSpecificException) {
+            int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+            if (packageName == null) {
+                packageName = exception.getMessage();
+            }
+
+            @Error int managerErrorCode = packedErrorCode & 0xFFFF;
+            switch (managerErrorCode) {
+                case ERROR_INVALID_DOMAIN_SET:
+                    int errorSpecificCode = packedErrorCode >> 16;
+                    return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
+                            domainSetId, packageName, errorSpecificCode));
+                case ERROR_NAME_NOT_FOUND:
+                    return new NameNotFoundException(packageName);
+                default:
+                    return exception;
+            }
+        } else if (exception instanceof RemoteException) {
+            return ((RemoteException) exception).rethrowFromSystemServer();
+        } else {
+            return exception;
+        }
+    }
 
     /**
      * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
      * provided by the caller is no longer valid. This may be recoverable, and the caller should
      * re-query the package name associated with the ID using
-     * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the
-     * package is no longer known to the device and thus all pending work for it should be dropped.
+     * {@link #getDomainVerificationInfo(String)}
+     * in order to check. If that also fails, then the package is no longer known to the device and
+     * thus all pending work for it should be dropped.
      *
      * @hide
      */
-    class InvalidDomainSetException extends IllegalArgumentException {
+    public static class InvalidDomainSetException extends IllegalArgumentException {
 
         public static final int REASON_ID_NULL = 1;
         public static final int REASON_ID_INVALID = 2;
@@ -313,7 +499,9 @@
         public static final int REASON_UNKNOWN_DOMAIN = 4;
         public static final int REASON_UNABLE_TO_APPROVE = 5;
 
-        /** @hide */
+        /**
+         * @hide
+         */
         @IntDef({
                 REASON_ID_NULL,
                 REASON_ID_INVALID,
@@ -352,7 +540,9 @@
         @Nullable
         private final String mPackageName;
 
-        /** @hide */
+        /**
+         * @hide
+         */
         public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
                 @Reason int reason) {
             super(buildMessage(domainSetId, packageName, reason));
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
deleted file mode 100644
index 8b9865c..0000000
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm.verify.domain;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * @hide
- */
-@SuppressWarnings("RedundantThrows")
-public class DomainVerificationManagerImpl implements DomainVerificationManager {
-
-    public static final int ERROR_INVALID_DOMAIN_SET = 1;
-    public static final int ERROR_NAME_NOT_FOUND = 2;
-
-    @IntDef(prefix = { "ERROR_" }, value = {
-            ERROR_INVALID_DOMAIN_SET,
-            ERROR_NAME_NOT_FOUND,
-    })
-    private @interface Error {
-    }
-
-    private final Context mContext;
-
-    private final IDomainVerificationManager mDomainVerificationManager;
-
-    public DomainVerificationManagerImpl(Context context,
-            IDomainVerificationManager domainVerificationManager) {
-        mContext = context;
-        mDomainVerificationManager = domainVerificationManager;
-    }
-
-    @NonNull
-    @Override
-    public List<String> getValidVerificationPackageNames() {
-        try {
-            return mDomainVerificationManager.getValidVerificationPackageNames();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Nullable
-    @Override
-    public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
-            throws NameNotFoundException {
-        try {
-            return mDomainVerificationManager.getDomainVerificationInfo(packageName);
-        } catch (Exception e) {
-            Exception converted = rethrow(e, packageName);
-            if (converted instanceof NameNotFoundException) {
-                throw (NameNotFoundException) converted;
-            } else if (converted instanceof RuntimeException) {
-                throw (RuntimeException) converted;
-            } else {
-                throw new RuntimeException(converted);
-            }
-        }
-    }
-
-    @Override
-    public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
-            int state) throws IllegalArgumentException, NameNotFoundException {
-        try {
-            mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
-                    new DomainSet(domains), state);
-        } catch (Exception e) {
-            Exception converted = rethrow(e, domainSetId);
-            if (converted instanceof NameNotFoundException) {
-                throw (NameNotFoundException) converted;
-            } else if (converted instanceof RuntimeException) {
-                throw (RuntimeException) converted;
-            } else {
-                throw new RuntimeException(converted);
-            }
-        }
-    }
-
-    @Override
-    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
-            boolean allowed) throws NameNotFoundException {
-        try {
-            mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
-                    allowed, mContext.getUserId());
-        } catch (Exception e) {
-            Exception converted = rethrow(e, packageName);
-            if (converted instanceof NameNotFoundException) {
-                throw (NameNotFoundException) converted;
-            } else if (converted instanceof RuntimeException) {
-                throw (RuntimeException) converted;
-            } else {
-                throw new RuntimeException(converted);
-            }
-        }
-    }
-
-    @Override
-    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
-            @NonNull Set<String> domains, boolean enabled)
-            throws IllegalArgumentException, NameNotFoundException {
-        try {
-            mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
-                    new DomainSet(domains), enabled, mContext.getUserId());
-        } catch (Exception e) {
-            Exception converted = rethrow(e, domainSetId);
-            if (converted instanceof NameNotFoundException) {
-                throw (NameNotFoundException) converted;
-            } else if (converted instanceof RuntimeException) {
-                throw (RuntimeException) converted;
-            } else {
-                throw new RuntimeException(converted);
-            }
-        }
-    }
-
-    @Nullable
-    @Override
-    public DomainVerificationUserSelection getDomainVerificationUserSelection(
-            @NonNull String packageName) throws NameNotFoundException {
-        try {
-            return mDomainVerificationManager.getDomainVerificationUserSelection(packageName,
-                    mContext.getUserId());
-        } catch (Exception e) {
-            Exception converted = rethrow(e, packageName);
-            if (converted instanceof NameNotFoundException) {
-                throw (NameNotFoundException) converted;
-            } else if (converted instanceof RuntimeException) {
-                throw (RuntimeException) converted;
-            } else {
-                throw new RuntimeException(converted);
-            }
-        }
-    }
-
-    @NonNull
-    @Override
-    public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
-        try {
-            return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
-        return rethrow(exception, domainSetId, null);
-    }
-
-    private Exception rethrow(Exception exception, @Nullable String packageName) {
-        return rethrow(exception, null, packageName);
-    }
-
-    private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
-            @Nullable String packageName) {
-        if (exception instanceof ServiceSpecificException) {
-            int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
-            if (packageName == null) {
-                packageName = exception.getMessage();
-            }
-
-            @Error int managerErrorCode = packedErrorCode & 0xFFFF;
-            switch (managerErrorCode) {
-                case ERROR_INVALID_DOMAIN_SET:
-                    int errorSpecificCode = packedErrorCode >> 16;
-                    return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
-                            domainSetId, packageName, errorSpecificCode));
-                case ERROR_NAME_NOT_FOUND:
-                    return new NameNotFoundException(packageName);
-                default:
-                    return exception;
-            }
-        } else if (exception instanceof RemoteException) {
-            return ((RemoteException) exception).rethrowFromSystemServer();
-        } else {
-            return exception;
-        }
-    }
-}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
similarity index 93%
rename from core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
rename to core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
index ddb5ef8..94690c1 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl
@@ -16,4 +16,4 @@
 
 package android.content.pm.verify.domain;
 
-parcelable DomainVerificationUserSelection;
+parcelable DomainVerificationUserState;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
similarity index 82%
rename from core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
rename to core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
index d23f5f1..1e60abb 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -29,39 +30,36 @@
 import com.android.internal.util.Parcelling;
 
 import java.util.Map;
-import java.util.Set;
 import java.util.UUID;
 
 /**
  * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
  * package in its manifest, whether or not they were marked for auto verification.
  * <p>
- * By default, all apps are allowed to automatically open links with domains that they've
- * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
- * can decide to disable this, disallowing the application from opening all links. Note that the
- * toggle affects <b>all</b> links and is not based on the verification state of the domains.
+ * Applications should use {@link #getHostToStateMap()} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
+ * <p>
+ * By default, all apps are allowed to automatically open links for the above case for domains that
+ * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening all links. Note
+ * that the toggle affects <b>all</b> links and is not based on the verification state of the
+ * domains.
  * <p>
  * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
  * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single
  * application can be approved for a domain unless the applications are both approved. If another
  * application is approved, the user will not be allowed to enable the domain.
- * <p>
- * These values can be changed through the
- * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
- * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
- * boolean)} APIs.
- * <p>
- * Note that because state is per user, if a different user needs to be changed, one will need to
- * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
- * @hide
  */
-@SystemApi
 @SuppressWarnings("DefaultAnnotationParam")
 @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
         genEqualsHashCode = true, genHiddenConstDefs = true)
-public final class DomainVerificationUserSelection implements Parcelable {
+public final class DomainVerificationUserState implements Parcelable {
 
     /**
      * The domain is unverified and unselected, and the application is unable to open web links
@@ -70,9 +68,8 @@
     public static final int DOMAIN_STATE_NONE = 0;
 
     /**
-     * The domain has been selected through the
-     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}
-     * API, under the assumption it has not been reset by the system.
+     * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if
+     * another application is selected or verified for the same domain.
      */
     public static final int DOMAIN_STATE_SELECTED = 1;
 
@@ -119,7 +116,16 @@
     @NonNull
     private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
         return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
-                DomainVerificationUserSelection.class.getClassLoader());
+                DomainVerificationUserState.class.getClassLoader());
+    }
+
+    /**
+     * @see DomainVerificationInfo#getIdentifier
+     * @hide
+     */
+    @SystemApi
+    public @NonNull UUID getIdentifier() {
+        return mIdentifier;
     }
 
 
@@ -130,7 +136,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -162,7 +168,7 @@
     }
 
     /**
-     * Creates a new DomainVerificationUserSelection.
+     * Creates a new DomainVerificationUserState.
      *
      * @param packageName
      *   The package name that this data corresponds to.
@@ -175,7 +181,7 @@
      * @hide
      */
     @DataClass.Generated.Member
-    public DomainVerificationUserSelection(
+    public DomainVerificationUserState(
             @NonNull UUID identifier,
             @NonNull String packageName,
             @NonNull UserHandle user,
@@ -201,14 +207,6 @@
     }
 
     /**
-     * @see DomainVerificationInfo#getIdentifier
-     */
-    @DataClass.Generated.Member
-    public @NonNull UUID getIdentifier() {
-        return mIdentifier;
-    }
-
-    /**
      * The package name that this data corresponds to.
      */
     @DataClass.Generated.Member
@@ -246,7 +244,7 @@
         // You can override field toString logic by defining methods like:
         // String fieldNameToString() { ... }
 
-        return "DomainVerificationUserSelection { " +
+        return "DomainVerificationUserState { " +
                 "identifier = " + mIdentifier + ", " +
                 "packageName = " + mPackageName + ", " +
                 "user = " + mUser + ", " +
@@ -259,13 +257,13 @@
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
+        // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
         // boolean fieldNameEquals(FieldType otherValue) { ... }
 
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         @SuppressWarnings("unchecked")
-        DomainVerificationUserSelection that = (DomainVerificationUserSelection) o;
+        DomainVerificationUserState that = (DomainVerificationUserState) o;
         //noinspection PointlessBooleanExpression
         return true
                 && java.util.Objects.equals(mIdentifier, that.mIdentifier)
@@ -323,7 +321,7 @@
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) {
+    /* package-private */ DomainVerificationUserState(@NonNull Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -354,24 +352,24 @@
     }
 
     @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR
-            = new Parcelable.Creator<DomainVerificationUserSelection>() {
+    public static final @NonNull Parcelable.Creator<DomainVerificationUserState> CREATOR
+            = new Parcelable.Creator<DomainVerificationUserState>() {
         @Override
-        public DomainVerificationUserSelection[] newArray(int size) {
-            return new DomainVerificationUserSelection[size];
+        public DomainVerificationUserState[] newArray(int size) {
+            return new DomainVerificationUserState[size];
         }
 
         @Override
-        public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) {
-            return new DomainVerificationUserSelection(in);
+        public DomainVerificationUserState createFromParcel(@NonNull Parcel in) {
+            return new DomainVerificationUserState(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1613683603297L,
+            time = 1614721840152L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
-            inputSignatures = "public static final  int DOMAIN_STATE_NONE\npublic static final  int DOMAIN_STATE_SELECTED\npublic static final  int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate  void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
+            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java",
+            inputSignatures = "public static final  int DOMAIN_STATE_NONE\npublic static final  int DOMAIN_STATE_SELECTED\npublic static final  int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate  void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserState extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
index 701af32..332b925 100644
--- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -19,7 +19,7 @@
 import android.content.pm.verify.domain.DomainOwner;
 import android.content.pm.verify.domain.DomainSet;
 import android.content.pm.verify.domain.DomainVerificationInfo;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
 import java.util.List;
 
 /**
@@ -28,13 +28,13 @@
  */
 interface IDomainVerificationManager {
 
-    List<String> getValidVerificationPackageNames();
+    List<String> queryValidVerificationPackageNames();
 
     @nullable
     DomainVerificationInfo getDomainVerificationInfo(String packageName);
 
     @nullable
-    DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
+    DomainVerificationUserState getDomainVerificationUserState(String packageName,
             int userId);
 
     @nullable
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
index d91cef5..236ae8b 100644
--- a/core/java/android/net/vcn/IVcnStatusCallback.aidl
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -18,7 +18,6 @@
 
 /** @hide */
 oneway interface IVcnStatusCallback {
-    void onEnteredSafeMode();
     void onVcnStatusChanged(int statusCode);
     void onGatewayConnectionError(
             in int[] gatewayNetworkCapabilities,
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index eb8c251..8ebf757 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -359,8 +359,6 @@
     /**
      * Value indicating that the VCN for the subscription group is not configured, or that the
      * callback is not privileged for the subscription group.
-     *
-     * @hide
      */
     public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0;
 
@@ -369,8 +367,6 @@
      *
      * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the
      * provisioning package is not privileged.
-     *
-     * @hide
      */
     public static final int VCN_STATUS_CODE_INACTIVE = 1;
 
@@ -380,8 +376,6 @@
      * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning
      * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered
      * active while it is connecting, fully connected, and disconnecting.
-     *
-     * @hide
      */
     public static final int VCN_STATUS_CODE_ACTIVE = 2;
 
@@ -391,8 +385,6 @@
      * <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).
-     *
-     * @hide
      */
     public static final int VCN_STATUS_CODE_SAFE_MODE = 3;
 
@@ -407,8 +399,6 @@
 
     /**
      * Value indicating that an internal failure occurred in this Gateway Connection.
-     *
-     * @hide
      */
     public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
 
@@ -416,8 +406,6 @@
      * Value indicating that an error with this Gateway Connection's configuration occurred.
      *
      * <p>For example, this error code will be returned after authentication failures.
-     *
-     * @hide
      */
     public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
 
@@ -427,38 +415,19 @@
      * <p>For example, this error code will be returned if an underlying {@link android.net.Network}
      * for this Gateway Connection is lost, or if an error occurs while resolving the connection
      * endpoint address.
-     *
-     * @hide
      */
     public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
 
-    // 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)}.
-         *
-         * @hide
-         */
-        public void onEnteredSafeMode() {}
-
-        /**
          * Invoked when status of the VCN for this callback's subscription group changes.
          *
          * @param statusCode the code for the status change encountered by this {@link
@@ -467,15 +436,16 @@
         public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode);
 
         /**
-         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription
+         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
          * encounters an error.
          *
-         * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway
-         *     Connection that encountered the error for identification purposes. These will be a
-         *     sorted list with no duplicates, matching one of the {@link
+         * @param networkCapabilities an array of NetworkCapabilities.NET_CAPABILITY_* capabilities
+         *     for the Gateway Connection that encountered the error, for identification purposes.
+         *     These will be a sorted list with no duplicates and will match {@link
+         *     VcnGatewayConnectionConfig#getRequiredUnderlyingCapabilities()} for one of the {@link
          *     VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription
          *     group.
-         * @param errorCode {@link VcnErrorCode} to indicate the error that occurred
+         * @param errorCode the code to indicate the error that occurred
          * @param detail Throwable to provide additional information about the error, or {@code
          *     null} if none
          */
@@ -496,6 +466,10 @@
      * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
      * privileges for the specified subscription at the time of invocation.
      *
+     * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered
+     * and there is a VCN active for its specified subscription group (this may happen after the
+     * callback is registered).
+     *
      * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the
      * current status for the specified subscription group's VCN. If the registrant is not
      * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
@@ -505,7 +479,6 @@
      * @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,
@@ -538,7 +511,6 @@
      * was registered with.
      *
      * @param callback The callback to be unregistered
-     * @hide
      */
     public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
         requireNonNull(callback, "callback must not be null");
@@ -599,12 +571,6 @@
         }
 
         @Override
-        public void onEnteredSafeMode() {
-            Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
-        }
-
-        @Override
         public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode)));
diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java
new file mode 100644
index 0000000..b6036b4
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+/**
+ * CertUtils provides utility methods for constructing Certificate.
+ *
+ * @hide
+ */
+public class CertUtils {
+    private static final String CERT_TYPE_X509 = "X.509";
+
+    /** Decodes an ASN.1 DER encoded Certificate */
+    public static X509Certificate certificateFromByteArray(byte[] derEncoded) {
+        Objects.requireNonNull(derEncoded, "derEncoded is null");
+
+        try {
+            CertificateFactory certFactory = CertificateFactory.getInstance(CERT_TYPE_X509);
+            InputStream in = new ByteArrayInputStream(derEncoded);
+            return (X509Certificate) certFactory.generateCertificate(in);
+        } catch (CertificateException e) {
+            throw new IllegalArgumentException("Fail to decode certificate", e);
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
new file mode 100644
index 0000000..ce5ec75
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
@@ -0,0 +1,73 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert ChildSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class ChildSaProposalUtils extends SaProposalUtilsBase {
+    /** Serializes a ChildSaProposal to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) {
+        return SaProposalUtilsBase.toPersistableBundle(proposal);
+    }
+
+    /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */
+    @NonNull
+    public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
+
+        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+        final List<EncryptionAlgoKeyLenPair> encryptList =
+                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+        for (EncryptionAlgoKeyLenPair t : encryptList) {
+            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+        }
+
+        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+        for (int algo : integrityAlgoIdArray) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+
+        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+        for (int dh : dhGroupArray) {
+            builder.addDhGroup(dh);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
new file mode 100644
index 0000000..853a52d
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
@@ -0,0 +1,276 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.eap.EapSessionConfig;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
+import android.net.eap.EapSessionConfig.EapMethodConfig;
+import android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import android.net.eap.EapSessionConfig.EapSimConfig;
+import android.net.eap.EapSessionConfig.EapTtlsConfig;
+import android.net.eap.EapSessionConfig.EapUiccConfig;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert EapSessionConfig to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class EapSessionConfigUtils {
+    private static final String EAP_ID_KEY = "EAP_ID_KEY";
+    private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY";
+    private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY";
+    private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY";
+    private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY";
+    private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY";
+
+    /** Serializes an EapSessionConfig to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putPersistableBundle(
+                EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity()));
+
+        if (config.getEapSimConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_SIM_CONFIG_KEY,
+                    EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig()));
+        }
+
+        if (config.getEapTtlsConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_TTLS_CONFIG_KEY,
+                    EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig()));
+        }
+
+        if (config.getEapAkaConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_AKA_CONFIG_KEY,
+                    EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig()));
+        }
+
+        if (config.getEapMsChapV2Config() != null) {
+            result.putPersistableBundle(
+                    EAP_MSCHAP_V2_CONFIG_KEY,
+                    EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config()));
+        }
+
+        if (config.getEapAkaPrimeConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_AKA_PRIME_CONFIG_KEY,
+                    EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig()));
+        }
+
+        return result;
+    }
+
+    /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */
+    @NonNull
+    public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final EapSessionConfig.Builder builder = new EapSessionConfig.Builder();
+
+        final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY);
+        Objects.requireNonNull(eapIdBundle, "EAP ID was null");
+        builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle));
+
+        final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY);
+        if (simBundle != null) {
+            EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder);
+        }
+
+        final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY);
+        if (ttlsBundle != null) {
+            EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder);
+        }
+
+        final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY);
+        if (akaBundle != null) {
+            EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder);
+        }
+
+        final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY);
+        if (msChapV2Bundle != null) {
+            EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder);
+        }
+
+        final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY);
+        if (akaPrimeBundle != null) {
+            EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder);
+        }
+
+        return builder.build();
+    }
+
+    private static class EapMethodConfigUtils {
+        private static final String METHOD_TYPE = "METHOD_TYPE";
+
+        /** Serializes an EapMethodConfig to a PersistableBundle. */
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) {
+            final PersistableBundle result = new PersistableBundle();
+            result.putInt(METHOD_TYPE, config.getMethodType());
+            return result;
+        }
+    }
+
+    private static class EapUiccConfigUtils extends EapMethodConfigUtils {
+        static final String SUB_ID_KEY = "SUB_ID_KEY";
+        static final String APP_TYPE_KEY = "APP_TYPE_KEY";
+
+        @NonNull
+        protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            result.putInt(SUB_ID_KEY, config.getSubId());
+            result.putInt(APP_TYPE_KEY, config.getAppType());
+
+            return result;
+        }
+    }
+
+    private static final class EapSimConfigUtils extends EapUiccConfigUtils {
+        @NonNull
+        public static PersistableBundle toPersistableBundle(EapSimConfig config) {
+            return EapUiccConfigUtils.toPersistableBundle(config);
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+        }
+    }
+
+    private static class EapAkaConfigUtils extends EapUiccConfigUtils {
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) {
+            return EapUiccConfigUtils.toPersistableBundle(config);
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+        }
+    }
+
+    private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils {
+        private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY";
+        private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) {
+            final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config);
+            result.putString(NETWORK_NAME_KEY, config.getNetworkName());
+            result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames());
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapAkaPrimeConfig(
+                    in.getInt(SUB_ID_KEY),
+                    in.getInt(APP_TYPE_KEY),
+                    in.getString(NETWORK_NAME_KEY),
+                    in.getBoolean(ALL_MISMATCHED_NETWORK_KEY));
+        }
+    }
+
+    private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils {
+        private static final String USERNAME_KEY = "USERNAME_KEY";
+        private static final String PASSWORD_KEY = "PASSWORD_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            result.putString(USERNAME_KEY, config.getUsername());
+            result.putString(PASSWORD_KEY, config.getPassword());
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY));
+        }
+    }
+
+    private static final class EapTtlsConfigUtils extends EapMethodConfigUtils {
+        private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
+        private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            try {
+                if (config.getServerCaCert() != null) {
+                    final PersistableBundle caBundle =
+                            PersistableBundleUtils.fromByteArray(
+                                    config.getServerCaCert().getEncoded());
+                    result.putPersistableBundle(TRUST_CERT_KEY, caBundle);
+                }
+            } catch (CertificateEncodingException e) {
+                throw new IllegalStateException("Fail to encode the certificate");
+            }
+
+            result.putPersistableBundle(
+                    EAP_SESSION_CONFIG_KEY,
+                    EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig()));
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY);
+            X509Certificate caCert = null;
+            if (caBundle != null) {
+                caCert =
+                        CertUtils.certificateFromByteArray(
+                                PersistableBundleUtils.toByteArray(caBundle));
+            }
+
+            final PersistableBundle eapSessionConfigBundle =
+                    in.getPersistableBundle(EAP_SESSION_CONFIG_KEY);
+            Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null");
+            final EapSessionConfig eapSessionConfig =
+                    EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle);
+
+            builder.setEapTtlsConfig(caCert, eapSessionConfig);
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
new file mode 100644
index 0000000..6acb34e
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.Objects;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Abstract utility class to convert IkeIdentification to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeIdentificationUtils {
+    private static final String ID_TYPE_KEY = "ID_TYPE_KEY";
+
+    private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY";
+    private static final String FQDN_KEY = "FQDN_KEY";
+    private static final String KEY_ID_KEY = "KEY_ID_KEY";
+    private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY";
+    private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY";
+    private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY";
+
+    private static final int ID_TYPE_DER_ASN1_DN = 1;
+    private static final int ID_TYPE_FQDN = 2;
+    private static final int ID_TYPE_IPV4_ADDR = 3;
+    private static final int ID_TYPE_IPV6_ADDR = 4;
+    private static final int ID_TYPE_KEY_ID = 5;
+    private static final int ID_TYPE_RFC822_ADDR = 6;
+
+    /** Serializes an IkeIdentification to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) {
+        if (ikeId instanceof IkeDerAsn1DnIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN);
+            IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId;
+            result.putPersistableBundle(
+                    DER_ASN1_DN_KEY,
+                    PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded()));
+            return result;
+        } else if (ikeId instanceof IkeFqdnIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN);
+            IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId;
+            result.putString(FQDN_KEY, id.fqdn);
+            return result;
+        } else if (ikeId instanceof IkeIpv4AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR);
+            IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId;
+            result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress());
+            return result;
+        } else if (ikeId instanceof IkeIpv6AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR);
+            IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId;
+            result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress());
+            return result;
+        } else if (ikeId instanceof IkeKeyIdIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID);
+            IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId;
+            result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId));
+            return result;
+        } else if (ikeId instanceof IkeRfc822AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR);
+            IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId;
+            result.putString(RFC822_ADDRESS_KEY, id.rfc822Name);
+            return result;
+        } else {
+            throw new IllegalStateException("Unrecognized IkeIdentification subclass");
+        }
+    }
+
+    private static PersistableBundle createPersistableBundle(int idType) {
+        final PersistableBundle result = new PersistableBundle();
+        result.putInt(ID_TYPE_KEY, idType);
+        return result;
+    }
+
+    /** Constructs an IkeIdentification by deserializing a PersistableBundle. */
+    @NonNull
+    public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+        int idType = in.getInt(ID_TYPE_KEY);
+        switch (idType) {
+            case ID_TYPE_DER_ASN1_DN:
+                final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY);
+                Objects.requireNonNull(dnBundle, "ASN1 DN was null");
+                return new IkeDerAsn1DnIdentification(
+                        new X500Principal(PersistableBundleUtils.toByteArray(dnBundle)));
+            case ID_TYPE_FQDN:
+                return new IkeFqdnIdentification(in.getString(FQDN_KEY));
+            case ID_TYPE_IPV4_ADDR:
+                final String v4AddressStr = in.getString(IP4_ADDRESS_KEY);
+                Objects.requireNonNull(v4AddressStr, "IPv4 address was null");
+                return new IkeIpv4AddrIdentification(
+                        (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr));
+            case ID_TYPE_IPV6_ADDR:
+                final String v6AddressStr = in.getString(IP6_ADDRESS_KEY);
+                Objects.requireNonNull(v6AddressStr, "IPv6 address was null");
+                return new IkeIpv6AddrIdentification(
+                        (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr));
+            case ID_TYPE_KEY_ID:
+                final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY);
+                Objects.requireNonNull(in, "Key ID was null");
+                return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle));
+            case ID_TYPE_RFC822_ADDR:
+                return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY));
+            default:
+                throw new IllegalStateException("Unrecognized IKE ID type: " + idType);
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
new file mode 100644
index 0000000..1459671
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert IkeSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeSaProposalUtils extends SaProposalUtilsBase {
+    private static final String PRF_KEY = "PRF_KEY";
+
+    /** Serializes an IkeSaProposal to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) {
+        final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal);
+
+        final int[] prfArray =
+                proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(PRF_KEY, prfArray);
+
+        return result;
+    }
+
+    /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */
+    @NonNull
+    public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
+
+        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+        final List<EncryptionAlgoKeyLenPair> encryptList =
+                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+        for (EncryptionAlgoKeyLenPair t : encryptList) {
+            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+        }
+
+        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+        for (int algo : integrityAlgoIdArray) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+
+        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+        for (int dh : dhGroupArray) {
+            builder.addDhGroup(dh);
+        }
+
+        final int[] prfArray = in.getIntArray(PRF_KEY);
+        Objects.requireNonNull(prfArray, "PRF array was null");
+        for (int prf : prfArray) {
+            builder.addPseudorandomFunction(prf);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
new file mode 100644
index 0000000..0c9ee84
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
@@ -0,0 +1,96 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.SaProposal;
+import android.os.PersistableBundle;
+import android.util.Pair;
+
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Abstract utility class to convert SaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+abstract class SaProposalUtilsBase {
+    static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY";
+    static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY";
+    static final String DH_GROUP_KEY = "DH_GROUP_KEY";
+
+    static class EncryptionAlgoKeyLenPair {
+        private static final String ALGO_KEY = "ALGO_KEY";
+        private static final String KEY_LEN_KEY = "KEY_LEN_KEY";
+
+        public final int encryptionAlgo;
+        public final int keyLen;
+
+        EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) {
+            this.encryptionAlgo = encryptionAlgo;
+            this.keyLen = keyLen;
+        }
+
+        EncryptionAlgoKeyLenPair(PersistableBundle in) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            this.encryptionAlgo = in.getInt(ALGO_KEY);
+            this.keyLen = in.getInt(KEY_LEN_KEY);
+        }
+
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(ALGO_KEY, encryptionAlgo);
+            result.putInt(KEY_LEN_KEY, keyLen);
+
+            return result;
+        }
+    }
+
+    /**
+     * Serializes common info of a SaProposal to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    static PersistableBundle toPersistableBundle(SaProposal proposal) {
+        final PersistableBundle result = new PersistableBundle();
+
+        final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>();
+        for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) {
+            encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second));
+        }
+        final PersistableBundle encryptionBundle =
+                PersistableBundleUtils.fromList(
+                        encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle);
+        result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle);
+
+        final int[] integrityAlgoIdArray =
+                proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray);
+
+        final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(DH_GROUP_KEY, dhGroupArray);
+
+        return result;
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
new file mode 100644
index 0000000..e62acac
--- /dev/null
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
@@ -0,0 +1,285 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class TunnelModeChildSessionParamsUtils {
+    private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName();
+
+    private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY";
+    private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY";
+    private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
+    private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
+    private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
+    private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
+
+    private static class ConfigRequest {
+        private static final int TYPE_IPV4_ADDRESS = 1;
+        private static final int TYPE_IPV6_ADDRESS = 2;
+        private static final int TYPE_IPV4_DNS = 3;
+        private static final int TYPE_IPV6_DNS = 4;
+        private static final int TYPE_IPV4_DHCP = 5;
+        private static final int TYPE_IPV4_NETMASK = 6;
+
+        private static final String TYPE_KEY = "type";
+        private static final String VALUE_KEY = "address";
+        private static final String IP6_PREFIX_LEN = "ip6PrefixLen";
+
+        private static final int PREFIX_LEN_UNUSED = -1;
+
+        public final int type;
+        public final int ip6PrefixLen;
+
+        // Null when it is an empty request
+        @Nullable public final InetAddress address;
+
+        ConfigRequest(TunnelModeChildConfigRequest config) {
+            int prefixLen = PREFIX_LEN_UNUSED;
+
+            if (config instanceof ConfigRequestIpv4Address) {
+                type = TYPE_IPV4_ADDRESS;
+                address = ((ConfigRequestIpv4Address) config).getAddress();
+            } else if (config instanceof ConfigRequestIpv6Address) {
+                type = TYPE_IPV6_ADDRESS;
+                address = ((ConfigRequestIpv6Address) config).getAddress();
+                if (address != null) {
+                    prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength();
+                }
+            } else if (config instanceof ConfigRequestIpv4DnsServer) {
+                type = TYPE_IPV4_DNS;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv6DnsServer) {
+                type = TYPE_IPV6_DNS;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv4DhcpServer) {
+                type = TYPE_IPV4_DHCP;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv4Netmask) {
+                type = TYPE_IPV4_NETMASK;
+                address = null;
+            } else {
+                throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
+            }
+
+            ip6PrefixLen = prefixLen;
+        }
+
+        ConfigRequest(PersistableBundle in) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            type = in.getInt(TYPE_KEY);
+            ip6PrefixLen = in.getInt(IP6_PREFIX_LEN);
+
+            String addressStr = in.getString(VALUE_KEY);
+            if (addressStr == null) {
+                address = null;
+            } else {
+                address = InetAddresses.parseNumericAddress(addressStr);
+            }
+        }
+
+        @NonNull
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(TYPE_KEY, type);
+            result.putInt(IP6_PREFIX_LEN, ip6PrefixLen);
+
+            if (address != null) {
+                result.putString(VALUE_KEY, address.getHostAddress());
+            }
+
+            return result;
+        }
+    }
+
+    /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(
+            @NonNull TunnelModeChildSessionParams params) {
+        final PersistableBundle result = new PersistableBundle();
+
+        final PersistableBundle saProposalBundle =
+                PersistableBundleUtils.fromList(
+                        params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle);
+        result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
+
+        final PersistableBundle inTsBundle =
+                PersistableBundleUtils.fromList(
+                        params.getInboundTrafficSelectors(),
+                        IkeTrafficSelectorUtils::toPersistableBundle);
+        result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle);
+
+        final PersistableBundle outTsBundle =
+                PersistableBundleUtils.fromList(
+                        params.getOutboundTrafficSelectors(),
+                        IkeTrafficSelectorUtils::toPersistableBundle);
+        result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle);
+
+        result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
+        result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
+
+        final List<ConfigRequest> reqList = new ArrayList<>();
+        for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) {
+            reqList.add(new ConfigRequest(req));
+        }
+        final PersistableBundle configReqListBundle =
+                PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
+        result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
+
+        return result;
+    }
+
+    private static List<IkeTrafficSelector> getTsFromPersistableBundle(
+            PersistableBundle in, String key) {
+        PersistableBundle tsBundle = in.getPersistableBundle(key);
+        Objects.requireNonNull(tsBundle, "Value for key " + key + " was null");
+        return PersistableBundleUtils.toList(
+                tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle);
+    }
+
+    /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */
+    @NonNull
+    public static TunnelModeChildSessionParams fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final TunnelModeChildSessionParams.Builder builder =
+                new TunnelModeChildSessionParams.Builder();
+
+        final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
+        Objects.requireNonNull(proposalBundle, "SA proposal was null");
+        final List<ChildSaProposal> proposals =
+                PersistableBundleUtils.toList(
+                        proposalBundle, ChildSaProposalUtils::fromPersistableBundle);
+        for (ChildSaProposal p : proposals) {
+            builder.addSaProposal(p);
+        }
+
+        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) {
+            builder.addInboundTrafficSelectors(ts);
+        }
+
+        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) {
+            builder.addOutboundTrafficSelectors(ts);
+        }
+
+        builder.setLifetimeSeconds(
+                in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
+        final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
+        Objects.requireNonNull(configReqListBundle, "Config request list was null");
+        final List<ConfigRequest> reqList =
+                PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
+
+        boolean hasIpv4AddressReq = false;
+        boolean hasIpv4NetmaskReq = false;
+        for (ConfigRequest req : reqList) {
+            switch (req.type) {
+                case ConfigRequest.TYPE_IPV4_ADDRESS:
+                    hasIpv4AddressReq = true;
+                    if (req.address == null) {
+                        builder.addInternalAddressRequest(AF_INET);
+                    } else {
+                        builder.addInternalAddressRequest((Inet4Address) req.address);
+                    }
+                    break;
+                case ConfigRequest.TYPE_IPV6_ADDRESS:
+                    if (req.address == null) {
+                        builder.addInternalAddressRequest(AF_INET6);
+                    } else {
+                        builder.addInternalAddressRequest(
+                                (Inet6Address) req.address, req.ip6PrefixLen);
+                    }
+                    break;
+                case ConfigRequest.TYPE_IPV4_NETMASK:
+                    // Do not need to set netmask because it will be automatically set by the
+                    // builder when an IPv4 internal address request is set.
+                    hasIpv4NetmaskReq = true;
+                    break;
+                case ConfigRequest.TYPE_IPV4_DNS:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported");
+                    }
+                    builder.addInternalDnsServerRequest(AF_INET);
+                    break;
+                case ConfigRequest.TYPE_IPV6_DNS:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported");
+                    }
+                    builder.addInternalDnsServerRequest(AF_INET6);
+                    break;
+                case ConfigRequest.TYPE_IPV4_DHCP:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported");
+                    }
+                    builder.addInternalDhcpServerRequest(AF_INET);
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unrecognized config request type: " + req.type);
+            }
+        }
+
+        if (hasIpv4AddressReq != hasIpv4NetmaskReq) {
+            Log.w(
+                    TAG,
+                    String.format(
+                            "Expect IPv4 address request and IPv4 netmask request either both"
+                                + " exist or both absent, but found hasIpv4AddressReq exists? %b,"
+                                + " hasIpv4AddressReq exists? %b, ",
+                            hasIpv4AddressReq, hasIpv4NetmaskReq));
+        }
+
+        return builder.build();
+    }
+}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0587610..03e5f1d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -503,6 +503,20 @@
     }
 
     /** @hide */
+    public static String effectStrengthToString(int effectStrength) {
+        switch (effectStrength) {
+            case EFFECT_STRENGTH_LIGHT:
+                return "LIGHT";
+            case EFFECT_STRENGTH_MEDIUM:
+                return "MEDIUM";
+            case EFFECT_STRENGTH_STRONG:
+                return "STRONG";
+            default:
+                return Integer.toString(effectStrength);
+        }
+    }
+
+    /** @hide */
     @TestApi
     public static class OneShot extends VibrationEffect implements Parcelable {
         private final long mDuration;
@@ -936,8 +950,8 @@
 
         @Override
         public String toString() {
-            return "Prebaked{mEffectId=" + mEffectId
-                + ", mEffectStrength=" + mEffectStrength
+            return "Prebaked{mEffectId=" + effectIdToString(mEffectId)
+                + ", mEffectStrength=" + effectStrengthToString(mEffectStrength)
                 + ", mFallback=" + mFallback
                 + ", mFallbackEffect=" + mFallbackEffect
                 + "}";
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 07272e7..50d2de3 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
 import android.util.SparseBooleanArray;
 
 import java.util.ArrayList;
@@ -33,20 +34,7 @@
  * @hide
  */
 public final class VibratorInfo implements Parcelable {
-
-    /**
-     * Capability to set amplitude values to vibrations.
-     * @hide
-     */
-    // Internally this maps to the HAL constant IVibrator::CAP_AMPLITUDE_CONTROL
-    public static final int CAPABILITY_AMPLITUDE_CONTROL = 4;
-
-    /**
-     * Capability to compose primitives into a single effect.
-     * @hide
-     */
-    // Internally this maps to the HAL constant IVibrator::CAP_COMPOSE_EFFECTS
-    public static final int CAPABILITY_COMPOSE_EFFECTS = 32;
+    private static final String TAG = "VibratorInfo";
 
     private final int mId;
     private final long mCapabilities;
@@ -108,7 +96,7 @@
         return "VibratorInfo{"
                 + "mId=" + mId
                 + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
-                + ", mCapabilities flags=" + mCapabilities
+                + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
                 + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
                 + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
                 + '}';
@@ -125,7 +113,7 @@
      * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
      */
     public boolean hasAmplitudeControl() {
-        return hasCapability(CAPABILITY_AMPLITUDE_CONTROL);
+        return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
     }
 
     /**
@@ -153,7 +141,7 @@
      * @return Whether the primitive is supported.
      */
     public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
-        return hasCapability(CAPABILITY_COMPOSE_EFFECTS) && mSupportedPrimitives != null
+        return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
                 && mSupportedPrimitives.get(primitiveId, false);
     }
 
@@ -170,11 +158,26 @@
 
     private String[] getCapabilitiesNames() {
         List<String> names = new ArrayList<>();
-        if (hasCapability(CAPABILITY_AMPLITUDE_CONTROL)) {
+        if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
+            names.add("ON_CALLBACK");
+        }
+        if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
+            names.add("PERFORM_CALLBACK");
+        }
+        if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+            names.add("COMPOSE_EFFECTS");
+        }
+        if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+            names.add("ALWAYS_ON_CONTROL");
+        }
+        if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
             names.add("AMPLITUDE_CONTROL");
         }
-        if (hasCapability(CAPABILITY_COMPOSE_EFFECTS)) {
-            names.add("COMPOSE_EFFECTS");
+        if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+            names.add("EXTERNAL_CONTROL");
+        }
+        if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
+            names.add("EXTERNAL_AMPLITUDE_CONTROL");
         }
         return names.toArray(new String[names.size()]);
     }
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index b12bb2e..396ba2d 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.IVold;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -112,4 +113,10 @@
      * @param bytes number of bytes which need to be freed
      */
     public abstract void freeCache(@Nullable String volumeUuid, long bytes);
+
+    /**
+     * Returns the {@link VolumeInfo#getId()} values for the volumes matching
+     * {@link VolumeInfo#isPrimary()}
+     */
+    public abstract List<String> getPrimaryVolumeIds();
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 084b18e..913b827 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -668,7 +668,7 @@
     public void getPrivilegesDescriptionStringForProfile(
             @NonNull String profileName,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<String> callback) {
+            @NonNull Consumer<CharSequence> callback) {
         mRemoteService.postAsync(service -> {
             AndroidFuture<String> future = new AndroidFuture<>();
             service.getPrivilegesDescriptionStringForProfile(profileName, future);
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index 1e07a87..bbe184b 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -239,14 +239,13 @@
         }
 
         @Override
-        public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason,
-                RemoteCallback callback) throws RemoteException {
+        public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason)
+                throws RemoteException {
             mHandler.post(() -> {
                 try {
                     onAnrDelayStarted(packageName, uid, tid, reason);
-                    sendResult(packageName, null /* throwable */, callback);
                 } catch (Throwable t) {
-                    sendResult(packageName, t, callback);
+                    // Ignored
                 }
             });
         }
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index ba98efa..0766b75 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,6 +32,5 @@
         in RemoteCallback callback);
     void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
         in RemoteCallback callback);
-    void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason,
-         in RemoteCallback callback);
+    void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason);
 }
\ No newline at end of file
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
new file mode 100644
index 0000000..c159a12
--- /dev/null
+++ b/core/java/android/view/InputEventAssigner.java
@@ -0,0 +1,92 @@
+/*
+ * 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.view;
+
+import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+/**
+ * Process input events and assign input event id to a specific frame.
+ *
+ * The assigned input event id is determined by where the current gesture is relative to the vsync.
+ * In the middle of the gesture (we already processed some input events, and already received at
+ * least 1 vsync), the latest InputEvent is assigned to the next frame.
+ * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame.
+ *
+ * Consider the following sequence:
+ * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will assign the "DOWN" input event.
+ * For VSYNC 2, we will assign the "MOVE 2" input event.
+ *
+ * Consider another sequence:
+ * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2"
+ * events are not attributed to any frame.
+ * For VSYNC 2, the "MOVE 3" input event will be assigned.
+ *
+ * @hide
+ */
+public class InputEventAssigner {
+    private static final String TAG = "InputEventAssigner";
+    private boolean mHasUnprocessedDown = false;
+    private int mEventId = INVALID_INPUT_EVENT_ID;
+
+    /**
+     * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
+     * the 'down' state to assign the latest input event to the current frame.
+     */
+    public void onChoreographerCallback() {
+        // Mark completion of this frame. Use newest input event from now on.
+        mHasUnprocessedDown = false;
+    }
+
+    /**
+     * Process the provided input event to determine which event id to assign to the current frame.
+     * @param event the input event currently being processed
+     * @return the id of the input event to use for the current frame
+     */
+    public int processEvent(InputEvent event) {
+        if (event instanceof KeyEvent) {
+            // We will not do any special handling for key events
+            return event.getId();
+        }
+
+        if (event instanceof MotionEvent) {
+            MotionEvent motionEvent = (MotionEvent) event;
+            final int action = motionEvent.getActionMasked();
+
+            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                mHasUnprocessedDown = false;
+            }
+            if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
+                mHasUnprocessedDown = true;
+                mEventId = event.getId();
+                // This will remain 'true' even if we receive a MOVE event, as long as choreographer
+                // hasn't invoked the 'CALLBACK_INPUT' callback.
+            }
+            // Don't update the event id if we haven't processed DOWN yet.
+            if (!mHasUnprocessedDown) {
+                mEventId = event.getId();
+            }
+            return mEventId;
+        }
+
+        throw new IllegalArgumentException("Received unexpected " + event);
+    }
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index d67439c..6801c27 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3589,6 +3589,7 @@
             msg.append(", deviceId=").append(getDeviceId());
             msg.append(", source=0x").append(Integer.toHexString(getSource()));
             msg.append(", displayId=").append(getDisplayId());
+            msg.append(", eventId=").append(getId());
         }
         msg.append(" }");
         return msg.toString();
diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java
index d4aaa61..36bf532 100644
--- a/core/java/android/view/ViewFrameInfo.java
+++ b/core/java/android/view/ViewFrameInfo.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.graphics.FrameInfo;
+import android.os.IInputConstants;
 
 /**
  * The timing information of events taking place in ViewRootImpl
@@ -24,32 +25,14 @@
  */
 public class ViewFrameInfo {
     public long drawStart;
-    public long oldestInputEventTime; // the time of the oldest input event consumed for this frame
-    public long newestInputEventTime; // the time of the newest input event consumed for this frame
+
+
     // Various flags set to provide extra metadata about the current frame. See flag definitions
     // inside FrameInfo.
     // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
     public long flags;
 
-    /**
-     * Update the oldest event time.
-     * @param eventTime the time of the input event
-     */
-    public void updateOldestInputEvent(long eventTime) {
-        if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) {
-            oldestInputEventTime = eventTime;
-        }
-    }
-
-    /**
-     * Update the newest event time.
-     * @param eventTime the time of the input event
-     */
-    public void updateNewestInputEvent(long eventTime) {
-        if (newestInputEventTime == 0 || eventTime > newestInputEventTime) {
-            newestInputEventTime = eventTime;
-        }
-    }
+    private int mInputEventId;
 
     /**
      * Populate the missing fields using the data from ViewFrameInfo
@@ -58,8 +41,7 @@
     public void populateFrameInfo(FrameInfo frameInfo) {
         frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
         frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
-        // TODO(b/169866723): Use InputEventAssigner
-        frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime;
+        frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId;
     }
 
     /**
@@ -67,8 +49,7 @@
      */
     public void reset() {
         drawStart = 0;
-        oldestInputEventTime = 0;
-        newestInputEventTime = 0;
+        mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID;
         flags = 0;
     }
 
@@ -78,4 +59,12 @@
     public void markDrawStart() {
         drawStart = System.nanoTime();
     }
+
+    /**
+     * Assign the value for input event id
+     * @param eventId the id of the input event
+     */
+    public void setInputEvent(int eventId) {
+        mInputEventId = eventId;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f8e65bd..390e3ae 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -457,6 +457,7 @@
     FallbackEventHandler mFallbackEventHandler;
     final Choreographer mChoreographer;
     protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
+    private final InputEventAssigner mInputEventAssigner = new InputEventAssigner();
 
     /**
      * Update the Choreographer's FrameInfo object with the timing information for the current
@@ -8352,16 +8353,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                     mPendingInputEventCount);
 
-            long eventTime = q.mEvent.getEventTimeNano();
-            long oldestEventTime = eventTime;
-            if (q.mEvent instanceof MotionEvent) {
-                MotionEvent me = (MotionEvent)q.mEvent;
-                if (me.getHistorySize() > 0) {
-                    oldestEventTime = me.getHistoricalEventTimeNano(0);
-                }
-            }
-            mViewFrameInfo.updateOldestInputEvent(oldestEventTime);
-            mViewFrameInfo.updateNewestInputEvent(eventTime);
+            mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
 
             deliverInputEvent(q);
         }
@@ -8497,6 +8489,11 @@
             consumedBatches = false;
         }
         doProcessInputEvents();
+        if (consumedBatches) {
+            // Must be done after we processed the input events, to mark the completion of the frame
+            // from the input point of view
+            mInputEventAssigner.onChoreographerCallback();
+        }
         return consumedBatches;
     }
 
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 15b29ad..04d29ee 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -66,12 +66,19 @@
      */
     int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4;
 
+    /**
+     * The hash algorithm sent to generate the hash was invalid. This means the value is not one
+     * of the supported values in {@link DisplayHashManager#getSupportedHashAlgorithms()}
+     */
+    int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5;
+
     /** @hide */
     @IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = {
             DISPLAY_HASH_ERROR_UNKNOWN,
             DISPLAY_HASH_ERROR_INVALID_BOUNDS,
             DISPLAY_HASH_ERROR_MISSING_WINDOW,
-            DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN
+            DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN,
+            DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface DisplayHashErrorCode {
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index 8941190..c06405a 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import android.hardware.vibrator.IVibrator;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
@@ -33,16 +34,16 @@
     @Test
     public void testHasAmplitudeControl() {
         assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl());
-        assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS
-                | VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL).hasAmplitudeControl());
+        assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS
+                | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl());
     }
 
     @Test
     public void testHasCapabilities() {
-        assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
-                .hasCapability(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS));
-        assertFalse(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
-                .hasCapability(VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL));
+        assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
+                .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
+        assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
+                .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
     }
 
     @Test
@@ -59,7 +60,7 @@
 
     @Test
     public void testIsPrimitiveSupported() {
-        VibratorInfo info = new VibratorInfo(/* id= */ 0, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+        VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS,
                 null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
         assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
         assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
@@ -73,30 +74,30 @@
     @Test
     public void testEquals() {
         VibratorInfo empty = new VibratorInfo(1, 0, null, null);
-        VibratorInfo complete = new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+        VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                 new int[]{VibrationEffect.EFFECT_CLICK},
                 new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
 
         assertEquals(complete, complete);
-        assertEquals(complete, new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+        assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                 new int[]{VibrationEffect.EFFECT_CLICK},
                 new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}));
 
         assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{})));
-        assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
                 new int[]{VibrationEffect.EFFECT_CLICK},
                 new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
-        assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                 new int[]{}, new int[]{})));
-        assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                 null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
-        assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
+        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                 new int[]{VibrationEffect.EFFECT_CLICK}, null)));
     }
 
     @Test
     public void testSerialization() {
-        VibratorInfo original = new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
+        VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
                 new int[]{VibrationEffect.EFFECT_CLICK}, null);
 
         Parcel parcel = Parcel.obtain();
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index ea42246..77a38a9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -166,6 +166,7 @@
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+    <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
@@ -176,6 +177,7 @@
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
     <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
     <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" />
+    <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" />
 
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index cabfad4..b7bf8ab 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -43,6 +43,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-2093859262": {
+      "message": "setClientVisible: %s clientVisible=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowToken.java"
+    },
     "-2072089308": {
       "message": "Attempted to add window with token that is a sub-window: %s.  Aborting.",
       "level": "WARN",
@@ -811,6 +817,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-1159577965": {
+      "message": "Focus requested for input consumer=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/InputMonitor.java"
+    },
     "-1156118957": {
       "message": "Updated config=%s",
       "level": "DEBUG",
@@ -823,12 +835,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
     },
-    "-1144293044": {
-      "message": "SURFACE SET FREEZE LAYER: %s",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
-    },
     "-1142279614": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
       "level": "VERBOSE",
@@ -1831,6 +1837,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "63329306": {
+      "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+    },
     "73987756": {
       "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
       "level": "INFO",
@@ -2461,6 +2473,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "691515534": {
+      "message": "  Commit wallpaper becoming invisible: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "693423992": {
       "message": "setAnimationLocked: setting mFocusMayChange true",
       "level": "INFO",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 1320780..d31e637b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -327,6 +327,10 @@
         return mImpl;
     }
 
+    public ShellExecutor getMainExecutor() {
+        return mMainExecutor;
+    }
+
     /**
      * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2f31acd..9ef3fb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -340,7 +340,7 @@
             mSettingsIcon.setVisibility(GONE);
         } else {
             mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
-            mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
+            mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
             mExpandedViewContainer.addView(mTaskView);
             bringChildToFront(mTaskView);
         }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 77ceda9..d663c52 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -421,7 +421,6 @@
                 "libstatspull",
                 "libstatssocket",
                 "libpdfium",
-                "libbinder_ndk",
             ],
             static_libs: [
                 "libgif",
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 912d04c5..e9b2f4a 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -80,6 +80,10 @@
     explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
         memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
         set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
+        // The struct is zeroed by memset above. That also sets FrameInfoIndex::InputEventId to
+        // equal android::os::IInputConstants::INVALID_INPUT_EVENT_ID == 0.
+        // Therefore, we can skip setting the value for InputEventId here. If the value for
+        // INVALID_INPUT_EVENT_ID changes, this code would have to be updated, as well.
         set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max();
     }
 
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 139c8e5..63edc77 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -165,6 +165,9 @@
         if (mParameters.isDeviceTransfer()) {
             flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER;
         }
+        if (mParameters.isEncrypted()) {
+            flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+        }
         return flags;
     }
 
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 2946db3..1ba1bc6 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -28,10 +28,12 @@
     private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
     private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
     private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
+    private static final String KEY_IS_ENCRYPTED = "is_encrypted";
 
     private boolean mFakeEncryptionFlag;
     private boolean mIsNonIncrementalOnly;
     private boolean mIsDeviceTransfer;
+    private boolean mIsEncrypted;
 
     public LocalTransportParameters(Handler handler, ContentResolver resolver) {
         super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -49,6 +51,10 @@
         return mIsDeviceTransfer;
     }
 
+    boolean isEncrypted() {
+        return mIsEncrypted;
+    }
+
     public String getSettingValue(ContentResolver resolver) {
         return Settings.Secure.getString(resolver, SETTING);
     }
@@ -57,5 +63,6 @@
         mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false);
         mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false);
         mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
+        mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
     }
 }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
new file mode 100644
index 0000000..c799b99
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- The main content view -->
+<LinearLayout
+    android:id="@+id/content_parent"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    android:transitionGroup="true"
+    android:orientation="vertical">
+    <Toolbar
+        android:id="@+id/action_bar"
+        style="?android:attr/actionBarStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="?android:attr/actionBarTheme" />
+    <FrameLayout
+        android:id="@+id/content_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 637805f..ad94cd03 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -24,6 +24,7 @@
 import android.widget.Toolbar;
 
 import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
 import androidx.fragment.app.FragmentActivity;
 
 import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -40,8 +41,15 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        super.setContentView(R.layout.collapsing_toolbar_base_layout);
-        mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+        // TODO(b/181723278): Update the version check after SDK for S is finalized
+        // The collapsing toolbar is only supported if the android platform version is S or higher.
+        // Otherwise the regular action bar will be shown.
+        if (BuildCompat.isAtLeastS()) {
+            super.setContentView(R.layout.collapsing_toolbar_base_layout);
+            mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+        } else {
+            super.setContentView(R.layout.toolbar_base_layout);
+        }
 
         final Toolbar toolbar = findViewById(R.id.action_bar);
         setActionBar(toolbar);
@@ -90,6 +98,14 @@
         super.setTitle(titleId);
     }
 
+    @Override
+    public boolean onNavigateUp() {
+        if (!super.onNavigateUp()) {
+            finish();
+        }
+        return true;
+    }
+
     /**
      * Returns an instance of collapsing toolbar.
      */
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 757dc0c..9624119 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1302,6 +1302,8 @@
 
     <!-- Name of the phone device. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name">Phone speaker</string>
+    <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
+    <string name="media_transfer_this_phone">This phone</string>
 
     <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
     <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off &amp; back on</string>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index 13572fa..db712e4 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -17,6 +17,7 @@
 <com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:minHeight="48dp"
     android:clickable="true"
     android:paddingBottom="@dimen/qs_tile_padding_top"
     android:paddingTop="@dimen/qs_tile_padding_top"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2d202fb..ec26b8d 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -608,6 +608,11 @@
         <item name="android:windowCloseOnTouchOutside">true</item>
     </style>
 
+    <!-- Privacy dialog -->
+    <style name="PrivacyDialog" parent="ScreenRecord">
+        <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+    </style>
+
     <!-- USB Contaminant dialog -->
     <style name ="USBContaminant" />
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 2373d75..83c2d1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -405,14 +405,19 @@
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
 
+        /**
+         * Set the amount (ratio) that the device has transitioned to doze.
+         *
+         * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+         */
         public void setDarkAmount(float darkAmount) {
-            boolean isAwake = darkAmount != 0;
-            boolean wasAwake = mDarkAmount != 0;
-            if (isAwake == wasAwake) {
+            boolean isDozing = darkAmount != 0;
+            boolean wasDozing = mDarkAmount != 0;
+            if (isDozing == wasDozing) {
                 return;
             }
             mDarkAmount = darkAmount;
-            setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener);
+            setLayoutAnimationListener(isDozing ? null : mKeepAwakeListener);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 7679d48..8ec9b68 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -47,7 +47,7 @@
     context: Context,
     private val list: List<PrivacyElement>,
     activityStarter: (String, Int) -> Unit
-) : SystemUIDialog(context, R.style.ScreenRecord) {
+) : SystemUIDialog(context, R.style.PrivacyDialog) {
 
     private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
     private val dismissed = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 6cdf6ab..58a54f6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -82,7 +82,7 @@
                 dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
         if (intent != null) {
             final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED, null, UserHandle.CURRENT);
+                    mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
             b.setContentIntent(pendingIntent);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1251b58..5206328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2052,8 +2052,7 @@
                 mNotificationLaunchHeight,
                 zProgress);
         setTranslationZ(translationZ);
-        float extraWidthForClipping = params.getWidth() - getWidth()
-                + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress());
+        float extraWidthForClipping = params.getWidth() - getWidth();
         setExtraWidthForClipping(extraWidthForClipping);
         int top = params.getTop();
         float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 756fe6c..8446b4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -133,7 +133,7 @@
      */
     public static int getNotificationLaunchHeight(Context context) {
         int zDistance = getZDistanceBetweenElements(context);
-        return getBaseHeight(zDistance) * 2;
+        return NOTIFICATIONS_HAVE_SHADOWS ? 2 * getBaseHeight(zDistance) : 4 * zDistance;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 38f3bc8..59c1138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -94,15 +94,6 @@
                             goingToFullShade,
                             oldState);
                 }
-
-                @Override
-                public void onDozeAmountChanged(float linearAmount, float amount) {
-                    if (DEBUG) {
-                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
-                                linearAmount, amount));
-                    }
-                    setDarkAmount(amount);
-                }
             };
 
     @Inject
@@ -294,20 +285,6 @@
         }
     }
 
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     *
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    private void setDarkAmount(float darkAmount) {
-        boolean isAwake = darkAmount != 0;
-        if (darkAmount == mDarkAmount) {
-            return;
-        }
-        mDarkAmount = darkAmount;
-        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
-    }
-
     private boolean isListAnimating() {
         return mKeyguardVisibilityHelper.isVisibilityAnimating();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 8845a05..5a80c05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -205,7 +205,7 @@
     protected void onViewAttached() {
         if (DEBUG) Log.d(TAG, "onViewAttached");
         mAdapter.registerDataSetObserver(mDataSetObserver);
-        mDataSetObserver.onChanged();
+        mAdapter.notifyDataSetChanged();
         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mScreenLifecycle.addObserver(mScreenObserver);
@@ -373,14 +373,13 @@
      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
      */
     private void setDarkAmount(float darkAmount) {
-        boolean isAwake = darkAmount != 0;
+        boolean isFullyDozed = darkAmount == 1;
         if (darkAmount == mDarkAmount) {
             return;
         }
         mDarkAmount = darkAmount;
         mListView.setDarkAmount(darkAmount);
-        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
-        if (!isAwake) {
+        if (isFullyDozed) {
             closeSwitcherIfOpenAndNotSimple(false);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 738cab1..5638503 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -439,7 +439,7 @@
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
         public void onAvailable(Network network) {
-            if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
+            if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId());
             updateState();
             fireCallbacks();
         };
@@ -448,7 +448,7 @@
         // how long the VPN connection is held on to.
         @Override
         public void onLost(Network network) {
-            if (DEBUG) Log.d(TAG, "onLost " + network.netId);
+            if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
             updateState();
             fireCallbacks();
         };
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 21cae45..a3a0cb4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1330,7 +1330,7 @@
         mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
                 deviceProfile, FgThread.getExecutor(), desc -> {
                         try {
-                            result.complete(desc);
+                            result.complete(String.valueOf(desc));
                         } catch (Exception e) {
                             result.completeExceptionally(e);
                         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7f85083..256e963 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3820,7 +3820,24 @@
                 removeListenRequestFromNetworks(req);
             }
         }
-        mDefaultNetworkRequests.remove(nri);
+        if (mDefaultNetworkRequests.remove(nri)) {
+            // If this request was one of the defaults, then the UID rules need to be updated
+            // WARNING : if the app(s) for which this network request is the default are doing
+            // traffic, this will kill their connected sockets, even if an equivalent request
+            // is going to be reinstated right away ; unconnected traffic will go on the default
+            // until the new default is set, which will happen very soon.
+            // TODO : The only way out of this is to diff old defaults and new defaults, and only
+            // remove ranges for those requests that won't have a replacement
+            final NetworkAgentInfo satisfier = nri.getSatisfier();
+            if (null != satisfier) {
+                try {
+                    mNetd.networkRemoveUidRanges(satisfier.network.getNetId(),
+                            toUidRangeStableParcels(nri.getUids()));
+                } catch (RemoteException e) {
+                    loge("Exception setting network preference default network", e);
+                }
+            }
+        }
         mNetworkRequestCounter.decrementCount(nri.mUid);
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
 
@@ -4462,16 +4479,13 @@
                 case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
                     handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
                     break;
-                case EVENT_SET_OEM_NETWORK_PREFERENCE:
+                case EVENT_SET_OEM_NETWORK_PREFERENCE: {
                     final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
                             (Pair<OemNetworkPreferences,
                                     IOnSetOemNetworkPreferenceListener>) msg.obj;
-                    try {
-                        handleSetOemNetworkPreference(arg.first, arg.second);
-                    } catch (RemoteException e) {
-                        loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
-                    }
+                    handleSetOemNetworkPreference(arg.first, arg.second);
                     break;
+                }
                 case EVENT_REPORT_NETWORK_ACTIVITY:
                     mNetworkActivityTracker.handleReportNetworkActivity();
                     break;
@@ -5229,11 +5243,20 @@
             ensureAllNetworkRequestsHaveType(r);
             mRequests = initializeRequests(r);
             mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
+            // Note here that the satisfier may have corresponded to an old request, that
+            // this code doesn't try to take over. While it is a small discrepancy in the
+            // structure of these requests, it will be fixed by the next rematch and it's
+            // not as bad as having an NRI not storing its real satisfier.
+            // Fixing this discrepancy would require figuring out in the copying code what
+            // is the new request satisfied by this, which is a bit complex and not very
+            // useful as no code is using it until rematch fixes it.
+            mSatisfier = nri.mSatisfier;
             mMessenger = nri.mMessenger;
             mBinder = nri.mBinder;
             mPid = nri.mPid;
             mUid = nri.mUid;
             mPendingIntent = nri.mPendingIntent;
+            mNetworkRequestCounter.incrementCountOrThrow(mUid);
             mCallingAttributionTag = nri.mCallingAttributionTag;
         }
 
@@ -5280,6 +5303,8 @@
         public String toString() {
             return "uid/pid:" + mUid + "/" + mPid + " active request Id: "
                     + (mActiveRequest == null ? null : mActiveRequest.requestId)
+                    + " callback request Id: "
+                    + mNetworkRequestForCallback.requestId
                     + " " + mRequests
                     + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
         }
@@ -7136,7 +7161,7 @@
                         toUidRangeStableParcels(nri.getUids()));
             }
         } catch (RemoteException | ServiceSpecificException e) {
-            loge("Exception setting OEM network preference default network :" + e);
+            loge("Exception setting OEM network preference default network", e);
         }
     }
 
@@ -7191,13 +7216,13 @@
     private static class NetworkReassignment {
         static class RequestReassignment {
             @NonNull public final NetworkRequestInfo mNetworkRequestInfo;
-            @NonNull public final NetworkRequest mOldNetworkRequest;
-            @NonNull public final NetworkRequest mNewNetworkRequest;
+            @Nullable public final NetworkRequest mOldNetworkRequest;
+            @Nullable public final NetworkRequest mNewNetworkRequest;
             @Nullable public final NetworkAgentInfo mOldNetwork;
             @Nullable public final NetworkAgentInfo mNewNetwork;
             RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo,
-                    @NonNull final NetworkRequest oldNetworkRequest,
-                    @NonNull final NetworkRequest newNetworkRequest,
+                    @Nullable final NetworkRequest oldNetworkRequest,
+                    @Nullable final NetworkRequest newNetworkRequest,
                     @Nullable final NetworkAgentInfo oldNetwork,
                     @Nullable final NetworkAgentInfo newNetwork) {
                 mNetworkRequestInfo = networkRequestInfo;
@@ -7208,7 +7233,9 @@
             }
 
             public String toString() {
-                return mNetworkRequestInfo.mRequests.get(0).requestId + " : "
+                final NetworkRequest requestToShow = null != mNewNetworkRequest
+                        ? mNewNetworkRequest : mNetworkRequestInfo.mRequests.get(0);
+                return requestToShow.requestId + " : "
                         + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null")
                         + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null");
             }
@@ -7268,14 +7295,14 @@
     }
 
     private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
-            @NonNull final NetworkRequest previousRequest,
-            @NonNull final NetworkRequest newRequest,
+            @Nullable final NetworkRequest previousRequest,
+            @Nullable final NetworkRequest newRequest,
             @Nullable final NetworkAgentInfo previousSatisfier,
             @Nullable final NetworkAgentInfo newSatisfier,
             final long now) {
         if (null != newSatisfier && mNoServiceNetwork != newSatisfier) {
             if (VDBG) log("rematch for " + newSatisfier.toShortString());
-            if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) {
+            if (null != previousRequest && null != previousSatisfier) {
                 if (VDBG || DDBG) {
                     log("   accepting network in place of " + previousSatisfier.toShortString());
                 }
@@ -7292,12 +7319,13 @@
                 newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE);
             }
 
+            // if newSatisfier is not null, then newRequest may not be null.
             newSatisfier.unlingerRequest(newRequest.requestId);
             if (!newSatisfier.addRequest(newRequest)) {
                 Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
                         + newRequest);
             }
-        } else if (null != previousSatisfier) {
+        } else if (null != previousRequest && null != previousSatisfier) {
             if (DBG) {
                 log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
                         + " request " + previousRequest.requestId);
@@ -9011,7 +9039,7 @@
 
     private void handleSetOemNetworkPreference(
             @NonNull final OemNetworkPreferences preference,
-            @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
         Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
         if (DBG) {
             log("set OEM network preferences :" + preference.toString());
@@ -9023,7 +9051,11 @@
         // TODO http://b/176496396 persist data to shared preferences.
 
         if (null != listener) {
-            listener.onComplete();
+            try {
+                listener.onComplete();
+            } catch (RemoteException e) {
+                loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+            }
         }
     }
 
@@ -9039,10 +9071,10 @@
         mDefaultNetworkRequests.addAll(nris);
         final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
                 getPerAppCallbackRequestsToUpdate();
-        handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
         final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
         nrisToRegister.addAll(
                 createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
+        handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
         handleRegisterNetworkRequests(nrisToRegister);
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c5233f4..27b648e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -578,6 +578,12 @@
      */
     private static final int PBKDF2_HASH_ROUNDS = 1024;
 
+    private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY =
+            "anr_delay_millis";
+
+    private static final String ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY =
+            "anr_delay_notify_external_storage_service";
+
     /**
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
@@ -948,25 +954,51 @@
         }
     }
 
-    // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider
     private class ExternalStorageServiceAnrController implements AnrController {
         @Override
         public long getAnrDelayMillis(String packageName, int uid) {
-            int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0);
-            Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms");
+            if (!isAppIoBlocked(uid)) {
+                return 0;
+            }
+
+            int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                    ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0);
+            Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms");
             return delay;
         }
 
         @Override
         public void onAnrDelayStarted(String packageName, int uid) {
-            Log.d(TAG, "onAnrDelayStarted: " + packageName);
+            if (!isAppIoBlocked(uid)) {
+                return;
+            }
+
+            boolean notifyExternalStorageService = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                    ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY, true);
+            if (notifyExternalStorageService) {
+                Slog.d(TAG, "onAnrDelayStarted for " + packageName
+                        + ". Notifying external storage service");
+                try {
+                    mStorageSessionController.notifyAnrDelayStarted(packageName, uid, 0 /* tid */,
+                            StorageManager.APP_IO_BLOCKED_REASON_TRANSCODING);
+                } catch (ExternalStorageServiceException e) {
+                    Slog.e(TAG, "Failed to notify ANR delay started for " + packageName, e);
+                }
+            } else {
+                // TODO(b/170973510): Implement framework spinning dialog for ANR delay
+            }
         }
 
         @Override
         public boolean onAnrDelayCompleted(String packageName, int uid) {
-            boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true);
-            Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show);
-            return show;
+            if (isAppIoBlocked(uid)) {
+                Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Showing ANR dialog...");
+                return true;
+            } else {
+                Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Skipping ANR dialog...");
+                return false;
+            }
         }
     }
 
@@ -4690,5 +4722,19 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        @Override
+        public List<String> getPrimaryVolumeIds() {
+            final List<String> primaryVolumeIds = new ArrayList<>();
+            synchronized (mLock) {
+                for (int i = 0; i < mVolumes.size(); i++) {
+                    final VolumeInfo vol = mVolumes.valueAt(i);
+                    if (vol.isPrimary()) {
+                        primaryVolumeIds.add(vol.getId());
+                    }
+                }
+            }
+            return primaryVolumeIds;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 8d5d3d9..ad2f524 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -35,6 +35,7 @@
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnManager;
 import android.net.vcn.VcnManager.VcnErrorCode;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.net.wifi.WifiInfo;
@@ -724,6 +725,26 @@
         }
     }
 
+    private boolean isCallbackPermissioned(
+            @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) {
+        if (!subgroup.equals(cbInfo.mSubGroup)) {
+            return false;
+        }
+
+        if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) {
+            return false;
+        }
+
+        if (!mLocationPermissionChecker.checkLocationPermission(
+                cbInfo.mPkgName,
+                "VcnStatusCallback" /* featureId */,
+                cbInfo.mUid,
+                null /* message */)) {
+            return false;
+        }
+        return true;
+    }
+
     /** Registers the provided callback for receiving VCN status updates. */
     @Override
     public void registerVcnStatusCallback(
@@ -758,6 +779,27 @@
                 }
 
                 mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+
+                // now that callback is registered, send it the VCN's current status
+                final VcnConfig vcnConfig = mConfigs.get(subGroup);
+                final Vcn vcn = mVcns.get(subGroup);
+                final int vcnStatus;
+                if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) {
+                    vcnStatus = VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
+                } else if (vcn == null) {
+                    vcnStatus = VcnManager.VCN_STATUS_CODE_INACTIVE;
+                } else if (vcn.isActive()) {
+                    vcnStatus = VcnManager.VCN_STATUS_CODE_ACTIVE;
+                } else {
+                    // TODO(b/181789060): create Vcn.getStatus() and Log.WTF() for unknown status
+                    vcnStatus = VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+                }
+
+                try {
+                    cbInfo.mCallback.onVcnStatusChanged(vcnStatus);
+                } catch (RemoteException e) {
+                    Slog.d(TAG, "VcnStatusCallback threw on VCN status change", e);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -806,26 +848,6 @@
             mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
         }
 
-        private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) {
-            if (!mSubGroup.equals(cbInfo.mSubGroup)) {
-                return false;
-            }
-
-            if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
-                    mSubGroup, cbInfo.mPkgName)) {
-                return false;
-            }
-
-            if (!mLocationPermissionChecker.checkLocationPermission(
-                    cbInfo.mPkgName,
-                    "VcnStatusCallback" /* featureId */,
-                    cbInfo.mUid,
-                    null /* message */)) {
-                return false;
-            }
-            return true;
-        }
-
         @Override
         public void onEnteredSafeMode() {
             synchronized (mLock) {
@@ -838,7 +860,7 @@
 
                 // Notify all registered StatusCallbacks for this subGroup
                 for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
-                    if (isCallbackPermissioned(cbInfo)) {
+                    if (isCallbackPermissioned(cbInfo, mSubGroup)) {
                         Binder.withCleanCallingIdentity(
                                 () ->
                                         cbInfo.mCallback.onVcnStatusChanged(
@@ -862,7 +884,7 @@
 
                 // Notify all registered StatusCallbacks for this subGroup
                 for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
-                    if (isCallbackPermissioned(cbInfo)) {
+                    if (isCallbackPermissioned(cbInfo, mSubGroup)) {
                         Binder.withCleanCallingIdentity(
                                 () ->
                                         cbInfo.mCallback.onGatewayConnectionError(
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 3258f8a..d03a47a 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -329,6 +329,22 @@
             info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
         }
 
+        // Retrieve controller with max ANR delay from AnrControllers
+        // Note that we retrieve the controller before dumping stacks because dumping stacks can
+        // take a few seconds, after which the cause of the ANR delay might have completed and
+        // there might no longer be a valid ANR controller to cancel the dialog in that case
+        AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo);
+        long anrDialogDelayMs = 0;
+        if (anrController != null) {
+            String packageName = aInfo.packageName;
+            int uid = aInfo.uid;
+            anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid);
+            // Might execute an async binder call to a system app to show an interim
+            // ANR progress UI
+            anrController.onAnrDelayStarted(packageName, uid);
+            Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName);
+        }
+
         StringBuilder report = new StringBuilder();
         report.append(MemoryPressureUtil.currentPsiState());
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
@@ -417,20 +433,6 @@
             return;
         }
 
-        // Retrieve max ANR delay from AnrControllers without the mService lock since the
-        // controllers might in turn call into apps
-        AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo);
-        long anrDialogDelayMs = 0;
-        if (anrController != null) {
-            String packageName = aInfo.packageName;
-            int uid = aInfo.uid;
-            anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid);
-            // Might execute an async binder call to a system app to show an interim
-            // ANR progress UI
-            anrController.onAnrDelayStarted(packageName, uid);
-            Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName);
-        }
-
         synchronized (mService) {
             // mBatteryStatsService can be null if the AMS is constructed with injector only. This
             // will only happen in tests.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 8d6bcad..18f7a06865 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -666,8 +666,19 @@
                         mSelectRequestBuffer.process();
                         resetSelectRequestBuffer();
 
-                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
-                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
+                        List<HotplugDetectionAction> hotplugActions
+                                = getActions(HotplugDetectionAction.class);
+                        if (hotplugActions.isEmpty()) {
+                            addAndStartAction(
+                                    new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
+                        }
+
+                        List<PowerStatusMonitorAction> powerStatusActions
+                                = getActions(PowerStatusMonitorAction.class);
+                        if (powerStatusActions.isEmpty()) {
+                            addAndStartAction(
+                                    new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
+                        }
 
                         HdmiDeviceInfo avr = getAvrDeviceInfo();
                         if (avr != null) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index 8c40424..6f7473d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -106,9 +106,7 @@
         addHandler(Constants.MESSAGE_SET_STREAM_PATH, mBystander);
         addHandler(Constants.MESSAGE_STANDBY, mBystander);
         addHandler(Constants.MESSAGE_SET_MENU_LANGUAGE, mBystander);
-        addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBystander);
         addHandler(Constants.MESSAGE_USER_CONTROL_RELEASED, mBystander);
-        addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBystander);
         addHandler(Constants.MESSAGE_FEATURE_ABORT, mBystander);
         addHandler(Constants.MESSAGE_INACTIVE_SOURCE, mBystander);
         addHandler(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, mBystander);
@@ -133,6 +131,8 @@
         addHandler(Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID, mBypasser);
         addHandler(Constants.MESSAGE_GIVE_OSD_NAME, mBypasser);
         addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser);
+        addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser);
+        addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser);
 
         addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler);
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index c9067a3..b61fd8d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -33,9 +33,9 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 
 import java.util.Arrays;
 import java.util.function.Function;
@@ -169,8 +169,8 @@
         }
 
         ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg);
-        SparseArray<DomainVerificationUserState> userStates =
-                pkgState.getUserSelectionStates();
+        SparseArray<DomainVerificationInternalUserState> userStates =
+                pkgState.getUserStates();
         if (userId == UserHandle.USER_ALL) {
             int size = userStates.size();
             if (size == 0) {
@@ -178,13 +178,13 @@
                         wasHeaderPrinted);
             } else {
                 for (int index = 0; index < size; index++) {
-                    DomainVerificationUserState userState = userStates.valueAt(index);
+                    DomainVerificationInternalUserState userState = userStates.valueAt(index);
                     printState(writer, pkgState, userState.getUserId(), userState, reusedSet,
                             allWebDomains, wasHeaderPrinted);
                 }
             }
         } else {
-            DomainVerificationUserState userState = userStates.get(userId);
+            DomainVerificationInternalUserState userState = userStates.get(userId);
             printState(writer, pkgState, userId, userState, reusedSet, allWebDomains,
                     wasHeaderPrinted);
         }
@@ -192,8 +192,9 @@
 
     boolean printState(@NonNull IndentingPrintWriter writer,
             @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
-            @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet,
-            @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) {
+            @Nullable DomainVerificationInternalUserState userState,
+            @NonNull ArraySet<String> reusedSet, @NonNull ArraySet<String> allWebDomains,
+            boolean wasHeaderPrinted) {
         reusedSet.clear();
         reusedSet.addAll(allWebDomains);
         if (userState != null) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index ed37fa0..1721a18 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -132,6 +132,21 @@
     /**
      * Enforced when mutating user selection state inside an exposed API method.
      */
+    public boolean assertApprovedUserStateQuerent(int callingUid, @UserIdInt int callingUserId,
+            @NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException {
+        if (callingUserId != targetUserId) {
+            mContext.enforcePermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    Binder.getCallingPid(), callingUid,
+                    "Caller is not allowed to edit other users");
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+    }
+
+    /**
+     * Enforced when mutating user selection state inside an exposed API method.
+     */
     public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
             @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
         if (callingUserId != targetUserId) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index a68b3da..0c2b4c5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -48,7 +48,7 @@
 import java.util.UUID;
 import java.util.function.Function;
 
-public interface DomainVerificationManagerInternal extends DomainVerificationManager {
+public interface DomainVerificationManagerInternal {
 
     UUID DISABLED_ID = new UUID(0, 0);
 
@@ -69,8 +69,8 @@
      * during the legacy transition period.
      *
      * TODO(b/177923646): The legacy values can be removed once the Settings API changes are
-     *  shipped. These values are not stable, so just deleting the constant and shifting others is
-     *  fine.
+     * shipped. These values are not stable, so just deleting the constant and shifting others is
+     * fine.
      */
     int APPROVAL_LEVEL_LEGACY_ASK = 1;
 
@@ -84,14 +84,15 @@
 
     /**
      * The app has been chosen by the user through
-     * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
-     * choice to use this app to open an unverified domain.
+     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+     * indicating an explicit choice to use this app to open an unverified domain.
      */
     int APPROVAL_LEVEL_SELECTION = 2;
 
     /**
      * The app is approved through the digital asset link statement being hosted at the domain
-     * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
+     * it is capturing. This is set through
+     * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by
      * the domain verification agent on device.
      */
     int APPROVAL_LEVEL_VERIFIED = 3;
@@ -102,7 +103,7 @@
      * declares against the digital asset link statements before allowing it to be installed.
      *
      * The user is still able to disable instant app link handling through
-     * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+     * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}.
      */
     int APPROVAL_LEVEL_INSTANT_APP = 4;
 
@@ -122,7 +123,17 @@
             APPROVAL_LEVEL_VERIFIED,
             APPROVAL_LEVEL_INSTANT_APP
     })
-    @interface ApprovalLevel{}
+    @interface ApprovalLevel {
+    }
+
+    /** @see DomainVerificationManager#getDomainVerificationInfo(String) */
+    @Nullable
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+            android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+    })
+    DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+            throws NameNotFoundException;
 
     /**
      * Generate a new domain set ID to be used for attaching new packages.
@@ -173,9 +184,9 @@
     /**
      * Migrates verification state from a previous install to a new one. It is expected that the
      * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
-     * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the
-     * assumption that the new package will pass the same server side config as the previous
-     * package, as they have matching signatures.
+     * {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS}
+     * domains under the assumption that the new package will pass the same server side config as
+     * the previous package, as they have matching signatures.
      * <p>
      * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
      * lock. This should never be called from within the domain verification classes themselves.
@@ -229,8 +240,10 @@
      * tag has already been entered.
      * <p>
      * This is <b>only</b> for restore, and will override package states, ignoring if their {@link
-     * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked
-     * as success verify against the server correctly, although the verification agent may decide to
+     * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains
+     * marked
+     * as success verify against the server correctly, although the verification agent may decide
+     * to
      * re-verify them when it gets the chance.
      */
     /*
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 6f28107..a7a52e0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -23,29 +23,29 @@
 import android.content.pm.verify.domain.DomainOwner;
 import android.content.pm.verify.domain.DomainSet;
 import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
-import android.content.pm.verify.domain.DomainVerificationManagerImpl;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.content.pm.verify.domain.IDomainVerificationManager;
 import android.os.ServiceSpecificException;
 
 import java.util.List;
 import java.util.UUID;
 
-class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
+public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
 
     @NonNull
-    private DomainVerificationService mService;
+    private final DomainVerificationService mService;
 
-    DomainVerificationManagerStub(DomainVerificationService service) {
+    public DomainVerificationManagerStub(DomainVerificationService service) {
         mService = service;
     }
 
     @NonNull
     @Override
-    public List<String> getValidVerificationPackageNames() {
+    public List<String> queryValidVerificationPackageNames() {
         try {
-            return mService.getValidVerificationPackageNames();
+            return mService.queryValidVerificationPackageNames();
         } catch (Exception e) {
             throw rethrow(e);
         }
@@ -95,10 +95,10 @@
 
     @Nullable
     @Override
-    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+    public DomainVerificationUserState getDomainVerificationUserState(
             String packageName, @UserIdInt int userId) {
         try {
-            return mService.getDomainVerificationUserSelection(packageName, userId);
+            return mService.getDomainVerificationUserState(packageName, userId);
         } catch (Exception e) {
             throw rethrow(e);
         }
@@ -117,13 +117,13 @@
 
     private RuntimeException rethrow(Exception exception) throws RuntimeException {
         if (exception instanceof InvalidDomainSetException) {
-            int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
+            int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET;
             packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
             return new ServiceSpecificException(packedErrorCode,
                     ((InvalidDomainSetException) exception).getPackageName());
         } else if (exception instanceof NameNotFoundException) {
             return new ServiceSpecificException(
-                    DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND);
+                    DomainVerificationManager.ERROR_NAME_NOT_FOUND);
         } else if (exception instanceof RuntimeException) {
             return (RuntimeException) exception;
         } else {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index c864b29..abb8d2f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -27,9 +27,9 @@
 import android.util.TypedXmlSerializer;
 
 import com.android.server.pm.SettingsXml;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -157,7 +157,7 @@
         UUID id = UUID.fromString(idString);
 
         final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
-        final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>();
+        final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>();
 
         SettingsXml.ChildSection child = section.children();
         while (child.moveToNext()) {
@@ -176,10 +176,10 @@
     }
 
     private static void readUserStates(@NonNull SettingsXml.ReadSection section,
-            @NonNull SparseArray<DomainVerificationUserState> userStates) {
+            @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
         SettingsXml.ChildSection child = section.children();
         while (child.moveToNext(TAG_USER_STATE)) {
-            DomainVerificationUserState userState = createUserStateFromXml(child);
+            DomainVerificationInternalUserState userState = createUserStateFromXml(child);
             if (userState != null) {
                 userStates.put(userState.getUserId(), userState);
             }
@@ -205,12 +205,12 @@
                              .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
                                      pkgState.isHasAutoVerifyDomains())) {
             writeStateMap(parentSection, pkgState.getStateMap());
-            writeUserStates(parentSection, pkgState.getUserSelectionStates());
+            writeUserStates(parentSection, pkgState.getUserStates());
         }
     }
 
     private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
-            @NonNull SparseArray<DomainVerificationUserState> states) throws IOException {
+            @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
         int size = states.size();
         if (size == 0) {
             return;
@@ -245,7 +245,7 @@
      * entered.
      */
     @Nullable
-    public static DomainVerificationUserState createUserStateFromXml(
+    public static DomainVerificationInternalUserState createUserStateFromXml(
             @NonNull SettingsXml.ReadSection section) {
         int userId = section.getInt(ATTR_USER_ID);
         if (userId == -1) {
@@ -260,7 +260,7 @@
             readEnabledHosts(child, enabledHosts);
         }
 
-        return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
+        return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling);
     }
 
     private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -275,7 +275,7 @@
     }
 
     public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
-            @NonNull DomainVerificationUserState userState) throws IOException {
+            @NonNull DomainVerificationInternalUserState userState) throws IOException {
         try (SettingsXml.WriteSection section =
                      parentSection.startSection(TAG_USER_STATE)
                              .attribute(ATTR_USER_ID, userState.getUserId())
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index dbd7f96..e85bbe4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -34,8 +34,9 @@
 import android.content.pm.verify.domain.DomainOwner;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
 import android.content.pm.verify.domain.DomainVerificationState;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.content.pm.verify.domain.IDomainVerificationManager;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -55,9 +56,9 @@
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
 
@@ -208,8 +209,7 @@
     }
 
     @NonNull
-    @Override
-    public List<String> getValidVerificationPackageNames() {
+    public List<String> queryValidVerificationPackageNames() {
         mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
         List<String> packageNames = new ArrayList<>();
         synchronized (mLock) {
@@ -272,7 +272,6 @@
         }
     }
 
-    @Override
     public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
             int state) throws InvalidDomainSetException, NameNotFoundException {
         if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
@@ -314,7 +313,7 @@
 
             int size = verifiedDomains.size();
             for (int index = 0; index < size; index++) {
-                removeUserSelectionsForDomain(verifiedDomains.get(index));
+                removeUserStatesForDomain(verifiedDomains.get(index));
             }
         }
 
@@ -401,12 +400,12 @@
         }
     }
 
-    private void removeUserSelectionsForDomain(@NonNull String domain) {
+    private void removeUserStatesForDomain(@NonNull String domain) {
         synchronized (mLock) {
             final int size = mAttachedPkgStates.size();
             for (int index = 0; index < size; index++) {
                 DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
-                SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates();
+                SparseArray<DomainVerificationInternalUserState> array = pkgState.getUserStates();
                 int arraySize = array.size();
                 for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) {
                     array.valueAt(arrayIndex).removeHost(domain);
@@ -415,13 +414,6 @@
         }
     }
 
-    @Override
-    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
-            boolean allowed) throws NameNotFoundException {
-        setDomainVerificationLinkHandlingAllowed(packageName, allowed,
-                mConnection.getCallingUserId());
-    }
-
     public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
             boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
         if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
@@ -434,7 +426,7 @@
                 throw DomainVerificationUtils.throwPackageUnavailable(packageName);
             }
 
-            pkgState.getOrCreateUserSelectionState(userId)
+            pkgState.getOrCreateUserState(userId)
                     .setLinkHandlingAllowed(allowed);
         }
 
@@ -452,11 +444,11 @@
                     DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
                     if (userId == UserHandle.USER_ALL) {
                         for (int aUserId : mConnection.getAllUserIds()) {
-                            pkgState.getOrCreateUserSelectionState(aUserId)
+                            pkgState.getOrCreateUserState(aUserId)
                                     .setLinkHandlingAllowed(allowed);
                         }
                     } else {
-                        pkgState.getOrCreateUserSelectionState(userId)
+                        pkgState.getOrCreateUserState(userId)
                                 .setLinkHandlingAllowed(allowed);
                     }
                 }
@@ -468,7 +460,7 @@
                     throw DomainVerificationUtils.throwPackageUnavailable(packageName);
                 }
 
-                pkgState.getOrCreateUserSelectionState(userId)
+                pkgState.getOrCreateUserState(userId)
                         .setLinkHandlingAllowed(allowed);
             }
         }
@@ -476,14 +468,6 @@
         mConnection.scheduleWriteSettings();
     }
 
-    @Override
-    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
-            @NonNull Set<String> domains, boolean enabled)
-            throws InvalidDomainSetException, NameNotFoundException {
-        setDomainVerificationUserSelection(domainSetId, domains, enabled,
-                mConnection.getCallingUserId());
-    }
-
     public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
             @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
             throws InvalidDomainSetException, NameNotFoundException {
@@ -500,7 +484,8 @@
 
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
                     false /* forAutoVerify */, callingUid, userId);
-            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+            DomainVerificationInternalUserState userState =
+                    pkgState.getOrCreateUserState(userId);
 
             // Disable other packages if approving this one. Note that this check is only done for
             // enabling. This allows an escape hatch in case multiple packages somehow get selected.
@@ -540,8 +525,8 @@
                             continue;
                         }
 
-                        DomainVerificationUserState approvedUserState =
-                                approvedPkgState.getUserSelectionState(userId);
+                        DomainVerificationInternalUserState approvedUserState =
+                                approvedPkgState.getUserState(userId);
                         if (approvedUserState == null) {
                             continue;
                         }
@@ -623,8 +608,8 @@
 
         if (userId == UserHandle.USER_ALL) {
             for (int aUserId : mConnection.getAllUserIds()) {
-                DomainVerificationUserState userState =
-                        pkgState.getOrCreateUserSelectionState(aUserId);
+                DomainVerificationInternalUserState userState =
+                        pkgState.getOrCreateUserState(aUserId);
                 if (enabled) {
                     userState.addHosts(domains);
                 } else {
@@ -632,7 +617,8 @@
                 }
             }
         } else {
-            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+            DomainVerificationInternalUserState userState =
+                    pkgState.getOrCreateUserState(userId);
             if (enabled) {
                 userState.addHosts(domains);
             } else {
@@ -643,17 +629,9 @@
 
     @Nullable
     @Override
-    public DomainVerificationUserSelection getDomainVerificationUserSelection(
-            @NonNull String packageName) throws NameNotFoundException {
-        return getDomainVerificationUserSelection(packageName,
-                mConnection.getCallingUserId());
-    }
-
-    @Nullable
-    @Override
-    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+    public DomainVerificationUserState getDomainVerificationUserState(
             @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
-        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+        if (!mEnforcer.assertApprovedUserStateQuerent(mConnection.getCallingUid(),
                 mConnection.getCallingUserId(), packageName, userId)) {
             throw DomainVerificationUtils.throwPackageUnavailable(packageName);
         }
@@ -673,7 +651,7 @@
 
             Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
             Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts();
 
             for (int index = 0; index < webDomainsSize; index++) {
@@ -682,11 +660,11 @@
 
                 int domainState;
                 if (state != null && DomainVerificationManager.isStateVerified(state)) {
-                    domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED;
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
                 } else if (enabledHosts.contains(host)) {
-                    domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED;
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
                 } else {
-                    domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE;
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
                 }
 
                 domains.put(host, domainState);
@@ -694,17 +672,11 @@
 
             boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
 
-            return new DomainVerificationUserSelection(pkgState.getId(), packageName,
+            return new DomainVerificationUserState(pkgState.getId(), packageName,
                     UserHandle.of(userId), linkHandlingAllowed, domains);
         }
     }
 
-    @NonNull
-    @Override
-    public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
-        return getOwnersForDomain(domain, mConnection.getCallingUserId());
-    }
-
     public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
         mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
                 userId);
@@ -795,7 +767,7 @@
             AndroidPackage newPkg = newPkgSetting.getPkg();
 
             ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
-            SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();
+            SparseArray<DomainVerificationInternalUserState> newUserStates = new SparseArray<>();
 
             if (oldPkgState == null || oldPkg == null || newPkg == null) {
                 // Should be impossible, but to be safe, continue with a new blank state instead
@@ -838,21 +810,22 @@
                 }
             }
 
-            SparseArray<DomainVerificationUserState> oldUserStates =
-                    oldPkgState.getUserSelectionStates();
+            SparseArray<DomainVerificationInternalUserState> oldUserStates =
+                    oldPkgState.getUserStates();
             int oldUserStatesSize = oldUserStates.size();
             if (oldUserStatesSize > 0) {
                 ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg);
                 for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
                         oldUserStatesIndex++) {
                     int userId = oldUserStates.keyAt(oldUserStatesIndex);
-                    DomainVerificationUserState oldUserState = oldUserStates.valueAt(
+                    DomainVerificationInternalUserState oldUserState = oldUserStates.valueAt(
                             oldUserStatesIndex);
                     ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
                     ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
                     newEnabledHosts.retainAll(newWebDomains);
-                    DomainVerificationUserState newUserState = new DomainVerificationUserState(
-                            userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
+                    DomainVerificationInternalUserState newUserState =
+                            new DomainVerificationInternalUserState(userId, newEnabledHosts,
+                                    oldUserState.isLinkHandlingAllowed());
                     newUserStates.put(userId, newUserState);
                 }
             }
@@ -926,7 +899,7 @@
                         webDomains = mCollector.collectAllWebDomains(pkg);
                     }
 
-                    pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
+                    pkgState.getOrCreateUserState(userId).addHosts(webDomains);
                 }
             }
 
@@ -1295,7 +1268,7 @@
     }
 
     @Override
-    public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) {
+    public void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId) {
         mEnforcer.assertInternal(mConnection.getCallingUid());
         synchronized (mLock) {
             if (packageNames == null) {
@@ -1545,7 +1518,7 @@
                 return APPROVAL_LEVEL_NONE;
             }
 
-            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
 
             if (userState != null && !userState.isLinkHandlingAllowed()) {
                 if (DEBUG_APPROVAL) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index a8e937c..f3d1dbb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -29,9 +29,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -216,21 +216,22 @@
             }
         }
 
-        SparseArray<DomainVerificationUserState> oldSelectionStates =
-                oldState.getUserSelectionStates();
+        SparseArray<DomainVerificationInternalUserState> oldSelectionStates =
+                oldState.getUserStates();
 
-        SparseArray<DomainVerificationUserState> newSelectionStates =
-                newState.getUserSelectionStates();
+        SparseArray<DomainVerificationInternalUserState> newSelectionStates =
+                newState.getUserStates();
 
-        DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM);
+        DomainVerificationInternalUserState newUserState =
+                newSelectionStates.get(UserHandle.USER_SYSTEM);
         if (newUserState != null) {
             ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
-            DomainVerificationUserState oldUserState =
+            DomainVerificationInternalUserState oldUserState =
                     oldSelectionStates.get(UserHandle.USER_SYSTEM);
 
             boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
             if (oldUserState == null) {
-                oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
+                oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM,
                         newEnabledHosts, linkHandlingAllowed);
                 oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
             } else {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index d083d11..94767f5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -24,7 +24,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
-import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.os.Binder;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -118,7 +118,7 @@
             case "set-app-links":
                 return runSetAppLinks(commandHandler);
             case "set-app-links-user-selection":
-                return runSetAppLinksUserSelection(commandHandler);
+                return runSetAppLinksUserState(commandHandler);
             case "set-app-links-allowed":
                 return runSetAppLinksAllowed(commandHandler);
         }
@@ -193,7 +193,7 @@
     }
 
     // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>...
-    private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) {
+    private boolean runSetAppLinksUserState(@NonNull BasicShellCommandHandler commandHandler) {
         Integer userId = null;
         String packageName = null;
 
@@ -224,7 +224,7 @@
             return false;
         }
 
-        userId = translateUserId(userId, "runSetAppLinksUserSelection");
+        userId = translateUserId(userId, "runSetAppLinksUserState");
 
         String enabledString = commandHandler.getNextArgRequired();
 
@@ -326,7 +326,7 @@
         }
 
         if (userId != null) {
-            mCallback.clearUserSelections(packageNames, userId);
+            mCallback.clearUserStates(packageNames, userId);
         } else {
             mCallback.clearDomainVerificationState(packageNames);
         }
@@ -457,10 +457,10 @@
                 throws PackageManager.NameNotFoundException;
 
         /**
-         * @see DomainVerificationManager#getDomainVerificationUserSelection(String)
+         * @see DomainVerificationManager#getDomainVerificationUserState(String)
          */
         @Nullable
-        DomainVerificationUserSelection getDomainVerificationUserSelection(
+        DomainVerificationUserState getDomainVerificationUserState(
                 @NonNull String packageName, @UserIdInt int userId)
                 throws PackageManager.NameNotFoundException;
 
@@ -486,7 +486,7 @@
          * Reset all the user selections for the given package names, or all package names if null
          * is provided.
          */
-        void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId);
+        void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId);
 
         /**
          * Broadcast a verification request for the given package names, or all package names if
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
similarity index 74%
rename from services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
rename to services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
index 8fbb33a..aa7407c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
@@ -29,7 +29,7 @@
  * when a web URL Intent is sent and the application is the highest priority for that domain.
  */
 @DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
-public class DomainVerificationUserState {
+public class DomainVerificationInternalUserState {
 
     @UserIdInt
     private final int mUserId;
@@ -43,32 +43,32 @@
      */
     private boolean mLinkHandlingAllowed = true;
 
-    public DomainVerificationUserState(@UserIdInt int userId) {
+    public DomainVerificationInternalUserState(@UserIdInt int userId) {
         mUserId = userId;
         mEnabledHosts = new ArraySet<>();
     }
 
-    public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) {
+    public DomainVerificationInternalUserState addHosts(@NonNull ArraySet<String> newHosts) {
         mEnabledHosts.addAll(newHosts);
         return this;
     }
 
-    public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) {
+    public DomainVerificationInternalUserState addHosts(@NonNull Set<String> newHosts) {
         mEnabledHosts.addAll(newHosts);
         return this;
     }
 
-    public DomainVerificationUserState removeHost(String host) {
+    public DomainVerificationInternalUserState removeHost(String host) {
         mEnabledHosts.remove(host);
         return this;
     }
 
-    public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
+    public DomainVerificationInternalUserState removeHosts(@NonNull ArraySet<String> newHosts) {
         mEnabledHosts.removeAll(newHosts);
         return this;
     }
 
-    public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) {
+    public DomainVerificationInternalUserState removeHosts(@NonNull Set<String> newHosts) {
         mEnabledHosts.removeAll(newHosts);
         return this;
     }
@@ -81,8 +81,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
-    // /verify/domain/models/DomainVerificationUserState.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -90,7 +89,7 @@
 
 
     /**
-     * Creates a new DomainVerificationUserState.
+     * Creates a new DomainVerificationInternalUserState.
      *
      * @param enabledHosts
      *   List of domains which have been enabled by the user. *
@@ -98,7 +97,7 @@
      *   Whether to allow this package to automatically open links by auto verification.
      */
     @DataClass.Generated.Member
-    public DomainVerificationUserState(
+    public DomainVerificationInternalUserState(
             @UserIdInt int userId,
             @NonNull ArraySet<String> enabledHosts,
             boolean linkHandlingAllowed) {
@@ -138,7 +137,7 @@
      * Whether to allow this package to automatically open links by auto verification.
      */
     @DataClass.Generated.Member
-    public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+    public @NonNull DomainVerificationInternalUserState setLinkHandlingAllowed( boolean value) {
         mLinkHandlingAllowed = value;
         return this;
     }
@@ -149,7 +148,7 @@
         // You can override field toString logic by defining methods like:
         // String fieldNameToString() { ... }
 
-        return "DomainVerificationUserState { " +
+        return "DomainVerificationInternalUserState { " +
                 "userId = " + mUserId + ", " +
                 "enabledHosts = " + mEnabledHosts + ", " +
                 "linkHandlingAllowed = " + mLinkHandlingAllowed +
@@ -160,13 +159,13 @@
     @DataClass.Generated.Member
     public boolean equals(@android.annotation.Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+        // boolean fieldNameEquals(DomainVerificationInternalUserState other) { ... }
         // boolean fieldNameEquals(FieldType otherValue) { ... }
 
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         @SuppressWarnings("unchecked")
-        DomainVerificationUserState that = (DomainVerificationUserState) o;
+        DomainVerificationInternalUserState that = (DomainVerificationInternalUserState) o;
         //noinspection PointlessBooleanExpression
         return true
                 && mUserId == that.mUserId
@@ -188,10 +187,10 @@
     }
 
     @DataClass.Generated(
-            time = 1612894390039L,
+            time = 1614714563905L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
-            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mLinkHandlingAllowed\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java",
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mLinkHandlingAllowed\npublic  com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHost(java.lang.String)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationInternalUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
index 48099aa..a089a60 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.util.ArrayMap;
 import android.util.SparseArray;
@@ -44,9 +43,9 @@
 
     /**
      * Whether or not the package declares any autoVerify domains. This is separate from an empty
-     * check on the map itself, because an empty map means no response recorded, not necessarily no
-     * domains declared. When this is false, {@link #mStateMap} will be empty, but
-     * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to
+     * check on the map itself, because an empty map means no response recorded, not necessarily
+     * no domains declared. When this is false, {@link #mStateMap} will be empty, but
+     * {@link #mUserStates} may contain any domains the user has explicitly chosen to
      * allow this package to open, which may or may not be marked autoVerify.
      */
     private final boolean mHasAutoVerifyDomains;
@@ -62,7 +61,7 @@
     private final ArrayMap<String, Integer> mStateMap;
 
     @NonNull
-    private final SparseArray<DomainVerificationUserState> mUserSelectionStates;
+    private final SparseArray<DomainVerificationInternalUserState> mUserStates;
 
     public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
             boolean hasAutoVerifyDomains) {
@@ -70,16 +69,17 @@
     }
 
     @Nullable
-    public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) {
-        return mUserSelectionStates.get(userId);
+    public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) {
+        return mUserStates.get(userId);
     }
 
     @Nullable
-    public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) {
-        DomainVerificationUserState userState = mUserSelectionStates.get(userId);
+    public DomainVerificationInternalUserState getOrCreateUserState(
+            @UserIdInt int userId) {
+        DomainVerificationInternalUserState userState = mUserStates.get(userId);
         if (userState == null) {
-            userState = new DomainVerificationUserState(userId);
-            mUserSelectionStates.put(userId, userState);
+            userState = new DomainVerificationInternalUserState(userId);
+            mUserStates.put(userId, userState);
         }
         return userState;
     }
@@ -89,20 +89,20 @@
     }
 
     public void removeUser(@UserIdInt int userId) {
-        mUserSelectionStates.remove(userId);
+        mUserStates.remove(userId);
     }
 
     public void removeAllUsers() {
-        mUserSelectionStates.clear();
+        mUserStates.clear();
     }
 
-    private int userSelectionStatesHashCode() {
-        return mUserSelectionStates.contentHashCode();
+    private int userStatesHashCode() {
+        return mUserStates.contentHashCode();
     }
 
-    private boolean userSelectionStatesEquals(
-            @NonNull SparseArray<DomainVerificationUserState> other) {
-        return mUserSelectionStates.contentEquals(other);
+    private boolean userStatesEquals(
+            @NonNull SparseArray<DomainVerificationInternalUserState> other) {
+        return mUserStates.contentEquals(other);
     }
 
 
@@ -113,7 +113,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -123,9 +123,15 @@
     /**
      * Creates a new DomainVerificationPkgState.
      *
+     * @param hasAutoVerifyDomains
+     *   Whether or not the package declares any autoVerify domains. This is separate from an empty
+     *   check on the map itself, because an empty map means no response recorded, not necessarily
+     *   no domains declared. When this is false, {@link #mStateMap} will be empty, but
+     *   {@link #mUserStates} may contain any domains the user has explicitly chosen to
+     *   allow this package to open, which may or may not be marked autoVerify.
      * @param stateMap
      *   Map of domains to state integers. Only domains that are not set to the default value of
-     *   {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+     *   {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
      *
      *   TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
      *    such as storing no state when the package is marked as a linked app in SystemConfig.
@@ -136,7 +142,7 @@
             @NonNull UUID id,
             boolean hasAutoVerifyDomains,
             @NonNull ArrayMap<String,Integer> stateMap,
-            @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) {
+            @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
         this.mPackageName = packageName;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPackageName);
@@ -147,9 +153,9 @@
         this.mStateMap = stateMap;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mStateMap);
-        this.mUserSelectionStates = userSelectionStates;
+        this.mUserStates = userStates;
         com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mUserSelectionStates);
+                NonNull.class, null, mUserStates);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -164,6 +170,13 @@
         return mId;
     }
 
+    /**
+     * Whether or not the package declares any autoVerify domains. This is separate from an empty
+     * check on the map itself, because an empty map means no response recorded, not necessarily
+     * no domains declared. When this is false, {@link #mStateMap} will be empty, but
+     * {@link #mUserStates} may contain any domains the user has explicitly chosen to
+     * allow this package to open, which may or may not be marked autoVerify.
+     */
     @DataClass.Generated.Member
     public boolean isHasAutoVerifyDomains() {
         return mHasAutoVerifyDomains;
@@ -171,7 +184,7 @@
 
     /**
      * Map of domains to state integers. Only domains that are not set to the default value of
-     * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+     * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
      *
      * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
      *  such as storing no state when the package is marked as a linked app in SystemConfig.
@@ -182,8 +195,8 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() {
-        return mUserSelectionStates;
+    public @NonNull SparseArray<DomainVerificationInternalUserState> getUserStates() {
+        return mUserStates;
     }
 
     @Override
@@ -197,7 +210,7 @@
                 "id = " + mId + ", " +
                 "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
                 "stateMap = " + mStateMap + ", " +
-                "userSelectionStates = " + mUserSelectionStates +
+                "userStates = " + mUserStates +
         " }";
     }
 
@@ -218,7 +231,7 @@
                 && Objects.equals(mId, that.mId)
                 && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
                 && Objects.equals(mStateMap, that.mStateMap)
-                && userSelectionStatesEquals(that.mUserSelectionStates);
+                && userStatesEquals(that.mUserStates);
     }
 
     @Override
@@ -232,15 +245,15 @@
         _hash = 31 * _hash + Objects.hashCode(mId);
         _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
         _hash = 31 * _hash + Objects.hashCode(mStateMap);
-        _hash = 31 * _hash + userSelectionStatesHashCode();
+        _hash = 31 * _hash + userStatesHashCode();
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1608234185474L,
+            time = 1614818362549L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic  void setId(java.util.UUID)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userSelectionStatesHashCode()\nprivate  boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic  void setId(java.util.UUID)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userStatesHashCode()\nprivate  boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 6aa7c8a..7ed7a59 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3759,13 +3759,15 @@
             ConnectivityManager.NetworkCallback {
         @Override
         public void onAvailable(Network network) {
-            FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+            FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED,
+                    network.getNetId(),
                     FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED);
         }
 
         @Override
         public void onLost(Network network) {
-            FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+            FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED,
+                    network.getNetId(),
                     FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED);
         }
     }
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index d2b05c0..9409eb5 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -53,11 +53,13 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService}
@@ -72,6 +74,7 @@
     private final Context mContext;
     private final int mUserId;
     private final StorageSessionController mSessionController;
+    private final StorageManagerInternal mSmInternal;
     private final ActiveConnection mActiveConnection = new ActiveConnection();
     @GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>();
     @GuardedBy("mLock") private final Set<Integer> mUidsBlockedOnIo = new ArraySet<>();
@@ -81,6 +84,7 @@
         mContext = Objects.requireNonNull(context);
         mUserId = Preconditions.checkArgumentNonnegative(userId);
         mSessionController = controller;
+        mSmInternal = LocalServices.getService(StorageManagerInternal.class);
         mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId);
         mHandlerThread.start();
     }
@@ -152,9 +156,13 @@
      */
     public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason)
             throws ExternalStorageServiceException {
+        List<String> primarySessionIds = mSmInternal.getPrimaryVolumeIds();
         synchronized (mSessionsLock) {
             for (String sessionId : mSessions.keySet()) {
-                mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason);
+                if (primarySessionIds.contains(sessionId)) {
+                    mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason);
+                    return;
+                }
             }
         }
     }
@@ -201,8 +209,7 @@
                 return;
             }
         }
-        StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class);
-        sm.resetUser(mUserId);
+        mSmInternal.resetUser(mUserId);
     }
 
     /**
@@ -317,6 +324,23 @@
             }
         }
 
+        private void asyncBestEffort(Consumer<IExternalStorageService> consumer) {
+            synchronized (mLock) {
+                if (mRemoteFuture == null) {
+                    Slog.w(TAG, "Dropping async request service is not bound");
+                    return;
+                }
+
+                IExternalStorageService service = mRemoteFuture.getNow(null);
+                if (service == null) {
+                    Slog.w(TAG, "Dropping async request service is not connected");
+                    return;
+                }
+
+                consumer.accept(service);
+            }
+        }
+
         private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
             CompletableFuture<Void> opFuture = new CompletableFuture<>();
             RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
@@ -401,13 +425,13 @@
 
         public void notifyAnrDelayStarted(String packgeName, int uid, int tid, int reason)
                 throws ExternalStorageServiceException {
-            try {
-                waitForAsyncVoid((service, callback) ->
-                        service.notifyAnrDelayStarted(packgeName, uid, tid, reason, callback));
-            } catch (Exception e) {
-                throw new ExternalStorageServiceException("Failed to notify ANR delay started: "
-                        + packgeName, e);
-            }
+            asyncBestEffort(service -> {
+                try {
+                    service.notifyAnrDelayStarted(packgeName, uid, tid, reason);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to notify ANR delay started", e);
+                }
+            });
         }
 
         private void setResult(Bundle result, CompletableFuture<Void> future) {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 607da3c..e84ee672 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -120,7 +120,9 @@
         if (newEffect.equals(mEffect)) {
             return;
         }
-        mOriginalEffect = mEffect;
+        if (mOriginalEffect == null) {
+            mOriginalEffect = mEffect;
+        }
         mEffect = newEffect;
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 90a763c..c9751bb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -344,10 +344,11 @@
             if (!isEffectValid(effect)) {
                 return;
             }
-            effect = fixupVibrationEffect(effect);
             attrs = fixupVibrationAttributes(attrs);
             Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
                     uid, opPkg, reason);
+            // Update with fixed up effect to keep the original effect in Vibration for debugging.
+            vib.updateEffect(fixupVibrationEffect(effect));
 
             synchronized (mLock) {
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
@@ -1138,6 +1139,9 @@
         void dumpText(PrintWriter pw) {
             pw.println("Vibrator Manager Service:");
             synchronized (mLock) {
+                pw.println("  mVibrationSettings:");
+                pw.println("    " + mVibrationSettings);
+                pw.println();
                 pw.println("  mVibratorControllers:");
                 for (int i = 0; i < mVibrators.size(); i++) {
                     pw.println("    " + mVibrators.valueAt(i));
@@ -1146,14 +1150,15 @@
                 pw.println("  mCurrentVibration:");
                 pw.println("    " + (mCurrentVibration == null
                         ? null : mCurrentVibration.getVibration().getDebugInfo()));
+                pw.println();
                 pw.println("  mNextVibration:");
                 pw.println("    " + (mNextVibration == null
                         ? null : mNextVibration.getVibration().getDebugInfo()));
+                pw.println();
                 pw.println("  mCurrentExternalVibration:");
                 pw.println("    " + (mCurrentExternalVibration == null
                         ? null : mCurrentExternalVibration.getDebugInfo()));
                 pw.println();
-                pw.println("  mVibrationSettings=" + mVibrationSettings);
                 for (int i = 0; i < mPreviousVibrations.size(); i++) {
                     pw.println();
                     pw.print("  Previous vibrations for usage ");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5932388..5562580 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -214,6 +214,7 @@
 import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
@@ -580,9 +581,6 @@
 
     private Task mLastParent;
 
-    // Have we told the window clients to show themselves?
-    private boolean mClientVisible;
-
     boolean firstWindowDrawn;
     /** Whether the visible window(s) of this activity is drawn. */
     private boolean mReportedDrawn;
@@ -998,7 +996,7 @@
         pw.print(prefix); pw.print("mOrientation=");
         pw.println(ActivityInfo.screenOrientationToString(mOrientation));
         pw.println(prefix + "mVisibleRequested=" + mVisibleRequested
-                + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible
+                + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible()
                 + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
                 + " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible);
         if (paused) {
@@ -1080,6 +1078,46 @@
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
         }
+
+        dumpLetterboxInfo(pw, prefix);
+    }
+
+    private void dumpLetterboxInfo(PrintWriter pw, String prefix) {
+        final WindowState mainWin = findMainWindow();
+        if (mainWin == null) {
+            return;
+        }
+
+        boolean isLetterboxed = isLetterboxed(mainWin);
+        pw.println(prefix + "isLetterboxed=" + isLetterboxed);
+        if (!isLetterboxed) {
+            return;
+        }
+
+        pw.println(prefix + "  letterboxReason=" + getLetterboxReasonString(mainWin));
+        pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
+                getLetterboxBackgroundColor().toArgb()));
+        pw.println(prefix + "  letterboxBackgroundType="
+                + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType()));
+        pw.println(prefix + "  letterboxAspectRatio="
+                + computeAspectRatio(getBounds()));
+    }
+
+    /**
+     * Returns a string representing the reason for letterboxing. This method assumes the activity
+     * is letterboxed.
+     */
+    private String getLetterboxReasonString(WindowState mainWin) {
+        if (inSizeCompatMode()) {
+            return "SIZE_COMPAT_MODE";
+        }
+        if (isLetterboxedForFixedOrientationAndAspectRatio()) {
+            return "FIXED_ORIENTATION";
+        }
+        if (mainWin.isLetterboxedForDisplayCutout()) {
+            return "DISPLAY_CUTOUT";
+        }
+        return "UNKNOWN_REASON";
     }
 
     void setAppTimeTracker(AppTimeTracker att) {
@@ -1695,7 +1733,7 @@
         keysPaused = false;
         inHistory = false;
         nowVisible = false;
-        mClientVisible = true;
+        super.setClientVisible(true);
         idle = false;
         hasBeenLaunched = false;
         mTaskSupervisor = supervisor;
@@ -3735,7 +3773,7 @@
                     setVisibleRequested(true);
                     mVisibleSetFromTransferredStartingWindow = true;
                 }
-                setClientVisible(fromActivity.mClientVisible);
+                setClientVisible(fromActivity.isClientVisible());
 
                 if (fromActivity.isAnimating()) {
                     transferAnimation(fromActivity);
@@ -5836,18 +5874,15 @@
         return mReportedDrawn;
     }
 
-    boolean isClientVisible() {
-        return mClientVisible;
-    }
-
+    @Override
     void setClientVisible(boolean clientVisible) {
-        if (mClientVisible == clientVisible || (!clientVisible && mDeferHidingClient)) {
-            return;
-        }
+        // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions.
+        //                          pip activities should just remain in clientVisible.
+        if (!clientVisible && mDeferHidingClient) return;
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible,
                 Debug.getCallers(5));
-        mClientVisible = clientVisible;
+        super.setClientVisible(clientVisible);
         sendAppVisibilityToClients();
     }
 
@@ -7391,8 +7426,7 @@
 
         final int containingAppWidth = containingAppBounds.width();
         final int containingAppHeight = containingAppBounds.height();
-        final float containingRatio = Math.max(containingAppWidth, containingAppHeight)
-                / (float) Math.min(containingAppWidth, containingAppHeight);
+        final float containingRatio = computeAspectRatio(containingAppBounds);
 
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
@@ -7456,6 +7490,18 @@
     }
 
     /**
+     * Returns the aspect ratio of the given {@code rect}.
+     */
+    private static float computeAspectRatio(Rect rect) {
+        final int width = rect.width();
+        final int height = rect.height();
+        if (width == 0 || height == 0) {
+            return 0;
+        }
+        return Math.max(width, height) / (float) Math.min(width, height);
+    }
+
+    /**
      * @return {@code true} if this activity was reparented to another display but
      *         {@link #ensureActivityConfiguration} is not called.
      */
@@ -8134,7 +8180,7 @@
         proto.write(TRANSLUCENT, !occludesParent());
         proto.write(VISIBLE, mVisible);
         proto.write(VISIBLE_REQUESTED, mVisibleRequested);
-        proto.write(CLIENT_VISIBLE, mClientVisible);
+        proto.write(CLIENT_VISIBLE, isClientVisible());
         proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient);
         proto.write(REPORTED_DRAWN, mReportedDrawn);
         proto.write(REPORTED_VISIBLE, reportedVisible);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 84bc853..168ab43f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4823,7 +4823,7 @@
         }
         mNoAnimationNotifyOnTransitionFinished.clear();
 
-        mWallpaperController.hideDeferredWallpapersIfNeeded();
+        mWallpaperController.hideDeferredWallpapersIfNeededLegacy();
 
         onAppTransitionDone();
 
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 1f7e152..316c20b 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -99,6 +99,9 @@
         mTask.forAllActivities(a -> {
             setActivityVisibilityState(a, starting, resumeTopActivity);
         });
+        if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+            mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+        }
     }
 
     private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 27ef147..28c5a6d9 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -410,15 +410,19 @@
             return;
         }
 
+        requestFocus(focusToken, focus.getName());
+    }
+
+    private void requestFocus(IBinder focusToken, String windowName) {
         if (focusToken == mInputFocus) {
             return;
         }
 
         mInputFocus = focusToken;
-        mInputTransaction.setFocusedWindow(mInputFocus, focus.getName(), mDisplayId);
-        EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus,
+        mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);
+        EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,
                 "reason=UpdateInputWindows");
-        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
+        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);
     }
 
     void setFocusedAppLw(ActivityRecord newApp) {
@@ -470,6 +474,8 @@
 
         boolean mInDrag;
 
+        private boolean mRecentsAnimationFocusOverride;
+
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
 
@@ -485,10 +491,16 @@
             mInDrag = inDrag;
 
             resetInputConsumers(mInputTransaction);
-
+            mRecentsAnimationFocusOverride = false;
             mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
 
-            updateInputFocusRequest();
+            if (mRecentsAnimationFocusOverride) {
+                requestFocus(mRecentsAnimationInputConsumer.mWindowHandle.token,
+                        mRecentsAnimationInputConsumer.mName);
+            } else {
+                updateInputFocusRequest();
+            }
+
 
             if (!mUpdateInputWindowsImmediately) {
                 mDisplayContent.getPendingTransaction().merge(mInputTransaction);
@@ -526,6 +538,7 @@
                         mRecentsAnimationInputConsumer.mWindowHandle)) {
                     mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord);
                     mAddRecentsAnimationInputConsumerHandle = false;
+                    mRecentsAnimationFocusOverride = true;
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 98eb11f..ee4c66d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -135,6 +135,11 @@
         mSyncId = mSyncEngine.startSyncSet(this);
     }
 
+    @VisibleForTesting
+    int getSyncId() {
+        return mSyncId;
+    }
+
     /**
      * Formally starts the transition. Participants can be collected before this is started,
      * but this won't consider itself ready until started -- even if all the participants have
@@ -271,12 +276,17 @@
         // Commit all going-invisible containers
         for (int i = 0; i < mParticipants.size(); ++i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar == null || ar.mVisibleRequested) {
-                continue;
+            if (ar != null && !ar.isVisibleRequested()) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "  Commit activity becoming invisible: %s", ar);
+                ar.commitVisibility(false /* visible */, false /* performLayout */);
             }
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    "  Commit activity becoming invisible: %s", ar);
-            ar.commitVisibility(false /* visible */, false /* performLayout */);
+            final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+            if (wt != null && !wt.isVisibleRequested()) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "  Commit wallpaper becoming invisible: %s", ar);
+                wt.commitVisibility(false /* visible */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1a3138d..7c5afa8 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -126,12 +126,18 @@
         }
 
         mFindResults.resetTopWallpaper = true;
-        if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
-                && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
-
-            // If this window's app token is hidden and not animating, it is of no interest to us.
-            if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
-            return false;
+        if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) {
+            if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
+                    && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
+                // If this window's app token is hidden and not animating, it is of no interest.
+                if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
+                return false;
+            }
+        } else {
+            if (w.mActivityRecord != null && !w.mActivityRecord.isVisibleRequested()) {
+                // An activity that is not going to remain visible shouldn't be the target.
+                return false;
+            }
         }
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
                 + " mDrawState=" + w.mWinAnimator.mDrawState);
@@ -227,7 +233,10 @@
     }
 
     boolean isWallpaperVisible() {
-        return isWallpaperVisible(mWallpaperTarget);
+        for (int i = mWallpaperTokens.size() - 1; i >= 0; --i) {
+            if (mWallpaperTokens.get(i).isVisible()) return true;
+        }
+        return false;
     }
 
     /**
@@ -240,7 +249,7 @@
         }
     }
 
-    private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+    private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) {
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
                     + mPrevWallpaperTarget);
@@ -255,18 +264,18 @@
     }
 
     void updateWallpaperVisibility() {
-        final boolean visible = isWallpaperVisible(mWallpaperTarget);
+        final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget);
 
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.updateWallpaperVisibility(visible);
+            token.setVisibility(visible);
         }
     }
 
-    void hideDeferredWallpapersIfNeeded() {
-        if (mDeferredHideWallpaper != null) {
-            hideWallpapers(mDeferredHideWallpaper);
-            mDeferredHideWallpaper = null;
+    void hideDeferredWallpapersIfNeededLegacy() {
+        for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(i);
+            token.commitVisibility(false);
         }
     }
 
@@ -275,18 +284,9 @@
                 && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) {
             return;
         }
-        if (mWallpaperTarget != null
-                && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) {
-            // Defer hiding the wallpaper when app transition is running until the animations
-            // are done.
-            mDeferredHideWallpaper = winGoingAway;
-            return;
-        }
-
-        final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway);
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
-            token.hideWallpaperToken(wasDeferred, "hideWallpapers");
+            token.setVisibility(false);
             if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
                 Slog.d(TAG, "Hiding wallpaper " + token
                         + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
@@ -616,7 +616,7 @@
 
         // The window is visible to the compositor...but is it visible to the user?
         // That is what the wallpaper cares about.
-        final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget);
+        final boolean visible = mWallpaperTarget != null;
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
                     + mDisplayContent.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 43303d4..717775605 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -19,7 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -34,6 +34,8 @@
 import android.view.WindowManager;
 import android.view.animation.Animation;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 import java.util.function.Consumer;
 
 /**
@@ -43,6 +45,8 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
 
+    private boolean mVisibleRequested = false;
+
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
         this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -57,18 +61,16 @@
     }
 
     @Override
+    WallpaperWindowToken asWallpaperToken() {
+        return this;
+    }
+
+    @Override
     void setExiting() {
         super.setExiting();
         mDisplayContent.mWallpaperController.removeWallpaperToken(this);
     }
 
-    void hideWallpaperToken(boolean wasDeferred, String reason) {
-        for (int j = mChildren.size() - 1; j >= 0; j--) {
-            final WindowState wallpaper = mChildren.get(j);
-            wallpaper.hideWallpaperWindow(wasDeferred, reason);
-        }
-    }
-
     void sendWindowWallpaperCommand(
             String action, int x, int y, int z, Bundle extras, boolean sync) {
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
@@ -93,24 +95,6 @@
         }
     }
 
-    void updateWallpaperVisibility(boolean visible) {
-        if (isVisible() != visible) {
-            mWmService.mAtmService.getTransitionController().collect(this);
-            // Need to do a layout to ensure the wallpaper now has the correct size.
-            mDisplayContent.setLayoutNeeded();
-        }
-
-        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
-        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
-            final WindowState wallpaper = mChildren.get(wallpaperNdx);
-            if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
-            }
-
-            wallpaper.dispatchWallpaperVisibility(visible);
-        }
-    }
-
     /**
      * Starts {@param anim} on all children.
      */
@@ -122,16 +106,16 @@
     }
 
     void updateWallpaperWindows(boolean visible) {
-
         if (isVisible() != visible) {
             if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
                     "Wallpaper token " + token + " visible=" + visible);
-            mWmService.mAtmService.getTransitionController().collect(this);
-            // Need to do a layout to ensure the wallpaper now has the correct size.
-            mDisplayContent.setLayoutNeeded();
+            setVisibility(visible);
+        }
+        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+        if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+            return;
         }
 
-        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget();
 
         if (visible && wallpaperTarget != null) {
@@ -153,21 +137,54 @@
             }
         }
 
-        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
-            final WindowState wallpaper = mChildren.get(wallpaperNdx);
+        setVisible(visible);
+    }
 
-            if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
+    private void setVisible(boolean visible) {
+        final boolean wasClientVisible = isClientVisible();
+        setClientVisible(visible);
+        if (visible && !wasClientVisible) {
+            for (int i = mChildren.size() - 1; i >= 0; i--) {
+                final WindowState wallpaper = mChildren.get(i);
+                wallpaper.requestUpdateWallpaperIfNeeded();
             }
-
-            // First, make sure the client has the current visibility state.
-            wallpaper.dispatchWallpaperVisibility(visible);
-
-            if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win "
-                    + wallpaper);
         }
     }
 
+    /**
+     * Sets the requested visibility of this token. The visibility may not be if this is part of a
+     * transition. In that situation, make sure to call {@link #commitVisibility} when done.
+     */
+    void setVisibility(boolean visible) {
+        // Before setting mVisibleRequested so we can track changes.
+        mWmService.mAtmService.getTransitionController().collect(this);
+
+        setVisibleRequested(visible);
+
+        // If in a transition, defer commits for activities that are going invisible
+        if (!visible && (mWmService.mAtmService.getTransitionController().inTransition()
+                || getDisplayContent().mAppTransition.isRunning())) {
+            return;
+        }
+
+        commitVisibility(visible);
+    }
+
+    /**
+     * Commits the visibility of this token. This will directly update the visibility without
+     * regard for other state (like being in a transition).
+     */
+    void commitVisibility(boolean visible) {
+        if (visible == isVisible()) return;
+
+        ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+                "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
+                isVisible(), mVisibleRequested);
+
+        setVisibleRequested(visible);
+        setVisible(visible);
+    }
+
     @Override
     void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
         if (attrs.height == ViewGroup.LayoutParams.MATCH_PARENT
@@ -186,9 +203,10 @@
     }
 
     boolean hasVisibleNotDrawnWallpaper() {
+        if (!isVisible()) return false;
         for (int j = mChildren.size() - 1; j >= 0; --j) {
             final WindowState wallpaper = mChildren.get(j);
-            if (wallpaper.hasVisibleNotDrawnWallpaper()) {
+            if (!wallpaper.isDrawn() && wallpaper.isVisible()) {
                 return true;
             }
         }
@@ -210,6 +228,21 @@
         return false;
     }
 
+    void setVisibleRequested(boolean visible) {
+        if (mVisibleRequested == visible) return;
+        mVisibleRequested = visible;
+        setInsetsFrozen(!visible);
+    }
+
+    @Override
+    boolean isVisibleRequested() {
+        return mVisibleRequested;
+    }
+
+    @Override
+    boolean isVisible() {
+        return isClientVisible();
+    }
 
     @Override
     public String toString() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 000889a..0c4ff2f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3060,6 +3060,11 @@
     }
 
     /** Cheap way of doing cast and instanceof. */
+    WallpaperWindowToken asWallpaperToken() {
+        return null;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
     DisplayArea asDisplayArea() {
         return null;
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 70b0f58..c9e1605 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3971,6 +3971,21 @@
                     ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
     }
 
+    /** Returns a string representing the given {@link LetterboxBackgroundType}. */
+    static String letterboxBackgroundTypeToString(
+            @LetterboxBackgroundType int backgroundType) {
+        switch (backgroundType) {
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                return "LETTERBOX_BACKGROUND_SOLID_COLOR";
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND";
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING";
+            default:
+                return "unknown=" + backgroundType;
+        }
+    }
+
     @Override
     public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
         if (!checkCallingPermission(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fec715e..deb3913 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -141,11 +141,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
@@ -358,7 +356,6 @@
     private boolean mForceHideNonSystemOverlayWindow;
     boolean mAppFreezing;
     boolean mHidden = true;    // Used to determine if to show child windows.
-    boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
     private boolean mDragResizing;
     private boolean mDragResizingChangeReported = true;
     private int mResizeMode;
@@ -1715,7 +1712,11 @@
 
     @Override
     boolean isVisibleRequested() {
-        return isVisible() && (mActivityRecord == null || mActivityRecord.isVisibleRequested());
+        if (mToken != null && (mActivityRecord != null || mToken.asWallpaperToken() != null)) {
+            // Currently only ActivityRecord and WallpaperToken support visibleRequested.
+            return isVisible() && mToken.isVisibleRequested();
+        }
+        return isVisible();
     }
 
     /**
@@ -1745,8 +1746,11 @@
      *         {@code false} otherwise.
      */
     boolean wouldBeVisibleIfPolicyIgnored() {
-        return mHasSurface && !isParentWindowHidden()
-                && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
+        if (!mHasSurface || isParentWindowHidden() || mAnimatingExit || mDestroying) {
+            return false;
+        }
+        final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null;
+        return !isWallpaper || mToken.isVisible();
     }
 
     /**
@@ -1804,6 +1808,10 @@
             return ((!isParentWindowHidden() && atoken.isVisible())
                     || isAnimating(TRANSITION | PARENTS));
         }
+        final WallpaperWindowToken wtoken = mToken.asWallpaperToken();
+        if (wtoken != null) {
+            return !isParentWindowHidden() && wtoken.isVisible();
+        }
         return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
     }
 
@@ -1943,8 +1951,9 @@
         // When there is keyguard, wallpaper could be placed over the secure app
         // window but invisible. We need to check wallpaper visibility explicitly
         // to determine if it's occluding apps.
-        return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
-                || (mIsWallpaper && mWallpaperVisible))
+        final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null;
+        return ((!isWallpaper && mAttrs.format == PixelFormat.OPAQUE)
+                || (isWallpaper && mToken.isVisible()))
                 && isDrawn() && !isAnimating(TRANSITION | PARENTS);
     }
 
@@ -3224,7 +3233,11 @@
     void sendAppVisibilityToClients() {
         super.sendAppVisibilityToClients();
 
-        final boolean clientVisible = mActivityRecord.isClientVisible();
+        if (mToken == null) return;
+
+        final boolean clientVisible = mToken.isClientVisible();
+        // TODO(shell-transitions): This is currently only applicable to app windows, BUT we
+        //                          want to extend the "starting" concept to other windows.
         if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) {
             // Don't hide the starting window.
             return;
@@ -3608,9 +3621,11 @@
         if (mActivityRecord != null && mActivityRecord.isRelaunching()) {
             return;
         }
-        // If the activity is invisible or going invisible, don't report either since it is going
-        // away. This is likely during a transition so we want to preserve the original state.
-        if (mActivityRecord != null && !mActivityRecord.isVisibleRequested()) {
+        // If this is an activity or wallpaper and is invisible or going invisible, don't report
+        // either since it is going away. This is likely during a transition so we want to preserve
+        // the original state.
+        if ((mActivityRecord != null || mToken.asWallpaperToken() != null)
+                && !mToken.isVisibleRequested()) {
             return;
         }
 
@@ -4024,8 +4039,7 @@
         if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) {
             pw.println(prefix + "mIsImWindow=" + mIsImWindow
                     + " mIsWallpaper=" + mIsWallpaper
-                    + " mIsFloatingLayer=" + mIsFloatingLayer
-                    + " mWallpaperVisible=" + mWallpaperVisible);
+                    + " mIsFloatingLayer=" + mIsFloatingLayer);
         }
         if (dumpAll) {
             pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
@@ -4839,61 +4853,6 @@
         return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
     }
 
-    void hideWallpaperWindow(boolean wasDeferred, String reason) {
-        for (int j = mChildren.size() - 1; j >= 0; --j) {
-            final WindowState c = mChildren.get(j);
-            c.hideWallpaperWindow(wasDeferred, reason);
-        }
-        if (!mWinAnimator.mLastHidden || wasDeferred) {
-            mWinAnimator.hide(getGlobalTransaction(), reason);
-            getDisplayContent().mWallpaperController.mDeferredHideWallpaper = null;
-            dispatchWallpaperVisibility(false);
-            final DisplayContent displayContent = getDisplayContent();
-            if (displayContent != null) {
-                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                if (DEBUG_LAYOUT_REPEATS) {
-                    mWmService.mWindowPlacerLocked.debugLayoutRepeats("hideWallpaperWindow " + this,
-                            displayContent.pendingLayoutChanges);
-                }
-            }
-        }
-    }
-
-    /**
-     * Check wallpaper window for visibility change and notify window if so.
-     * @param visible Current visibility.
-     */
-    void dispatchWallpaperVisibility(final boolean visible) {
-        final boolean hideAllowed =
-                getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null;
-
-        // Only send notification if the visibility actually changed and we are not trying to hide
-        // the wallpaper when we are deferring hiding of the wallpaper.
-        if (mWallpaperVisible != visible && (hideAllowed || visible)) {
-            mWallpaperVisible = visible;
-            try {
-                if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
-                        "Updating vis of wallpaper " + this
-                                + ": " + visible + " from:\n" + Debug.getCallers(4, "  "));
-                mClient.dispatchAppVisibility(visible);
-            } catch (RemoteException e) {
-            }
-        }
-    }
-
-    boolean hasVisibleNotDrawnWallpaper() {
-        if (mWallpaperVisible && !isDrawn()) {
-            return true;
-        }
-        for (int j = mChildren.size() - 1; j >= 0; --j) {
-            final WindowState c = mChildren.get(j);
-            if (c.hasVisibleNotDrawnWallpaper()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     void updateReportedVisibility(UpdateReportedVisibilityResults results) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState c = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ece256e..ebbebbb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -57,7 +57,6 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
@@ -575,10 +574,7 @@
 
         setSurfaceBoundariesLocked(t);
 
-        if (mIsWallpaper && !w.mWallpaperVisible) {
-            // Wallpaper is no longer visible and there is no wp target => hide it.
-            hide(t, "prepareSurfaceLocked");
-        } else if (w.isParentWindowHidden() || !w.isOnScreen()) {
+        if (w.isParentWindowHidden() || !w.isOnScreen()) {
             hide(t, "prepareSurfaceLocked");
             mWallpaperControllerLocked.hideWallpapers(w);
 
@@ -631,9 +627,6 @@
                     if (showSurfaceRobustlyLocked(t)) {
                         mAnimator.requestRemovalOfReplacedWindows(w);
                         mLastHidden = false;
-                        if (mIsWallpaper) {
-                            w.dispatchWallpaperVisibility(true);
-                        }
                         final DisplayContent displayContent = w.getDisplayContent();
                         if (!displayContent.getLastHasContent()) {
                             // This draw means the difference between unique content and mirroring.
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 066cc1e..8867aa7 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -112,6 +113,9 @@
      */
     private final boolean mFromClientToken;
 
+    /** Have we told the window clients to show themselves? */
+    private boolean mClientVisible;
+
     /**
      * Used to fix the transform of the token to be rotated to a rotation different than it's
      * display. The window frames and surfaces corresponding to this token will be layouted and
@@ -397,6 +401,21 @@
         return builder;
     }
 
+    boolean isClientVisible() {
+        return mClientVisible;
+    }
+
+    void setClientVisible(boolean clientVisible) {
+        if (mClientVisible == clientVisible) {
+            return;
+        }
+        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible,
+                Debug.getCallers(5));
+        mClientVisible = clientVisible;
+        sendAppVisibilityToClients();
+    }
+
     boolean hasFixedRotationTransform() {
         return mFixedRotationTransformState != null;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 28e9acf..04af5c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1583,8 +1583,7 @@
         }
 
         public String[] getPersonalAppsForSuspension(int userId) {
-            return new PersonalAppsSuspensionHelper(
-                    mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
+            return PersonalAppsSuspensionHelper.forUser(mContext, userId)
                     .getPersonalAppsForSuspension();
         }
 
@@ -1599,6 +1598,10 @@
         void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
             mSafetyChecker = safetyChecker;
         }
+
+        void dumpPerUserData(IndentingPrintWriter pw, @UserIdInt int userId) {
+            PersonalAppsSuspensionHelper.forUser(mContext, userId).dump(pw);
+        }
     }
 
     /**
@@ -9161,11 +9164,17 @@
         }
     }
 
-    private void dumpDevicePolicyData(IndentingPrintWriter pw) {
+    private void dumpPerUserData(IndentingPrintWriter pw) {
         int userCount = mUserData.size();
-        for (int u = 0; u < userCount; u++) {
-            DevicePolicyData policy = getUserData(mUserData.keyAt(u));
+        for (int userId = 0; userId < userCount; userId++) {
+            DevicePolicyData policy = getUserData(mUserData.keyAt(userId));
             policy.dump(pw);
+            pw.println();
+
+            pw.increaseIndent();
+            mInjector.dumpPerUserData(pw, userId);
+            pw.decreaseIndent();
+            pw.println();
         }
     }
 
@@ -9183,7 +9192,7 @@
                 pw.println();
                 mDeviceAdminServiceController.dump(pw);
                 pw.println();
-                dumpDevicePolicyData(pw);
+                dumpPerUserData(pw);
                 pw.println();
                 mConstants.dump(pw);
                 pw.println();
@@ -9229,20 +9238,30 @@
         pw.increaseIndent();
         dumpResources(pw, mContext, "cross_profile_apps", R.array.cross_profile_apps);
         dumpResources(pw, mContext, "vendor_cross_profile_apps", R.array.vendor_cross_profile_apps);
+        dumpResources(pw, mContext, "config_packagesExemptFromSuspension",
+                R.array.config_packagesExemptFromSuspension);
         pw.decreaseIndent();
         pw.println();
     }
 
     static void dumpResources(IndentingPrintWriter pw, Context context, String resName, int resId) {
-        String[] apps = context.getResources().getStringArray(resId);
-        if (apps == null || apps.length == 0) {
-            pw.printf("%s: empty\n", resName);
+        dumpApps(pw, resName, context.getResources().getStringArray(resId));
+    }
+
+    static void dumpApps(IndentingPrintWriter pw, String name, String[] apps) {
+        dumpApps(pw, name, Arrays.asList(apps));
+    }
+
+    static void dumpApps(IndentingPrintWriter pw, String name, List apps) {
+        if (apps == null || apps.isEmpty()) {
+            pw.printf("%s: empty\n", name);
             return;
         }
-        pw.printf("%s: %d app%s\n", resName, apps.length, apps.length == 1 ? "" : "s");
+        int size = apps.size();
+        pw.printf("%s: %d app%s\n", name, size, size == 1 ? "" : "s");
         pw.increaseIndent();
-        for (int i = 0; i < apps.length; i++) {
-            pw.printf("%d: %s\n", i, apps[i]);
+        for (int i = 0; i < size; i++) {
+            pw.printf("%d: %s\n", i, apps.get(i));
         }
         pw.decreaseIndent();
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index c6871842..37dbfc1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -20,6 +20,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -29,10 +30,12 @@
 import android.content.pm.ResolveInfo;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
@@ -49,7 +52,7 @@
 /**
  * Utility class to find what personal apps should be suspended to limit personal device use.
  */
-public class PersonalAppsSuspensionHelper {
+public final class PersonalAppsSuspensionHelper {
     private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
 
     // Flags to get all packages even if the user is still locked.
@@ -60,9 +63,17 @@
     private final PackageManager mPackageManager;
 
     /**
+     * Factory method
+     */
+    public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) {
+        return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId),
+                /* flags= */ 0));
+    }
+
+    /**
      * @param context Context for the user whose apps should to be suspended.
      */
-    public PersonalAppsSuspensionHelper(Context context) {
+    private PersonalAppsSuspensionHelper(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
     }
@@ -181,4 +192,21 @@
                 iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder);
         return new AccessibilityManager(mContext, service, userId);
     }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("PersonalAppsSuspensionHelper");
+        pw.increaseIndent();
+
+        DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages());
+        DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages());
+        DevicePolicyManagerService.dumpApps(pw, "accessibility services",
+                getAccessibilityServices());
+        DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages());
+        pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext));
+        pw.printf("Settings package: %s\n", getSettingsPackageName());
+        DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension",
+                getPersonalAppsForSuspension());
+
+        pw.decreaseIndent();
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
index 9447f39..8ef9239 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -19,7 +19,7 @@
 import android.content.pm.verify.domain.DomainSet
 import android.content.pm.verify.domain.DomainVerificationInfo
 import android.content.pm.verify.domain.DomainVerificationRequest
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
 import android.os.Parcel
 import android.os.Parcelable
 import android.os.UserHandle
@@ -28,7 +28,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import java.util.UUID
-import kotlin.random.Random
 
 @RunWith(Parameterized::class)
 class DomainVerificationCoreApiTest {
@@ -92,9 +91,9 @@
                 }
             ),
             Parameter(
-                testName = "DomainVerificationUserSelection",
+                testName = "DomainVerificationUserState",
                 initial = {
-                    DomainVerificationUserSelection(
+                    DomainVerificationUserState(
                         UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
                         "com.test.pkg",
                         UserHandle.of(10),
@@ -103,22 +102,22 @@
                             .associate { it.value to (it.index % 3) }
                     )
                 },
-                unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
+                unparcel = { DomainVerificationUserState.CREATOR.createFromParcel(it) },
                 assertion = { first, second ->
-                    assertAll<DomainVerificationUserSelection, UUID>(first, second,
+                    assertAll<DomainVerificationUserState, UUID>(first, second,
                         { it.identifier }, { it.component1() }, IS_EQUAL_TO
                     )
-                    assertAll<DomainVerificationUserSelection, String>(first, second,
+                    assertAll<DomainVerificationUserState, String>(first, second,
                         { it.packageName }, { it.component2() }, IS_EQUAL_TO
                     )
-                    assertAll<DomainVerificationUserSelection, UserHandle>(first, second,
+                    assertAll<DomainVerificationUserState, UserHandle>(first, second,
                         { it.user }, { it.component3() }, IS_EQUAL_TO
                     )
-                    assertAll<DomainVerificationUserSelection, Boolean>(
+                    assertAll<DomainVerificationUserState, Boolean>(
                         first, second, { it.isLinkHandlingAllowed },
                         { it.component4() }, IS_EQUAL_TO
                     )
-                    assertAll<DomainVerificationUserSelection, Map<String, Int>>(
+                    assertAll<DomainVerificationUserState, Map<String, Int>>(
                         first, second, { it.hostToStateMap },
                         { it.component5() }, IS_MAP_EQUAL_TO
                     )
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 89394837..53f0ca2 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -155,6 +155,15 @@
                     assertApprovedVerifier(it.callingUid, it.proxy)
                 },
                 enforcer(
+                    Type.SELECTION_QUERENT,
+                    "approvedUserStateQuerent"
+                ) {
+                    assertApprovedUserStateQuerent(
+                        it.callingUid, it.callingUserId,
+                        it.targetPackageName, it.userId
+                    )
+                },
+                enforcer(
                     Type.SELECTOR,
                     "approvedUserSelector"
                 ) {
@@ -170,7 +179,7 @@
                         ArraySet(setOf("example.com"))
                     )
                 },
-                service(Type.INTERNAL, "setUserSelectionInternal") {
+                service(Type.INTERNAL, "setUserStateInternal") {
                     setDomainVerificationUserSelectionInternal(
                         it.userId,
                         it.targetPackageName,
@@ -184,11 +193,11 @@
                 service(Type.INTERNAL, "clearState") {
                     clearDomainVerificationState(listOf(it.targetPackageName))
                 },
-                service(Type.INTERNAL, "clearUserSelections") {
-                    clearUserSelections(listOf(it.targetPackageName), it.userId)
+                service(Type.INTERNAL, "clearUserStates") {
+                    clearUserStates(listOf(it.targetPackageName), it.userId)
                 },
-                service(Type.VERIFIER, "getPackageNames") {
-                    validVerificationPackageNames
+                service(Type.VERIFIER, "queryValidPackageNames") {
+                    queryValidVerificationPackageNames()
                 },
                 service(Type.QUERENT, "getInfo") {
                     getDomainVerificationInfo(it.targetPackageName)
@@ -208,26 +217,13 @@
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service(Type.SELECTOR, "setLinkHandlingAllowed") {
-                    setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true)
-                },
                 service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
                     setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId)
                 },
-                service(Type.SELECTOR, "getUserSelection") {
-                    getDomainVerificationUserSelection(it.targetPackageName)
+                service(Type.SELECTION_QUERENT, "getUserStateUserId") {
+                    getDomainVerificationUserState(it.targetPackageName, it.userId)
                 },
-                service(Type.SELECTOR_USER, "getUserSelectionUserId") {
-                    getDomainVerificationUserSelection(it.targetPackageName, it.userId)
-                },
-                service(Type.SELECTOR, "setUserSelection") {
-                    setDomainVerificationUserSelection(
-                        it.targetDomainSetId,
-                        setOf("example.com"),
-                        true
-                    )
-                },
-                service(Type.SELECTOR_USER, "setUserSelectionUserId") {
+                service(Type.SELECTOR_USER, "setUserStateUserId") {
                     setDomainVerificationUserSelection(
                         it.targetDomainSetId,
                         setOf("example.com"),
@@ -244,10 +240,6 @@
                 service(Type.LEGACY_QUERENT, "getLegacyUserState") {
                     getLegacyState(it.targetPackageName, it.userId)
                 },
-                service(Type.OWNER_QUERENT, "getOwnersForDomain") {
-                    // Re-use package name, since the result itself isn't relevant
-                    getOwnersForDomain(it.targetPackageName)
-                },
                 service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") {
                     // Re-use package name, since the result itself isn't relevant
                     getOwnersForDomain(it.targetPackageName, it.userId)
@@ -362,6 +354,7 @@
             Type.INTERNAL -> internal()
             Type.QUERENT -> approvedQuerent()
             Type.VERIFIER -> approvedVerifier()
+            Type.SELECTION_QUERENT -> approvedUserStateQuerent(verifyCrossUser = true)
             Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
             Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
             Type.LEGACY_QUERENT -> legacyQuerent()
@@ -371,7 +364,7 @@
         }.run { /*exhaust*/ }
     }
 
-    fun internal() {
+    private fun internal() {
         val context: Context = mockThrowOnUnmocked()
         val target = params.construct(context)
 
@@ -385,13 +378,13 @@
         }
     }
 
-    fun approvedQuerent() {
-        val allowUserSelection = AtomicBoolean(false)
+    private fun approvedQuerent() {
+        val allowUserState = AtomicBoolean(false)
         val allowPreferredApps = AtomicBoolean(false)
         val allowQueryAll = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
             initPermission(
-                allowUserSelection,
+                allowUserState,
                 android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
             )
             initPermission(
@@ -418,7 +411,7 @@
 
         assertFails { runMethod(target, NON_VERIFIER_UID) }
 
-        allowUserSelection.set(true)
+        allowUserState.set(true)
 
         assertFails { runMethod(target, NON_VERIFIER_UID) }
 
@@ -427,7 +420,7 @@
         runMethod(target, NON_VERIFIER_UID)
     }
 
-    fun approvedVerifier() {
+    private fun approvedVerifier() {
         val allowDomainVerificationAgent = AtomicBoolean(false)
         val allowIntentVerificationAgent = AtomicBoolean(false)
         val allowQueryAll = AtomicBoolean(false)
@@ -469,12 +462,61 @@
         assertFails { runMethod(target, NON_VERIFIER_UID) }
     }
 
-    fun approvedUserSelector(verifyCrossUser: Boolean) {
-        val allowUserSelection = AtomicBoolean(false)
+    private fun approvedUserStateQuerent(verifyCrossUser: Boolean) {
         val allowInteractAcrossUsers = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
             initPermission(
-                allowUserSelection,
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
+        }
+        val target = params.construct(context)
+
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // User selector makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // User selector doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        if (verifyCrossUser) {
+            runTestCases(callingUserId, notCallingUserId, throws = true)
+        }
+
+        allowInteractAcrossUsers.set(true)
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        if (verifyCrossUser) {
+            runTestCases(callingUserId, notCallingUserId, throws = false)
+        }
+    }
+
+    private fun approvedUserSelector(verifyCrossUser: Boolean) {
+        val allowUserState = AtomicBoolean(false)
+        val allowInteractAcrossUsers = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            initPermission(
+                allowUserState,
                 android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
             )
             initPermission(
@@ -515,7 +557,7 @@
             runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
-        allowUserSelection.set(true)
+        allowUserState.set(true)
 
         runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
@@ -641,7 +683,7 @@
 
     private fun ownerQuerent(verifyCrossUser: Boolean) {
         val allowQueryAll = AtomicBoolean(false)
-        val allowUserSelection = AtomicBoolean(false)
+        val allowUserState = AtomicBoolean(false)
         val allowInteractAcrossUsers = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
             initPermission(
@@ -649,7 +691,7 @@
                 android.Manifest.permission.QUERY_ALL_PACKAGES
             )
             initPermission(
-                allowUserSelection,
+                allowUserState,
                 android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
             )
             initPermission(
@@ -690,7 +732,7 @@
             runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
-        allowUserSelection.set(true)
+        allowUserState.set(true)
 
         runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
@@ -769,6 +811,9 @@
         // INTERNAL || domain verification agent
         VERIFIER,
 
+        // No permissions, allows all apps to view domain state for visible packages
+        SELECTION_QUERENT,
+
         // Holding the user setting permission
         SELECTOR,
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
index 439048c..8c31c65 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -18,7 +18,7 @@
 
 import android.content.pm.verify.domain.DomainVerificationRequest
 import android.content.pm.verify.domain.DomainVerificationInfo
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
 import com.android.server.pm.verify.domain.DomainVerificationPersistence
 
 operator fun <F> android.util.Pair<F, *>.component1() = first
@@ -30,11 +30,11 @@
 operator fun DomainVerificationInfo.component2() = packageName
 operator fun DomainVerificationInfo.component3() = hostToStateMap
 
-operator fun DomainVerificationUserSelection.component1() = identifier
-operator fun DomainVerificationUserSelection.component2() = packageName
-operator fun DomainVerificationUserSelection.component3() = user
-operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
-operator fun DomainVerificationUserSelection.component5() = hostToStateMap
+operator fun DomainVerificationUserState.component1() = identifier
+operator fun DomainVerificationUserState.component2() = packageName
+operator fun DomainVerificationUserState.component3() = user
+operator fun DomainVerificationUserState.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserState.component5() = hostToStateMap
 
 operator fun DomainVerificationPersistence.ReadResult.component1() = active
 operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index a92ab9e..ad9aa7b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -22,9 +22,9 @@
 import android.util.TypedXmlSerializer
 import android.util.Xml
 import com.android.server.pm.verify.domain.DomainVerificationPersistence
+import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap
-import com.android.server.pm.verify.domain.models.DomainVerificationUserState
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
@@ -102,14 +102,14 @@
             // A domain without a written state falls back to default
             stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
 
-            userSelectionStates[1] = DomainVerificationUserState(1).apply {
+            userStates[1] = DomainVerificationInternalUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
                 isLinkHandlingAllowed = true
             }
         }
         val stateOne = mockEmptyPkgState(1).apply {
             // It's valid to have a user selection without any autoVerify domains
-            userSelectionStates[1] = DomainVerificationUserState(1).apply {
+            userStates[1] = DomainVerificationInternalUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
                 isLinkHandlingAllowed = false
             }
@@ -214,7 +214,7 @@
 
     private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
         stateMap["$packageName.com"] = id
-        userSelectionStates[id] = DomainVerificationUserState(id).apply {
+        userStates[id] = DomainVerificationInternalUserState(id).apply {
             addHosts(setOf("$packageName-user.com"))
             isLinkHandlingAllowed = true
         }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 010eacf..0d8f275 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -122,8 +122,8 @@
                 service("clearState") {
                     clearDomainVerificationState(listOf(TEST_PKG))
                 },
-                service("clearUserSelections") {
-                    clearUserSelections(listOf(TEST_PKG), TEST_USER_ID)
+                service("clearUserStates") {
+                    clearUserStates(listOf(TEST_PKG), TEST_USER_ID)
                 },
                 service("setStatus") {
                     setDomainVerificationStatus(
@@ -147,19 +147,13 @@
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service("setLinkHandlingAllowed") {
-                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
-                },
                 service("setLinkHandlingAllowedUserId") {
                     setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, TEST_USER_ID)
                 },
                 service("setLinkHandlingAllowedInternal") {
                     setDomainVerificationLinkHandlingAllowedInternal(TEST_PKG, true, TEST_USER_ID)
                 },
-                service("setUserSelection") {
-                    setDomainVerificationUserSelection(TEST_UUID, setOf("example.com"), true)
-                },
-                service("setUserSelectionUserId") {
+                service("setUserStateUserId") {
                     setDomainVerificationUserSelection(
                         TEST_UUID,
                         setOf("example.com"),
@@ -167,7 +161,7 @@
                         TEST_USER_ID
                     )
                 },
-                service("setUserSelectionInternal") {
+                service("setUserStateInternal") {
                     setDomainVerificationUserSelectionInternal(
                         TEST_USER_ID,
                         TEST_PKG,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
similarity index 82%
rename from services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
rename to services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 48056a2..0576125 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -16,18 +16,16 @@
 
 package com.android.server.pm.test.verify.domain
 
-import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
 import android.content.pm.verify.domain.DomainVerificationManager
-import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.content.pm.verify.domain.DomainVerificationUserState
 import android.os.Build
 import android.os.PatternMatcher
 import android.os.Process
 import android.util.ArraySet
-import androidx.test.InstrumentationRegistry
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.verify.domain.DomainVerificationService
@@ -41,7 +39,7 @@
 import org.mockito.ArgumentMatchers.anyString
 import java.util.UUID
 
-class DomainVerificationManagerUserSelectionOverrideTest {
+class DomainVerificationUserStateOverrideTest {
 
     companion object {
         private const val PKG_ONE = "com.test.one"
@@ -50,17 +48,19 @@
         private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
 
         private val DOMAIN_ONE =
-            DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName
+            DomainVerificationUserStateOverrideTest::class.java.packageName
 
-        private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE
-        private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED
-        private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED
+        private const val STATE_NONE = DomainVerificationUserState.DOMAIN_STATE_NONE
+        private const val STATE_SELECTED = DomainVerificationUserState.DOMAIN_STATE_SELECTED
+        private const val STATE_VERIFIED = DomainVerificationUserState.DOMAIN_STATE_VERIFIED
+
+        private const val USER_ID = 0
     }
 
     private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
     private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
 
-    fun makeManager(): DomainVerificationManager =
+    fun makeService() =
         DomainVerificationService(mockThrowOnUnmocked {
             // Assume the test has every permission necessary
             whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
@@ -88,7 +88,7 @@
             addPackage(pkg2)
 
             // Starting state for all tests is to have domain 1 enabled for the first package
-            setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true)
+            setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID)
 
             assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
         }
@@ -138,37 +138,37 @@
 
     @Test
     fun anotherPackageTakeoverSuccess() {
-        val manager = makeManager()
+        val service = makeService()
 
         // Attempt override by package 2
-        manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+        service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
 
         // 1 loses approval
-        assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
+        assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
 
         // 2 gains approval
-        assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
+        assertThat(service.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
 
         // 2 is the only owner
-        assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+        assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
             .containsExactly(PKG_TWO)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun anotherPackageTakeoverFailure() {
-        val manager = makeManager()
+        val service = makeService()
 
         // Verify 1 to give it a higher approval level
-        manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
+        service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
             DomainVerificationManager.STATE_SUCCESS)
-        assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
-        assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+        assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
+        assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
             .containsExactly(PKG_ONE)
 
         // Attempt override by package 2
-        manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+        service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
     }
 
-    private fun DomainVerificationManager.stateFor(pkgName: String, host: String) =
-        getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host]
+    private fun DomainVerificationService.stateFor(pkgName: String, host: String) =
+        getDomainVerificationUserState(pkgName, USER_ID)!!.hostToStateMap[host]
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index f7f5928..3870b02 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -641,6 +641,6 @@
     private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
         return new JobStatus(job.build(), uid, null, -1, 0, null,
-                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
+                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 91b3cb7..7925b69 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -685,7 +685,7 @@
         final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
         return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
-                latestRunTimeElapsedMillis, 0, 0, null, 0);
+                latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
     }
 
     private static JobStatus createJobStatus(JobInfo job) {
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index deaeb46..8b35af8 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -276,7 +276,7 @@
                 0 /* sourceUserId */, 0, "someTag",
                 invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
-                persistedExecutionTimesUTC, 0 /* innerFlagg */);
+                persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */);
 
         mTaskStoreUnderTest.add(js);
         waitForPendingIo();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 137cf65..09a436c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -518,6 +518,7 @@
                 TYPE_WALLPAPER, TYPE_APPLICATION);
         final WindowState wallpaper = windows[0];
         assertTrue(wallpaper.mIsWallpaper);
+        wallpaper.mToken.asWallpaperToken().setVisibility(false);
         // By default WindowState#mWallpaperVisible is false.
         assertFalse(wallpaper.isVisible());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 401ace03c..154a899 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -316,9 +316,9 @@
                 mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */));
         final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
                 "wallpaperWindow");
-        wallpaperWindow.mWallpaperVisible = false;
+        wallpaperWindowToken.setVisibleRequested(false);
         transition.collect(wallpaperWindowToken);
-        wallpaperWindow.mWallpaperVisible = true;
+        wallpaperWindowToken.setVisibleRequested(true);
         wallpaperWindow.mHasSurface = true;
 
         // doesn't matter which order collected since participants is a set
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index d1d0ac6..8b4e947 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -24,6 +24,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -49,7 +51,9 @@
 import android.view.InsetsState;
 import android.view.RoundedCorners;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.window.ITransitionPlayer;
 
 import androidx.test.filters.SmallTest;
 
@@ -135,7 +139,8 @@
         int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
 
         // Check that the wallpaper is correctly scaled
-        assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrame());
+        assertEquals(expectedWidth, wallpaperWindow.getFrame().width());
+        assertEquals(displayHeight, wallpaperWindow.getFrame().height());
         Rect portraitFrame = wallpaperWindow.getFrame();
 
         // Rotate the display
@@ -297,6 +302,46 @@
         assertFalse(mAppWindow.mActivityRecord.hasFixedRotationTransform());
     }
 
+    @Test
+    public void testWallpaperTokenVisibility() {
+        final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
+        final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, dc, true /* ownerCanManageAppTokens */);
+        final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, token,
+                "wallpaperWindow");
+        wallpaperWindow.setHasSurface(true);
+
+        // Set-up mock shell transitions
+        final IBinder mockBinder = mock(IBinder.class);
+        final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class);
+        doReturn(mockBinder).when(mockPlayer).asBinder();
+        mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer);
+
+        Transition transit =
+                mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN);
+
+        // wallpaper windows are immediately visible when set to visible even during a transition
+        token.setVisibility(true);
+        assertTrue(wallpaperWindow.isVisible());
+        assertTrue(token.isVisibleRequested());
+        assertTrue(token.isVisible());
+        mWm.mAtmService.getTransitionController().abort(transit);
+
+        // In a transition, setting invisible should ONLY set requestedVisible false; otherwise
+        // wallpaper should remain "visible" until transition is over.
+        transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE);
+        transit.start();
+        token.setVisibility(false);
+        assertTrue(wallpaperWindow.isVisible());
+        assertFalse(token.isVisibleRequested());
+        assertTrue(token.isVisible());
+
+        transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class));
+        transit.finishTransition();
+        assertFalse(wallpaperWindow.isVisible());
+        assertFalse(token.isVisible());
+    }
+
     private WindowState createWallpaperTargetWindow(DisplayContent dc) {
         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
                 .setTask(dc.getDefaultTaskDisplayArea().getRootHomeTask())
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ebc5c4f..1f38f46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -245,6 +245,9 @@
 
     private WindowToken createWindowToken(
             DisplayContent dc, int windowingMode, int activityType, int type) {
+        if (type == TYPE_WALLPAPER) {
+            return createWallpaperToken(dc);
+        }
         if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
             return createTestWindowToken(type, dc);
         }
@@ -252,6 +255,11 @@
         return createActivityRecord(dc, windowingMode, activityType);
     }
 
+    private WindowToken createWallpaperToken(DisplayContent dc) {
+        return new WallpaperWindowToken(mWm, mock(IBinder.class), true /* explicit */, dc,
+                true /* ownerCanManageAppTokens */);
+    }
+
     WindowState createAppWindow(Task task, int type, String name) {
         final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
         task.addChild(activity, 0);
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
new file mode 100644
index 0000000..2e985fb
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.test.input
+
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputEventAssigner
+import android.view.KeyEvent
+import android.view.MotionEvent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Create a MotionEvent with the provided action, eventTime, and source
+ */
+fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent {
+    val downTime: Long = 10
+    val x = 1f
+    val y = 2f
+    val pressure = 3f
+    val size = 1f
+    val metaState = 0
+    val xPrecision = 0f
+    val yPrecision = 0f
+    val deviceId = 1
+    val edgeFlags = 0
+    val displayId = 0
+    return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
+            xPrecision, yPrecision, deviceId, edgeFlags, source, displayId)
+}
+
+fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
+    val code = KeyEvent.KEYCODE_A
+    val repeat = 0
+    return KeyEvent(eventTime, eventTime, action, code, repeat)
+}
+
+class InputEventAssignerTest {
+    companion object {
+        private const val TAG = "InputEventAssignerTest"
+    }
+
+    /**
+     * A single MOVE event should be assigned to the next available frame.
+     */
+    @Test
+    fun testTouchGesture() {
+        val assigner = InputEventAssigner()
+        val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN)
+        val eventId = assigner.processEvent(event)
+        assertEquals(event.id, eventId)
+    }
+
+    /**
+     * DOWN event should be used until a vsync comes in. After vsync, the latest event should be
+     * produced.
+     */
+    @Test
+    fun testTouchDownWithMove() {
+        val assigner = InputEventAssigner()
+        val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN)
+        val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN)
+        val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN)
+        val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN)
+        val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move1)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move2)
+        // Even though we already had 2 move events, there was no choreographer callback yet.
+        // Therefore, we should still get the id of the down event
+        assertEquals(down.id, eventId)
+
+        // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
+        assigner.onChoreographerCallback()
+        eventId = assigner.processEvent(move3)
+        assertEquals(move3.id, eventId)
+        eventId = assigner.processEvent(move4)
+        assertEquals(move4.id, eventId)
+    }
+
+    /**
+     * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency
+     * concept for non-touchscreens, the latest input event will be used.
+     */
+    @Test
+    fun testMouseDownWithMove() {
+        val assigner = InputEventAssigner()
+        val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE)
+        val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move1)
+        assertEquals(move1.id, eventId)
+    }
+
+    /**
+     * KeyEvents are processed immediately, so the latest event should be returned.
+     */
+    @Test
+    fun testKeyEvent() {
+        val assigner = InputEventAssigner()
+        val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        val up = createKeyEvent(KeyEvent.ACTION_UP, 21)
+        eventId = assigner.processEvent(up)
+        // DOWN is only sticky for Motions, not for keys
+        assertEquals(up.id, eventId)
+        assigner.onChoreographerCallback()
+        val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
+        eventId = assigner.processEvent(down2)
+        assertEquals(down2.id, eventId)
+    }
+}
diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
index c19e5cc..c01d32b 100644
--- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -17,6 +17,7 @@
 package com.android.test.input
 
 import android.graphics.FrameInfo
+import android.os.IInputConstants.INVALID_INPUT_EVENT_ID
 import android.os.SystemClock
 import android.view.ViewFrameInfo
 import com.google.common.truth.Truth.assertThat
@@ -33,8 +34,7 @@
     @Before
     fun setUp() {
         mViewFrameInfo.reset()
-        mViewFrameInfo.updateOldestInputEvent(10)
-        mViewFrameInfo.updateNewestInputEvent(20)
+        mViewFrameInfo.setInputEvent(139)
         mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
         mTimeStarted = SystemClock.uptimeNanos()
         mViewFrameInfo.markDrawStart()
@@ -43,8 +43,6 @@
     @Test
     fun testPopulateFields() {
         assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
-        assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10)
-        assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20)
         assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
     }
 
@@ -53,8 +51,6 @@
         mViewFrameInfo.reset()
         // Ensure that the original object is reset correctly
         assertThat(mViewFrameInfo.drawStart).isEqualTo(0)
-        assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0)
-        assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0)
         assertThat(mViewFrameInfo.flags).isEqualTo(0)
     }
 
@@ -62,12 +58,13 @@
     fun testUpdateFrameInfoFromViewFrameInfo() {
         val frameInfo = FrameInfo()
         // By default, all values should be zero
-        // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID
+        assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(INVALID_INPUT_EVENT_ID)
         assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0)
         assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0)
 
         // The values inside FrameInfo should match those from ViewFrameInfo after we update them
         mViewFrameInfo.populateFrameInfo(frameInfo)
+        assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139)
         assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
                 FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
         assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f0d10d2..3bc15a3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -9877,12 +9877,11 @@
                 .build();
 
         // Act on ConnectivityService.setOemNetworkPreference()
-        final TestOemListenerCallback mOnSetOemNetworkPreferenceTestListener =
-                new TestOemListenerCallback();
-        mService.setOemNetworkPreference(pref, mOnSetOemNetworkPreferenceTestListener);
+        final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
+        mService.setOemNetworkPreference(pref, oemPrefListener);
 
         // Verify call returned successfully
-        mOnSetOemNetworkPreferenceTestListener.expectOnComplete();
+        oemPrefListener.expectOnComplete();
     }
 
     private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener {
diff --git a/tests/vcn/assets/self-signed-ca.pem b/tests/vcn/assets/self-signed-ca.pem
new file mode 100644
index 0000000..5135ea7
--- /dev/null
+++ b/tests/vcn/assets/self-signed-ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPjCCAiagAwIBAgIICrKLpR7LxlowDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxHDAaBgNVBAMTE2NhLnRlc3QuYW5kcm9p
+ZC5uZXQwHhcNMTkwNzE2MTcxNTUyWhcNMjkwNzEzMTcxNTUyWjA9MQswCQYDVQQG
+EwJVUzEQMA4GA1UEChMHQW5kcm9pZDEcMBoGA1UEAxMTY2EudGVzdC5hbmRyb2lk
+Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANsvTwad2Nie0VOy
+Xb1VtHL0R760Jm4vr14JWMcX4oiE6jUdTNdXQ0CGb65wvulP2aEeukFH0D/cvBMR
+Bv9+haEwo9/grIXg9ALNKp+GfuZYw/dfnUMHFn3g2+SUgP6BoMZc4lkHktjkDKxp
+99Q6h4NP/ip1labkhBeB9+Z6l78LTixKRKspNITWASJed9bjzshYxKHi6dJy3maQ
+1LwYKmK7PEGRpoDoT8yZhFbxsVDUojGnJKH1RLXVOn/psG6dI/+IsbTipAttj5zc
+g2VAD56PZG2Jd+vsup+g4Dy72hyy242x5c/H2LKZn4X0B0B+IXyii/ZVc+DJldQ5
+JqplOL8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFGYUzuvZUaVJl8mcxejuFiUNGcTfMA0GCSqGSIb3DQEBCwUAA4IB
+AQDQYeqjvHsK2ZqSqxakDp0nu36Plbj48Wvx1ru7GW2faz7i0w/Zkxh06zniILCb
+QJRjDebSTHc5SSbCFrRTvqagaLDhbH42/hQncWqIoJqW+pmznJET4JiBO0sqzm05
+yQWsLI/h9Ir28Y2g5N+XPBU0VVVejQqH4iI0iwQx7y7ABssQ0Xa/K73VPbeGaKd6
+Prt4wjJvTlIL2yE2+0MggJ3F2rNptL5SDpg3g+4/YQ6wVRBFil95kUqplEsCtU4P
+t+8RghiEmsRx/8CywKfZ5Hex87ODhsSDmDApcefbd5gxoWVkqxZUkPcKwYv1ucm8
+u4r44fj4/9W0Zeooav5Yoh1q
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 66590c9..7515971 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -203,9 +203,6 @@
         IVcnStatusCallback cbBinder =
                 new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
 
-        cbBinder.onEnteredSafeMode();
-        verify(mMockStatusCallback).onEnteredSafeMode();
-
         cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
         verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
 
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
new file mode 100644
index 0000000..bc8e9d3
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.eap.EapSessionConfig;
+import android.os.PersistableBundle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class EapSessionConfigUtilsTest {
+    private static final byte[] EAP_ID = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+    private static final String USERNAME = "username";
+    private static final String PASSWORD = "password";
+    private static final int SUB_ID = 1;
+    private static final String NETWORK_NAME = "android.net";
+    private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = true;
+
+    private EapSessionConfig.Builder createBuilderWithId() {
+        return new EapSessionConfig.Builder().setEapIdentity(EAP_ID);
+    }
+
+    private static void verifyPersistableBundleEncodeDecodeIsLossless(EapSessionConfig config) {
+        final PersistableBundle bundle = EapSessionConfigUtils.toPersistableBundle(config);
+        final EapSessionConfig resultConfig = EapSessionConfigUtils.fromPersistableBundle(bundle);
+
+        assertEquals(config, resultConfig);
+    }
+
+    @Test
+    public void testSetEapMsChapV2EncodeDecodeIsLossless() throws Exception {
+        final EapSessionConfig config =
+                createBuilderWithId().setEapMsChapV2Config(USERNAME, PASSWORD).build();
+
+        verifyPersistableBundleEncodeDecodeIsLossless(config);
+    }
+
+    @Test
+    public void testSetEapSimEncodeDecodeIsLossless() throws Exception {
+        final EapSessionConfig config =
+                createBuilderWithId().setEapSimConfig(SUB_ID, APPTYPE_USIM).build();
+
+        verifyPersistableBundleEncodeDecodeIsLossless(config);
+    }
+
+    @Test
+    public void testSetEapAkaEncodeDecodeIsLossless() throws Exception {
+        final EapSessionConfig config =
+                createBuilderWithId().setEapAkaConfig(SUB_ID, APPTYPE_USIM).build();
+
+        verifyPersistableBundleEncodeDecodeIsLossless(config);
+    }
+
+    @Test
+    public void testSetEapAkaPrimeEncodeDecodeIsLossless() throws Exception {
+        final EapSessionConfig config =
+                createBuilderWithId()
+                        .setEapAkaPrimeConfig(
+                                SUB_ID, APPTYPE_USIM, NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES)
+                        .build();
+
+        verifyPersistableBundleEncodeDecodeIsLossless(config);
+    }
+
+    @Test
+    public void testSetEapTtlsEncodeDecodeIsLossless() throws Exception {
+        final InputStream inputStream =
+                InstrumentationRegistry.getContext()
+                        .getResources()
+                        .getAssets()
+                        .open("self-signed-ca.pem");
+        final CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        final X509Certificate trustedCa =
+                (X509Certificate) factory.generateCertificate(inputStream);
+
+        final EapSessionConfig innerConfig =
+                new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build();
+
+        final EapSessionConfig config =
+                new EapSessionConfig.Builder().setEapTtlsConfig(trustedCa, innerConfig).build();
+
+        verifyPersistableBundleEncodeDecodeIsLossless(config);
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
new file mode 100644
index 0000000..4f3930f
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+import javax.security.auth.x500.X500Principal;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IkeIdentificationUtilsTest {
+    private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeIdentification id) {
+        final PersistableBundle bundle = IkeIdentificationUtils.toPersistableBundle(id);
+        final IkeIdentification result = IkeIdentificationUtils.fromPersistableBundle(bundle);
+
+        assertEquals(result, id);
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeIpv4AddressId() throws Exception {
+        final Inet4Address ipv4Address = (Inet4Address) InetAddress.getByName("192.0.2.100");
+        verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv4AddrIdentification(ipv4Address));
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeIpv6AddressId() throws Exception {
+        final Inet6Address ipv6Address = (Inet6Address) InetAddress.getByName("2001:db8:2::100");
+        verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv6AddrIdentification(ipv6Address));
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeRfc822AddrId() throws Exception {
+        verifyPersistableBundleEncodeDecodeIsLossless(new IkeFqdnIdentification("ike.android.net"));
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeFqdnId() throws Exception {
+        verifyPersistableBundleEncodeDecodeIsLossless(
+                new IkeRfc822AddrIdentification("androidike@example.com"));
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeKeyId() throws Exception {
+        verifyPersistableBundleEncodeDecodeIsLossless(
+                new IkeKeyIdIdentification("androidIkeKeyId".getBytes()));
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeDerAsn1DnId() throws Exception {
+        verifyPersistableBundleEncodeDecodeIsLossless(
+                new IkeDerAsn1DnIdentification(
+                        new X500Principal("CN=small.server.test.android.net, O=Android, C=US")));
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
new file mode 100644
index 0000000..8ae8692
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.SaProposal;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SaProposalUtilsTest {
+    @Test
+    public void testPersistableBundleEncodeDecodeIsLosslessIkeProposal() throws Exception {
+        final IkeSaProposal proposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED)
+                        .addEncryptionAlgorithm(
+                                SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+                        .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+                        .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
+                        .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256)
+                        .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+                        .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP)
+                        .build();
+
+        final PersistableBundle bundle = IkeSaProposalUtils.toPersistableBundle(proposal);
+        final SaProposal resultProposal = IkeSaProposalUtils.fromPersistableBundle(bundle);
+
+        assertEquals(proposal, resultProposal);
+    }
+
+    /** Package private so that TunnelModeChildSessionParamsUtilsTest can use it */
+    static ChildSaProposal buildTestChildSaProposal() {
+        return new ChildSaProposal.Builder()
+                .addEncryptionAlgorithm(
+                        SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                .addEncryptionAlgorithm(
+                        SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_192)
+                .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+                .addDhGroup(SaProposal.DH_GROUP_4096_BIT_MODP)
+                .build();
+    }
+
+    @Test
+    public void testPersistableBundleEncodeDecodeIsLosslessChildProposal() throws Exception {
+        final ChildSaProposal proposal = buildTestChildSaProposal();
+
+        final PersistableBundle bundle = ChildSaProposalUtils.toPersistableBundle(proposal);
+        final SaProposal resultProposal = ChildSaProposalUtils.fromPersistableBundle(bundle);
+
+        assertEquals(proposal, resultProposal);
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
new file mode 100644
index 0000000..b3cd0ab
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TunnelModeChildSessionParamsUtilsTest {
+    private TunnelModeChildSessionParams.Builder createBuilderMinimum() {
+        final ChildSaProposal saProposal = SaProposalUtilsTest.buildTestChildSaProposal();
+        return new TunnelModeChildSessionParams.Builder().addSaProposal(saProposal);
+    }
+
+    private static void verifyPersistableBundleEncodeDecodeIsLossless(
+            TunnelModeChildSessionParams params) {
+        final PersistableBundle bundle =
+                TunnelModeChildSessionParamsUtils.toPersistableBundle(params);
+        final TunnelModeChildSessionParams result =
+                TunnelModeChildSessionParamsUtils.fromPersistableBundle(bundle);
+
+        assertEquals(params, result);
+    }
+
+    @Test
+    public void testMinimumParamsEncodeDecodeIsLossless() throws Exception {
+        final TunnelModeChildSessionParams sessionParams = createBuilderMinimum().build();
+        verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+    }
+
+    @Test
+    public void testSetTsEncodeDecodeIsLossless() throws Exception {
+        final IkeTrafficSelector tsInbound =
+                new IkeTrafficSelector(
+                        16,
+                        65520,
+                        InetAddresses.parseNumericAddress("192.0.2.100"),
+                        InetAddresses.parseNumericAddress("192.0.2.101"));
+        final IkeTrafficSelector tsOutbound =
+                new IkeTrafficSelector(
+                        32,
+                        256,
+                        InetAddresses.parseNumericAddress("192.0.2.200"),
+                        InetAddresses.parseNumericAddress("192.0.2.255"));
+
+        final TunnelModeChildSessionParams sessionParams =
+                createBuilderMinimum()
+                        .addInboundTrafficSelectors(tsInbound)
+                        .addOutboundTrafficSelectors(tsOutbound)
+                        .build();
+        verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+    }
+
+    @Test
+    public void testSetLifetimesEncodeDecodeIsLossless() throws Exception {
+        final int hardLifetime = (int) TimeUnit.HOURS.toSeconds(3L);
+        final int softLifetime = (int) TimeUnit.HOURS.toSeconds(1L);
+
+        final TunnelModeChildSessionParams sessionParams =
+                createBuilderMinimum().setLifetimeSeconds(hardLifetime, softLifetime).build();
+        verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+    }
+
+    @Test
+    public void testSetConfigRequestsEncodeDecodeIsLossless() throws Exception {
+        final int ipv6PrefixLen = 64;
+        final Inet4Address ipv4Address =
+                (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.100");
+        final Inet6Address ipv6Address =
+                (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
+
+        final TunnelModeChildSessionParams sessionParams =
+                createBuilderMinimum()
+                        .addInternalAddressRequest(AF_INET)
+                        .addInternalAddressRequest(AF_INET6)
+                        .addInternalAddressRequest(ipv4Address)
+                        .addInternalAddressRequest(ipv6Address, ipv6PrefixLen)
+                        .addInternalDnsServerRequest(AF_INET)
+                        .addInternalDnsServerRequest(AF_INET6)
+                        .addInternalDhcpServerRequest(AF_INET)
+                        .build();
+        verifyPersistableBundleEncodeDecodeIsLossless(sessionParams);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 9b500a7..73a6b88 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -100,6 +100,8 @@
 public class VcnManagementServiceTest {
     private static final String TEST_PACKAGE_NAME =
             VcnManagementServiceTest.class.getPackage().getName();
+    private static final String TEST_CB_PACKAGE_NAME =
+            VcnManagementServiceTest.class.getPackage().getName() + ".callback";
     private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
     private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
     private static final VcnConfig TEST_VCN_CONFIG;
@@ -288,6 +290,14 @@
 
     private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
             Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
+        return triggerSubscriptionTrackerCbAndGetSnapshot(
+                activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */);
+    }
+
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            Set<ParcelUuid> activeSubscriptionGroups,
+            Map<Integer, ParcelUuid> subIdToGroupMap,
+            boolean hasCarrierPrivileges) {
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
 
@@ -295,7 +305,7 @@
                 (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
                         ? Collections.emptySet()
                         : Collections.singleton(TEST_PACKAGE_NAME);
-        doReturn(true)
+        doReturn(hasCarrierPrivileges)
                 .when(snapshot)
                 .packageHasPermissionsForSubscriptionGroup(
                         argThat(val -> activeSubscriptionGroups.contains(val)),
@@ -549,13 +559,6 @@
         mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
     }
 
-    private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
-        mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
-
-        triggerSubscriptionTrackerCbAndGetSnapshot(
-                Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
-    }
-
     private void verifyMergedNetworkCapabilities(
             NetworkCapabilities mergedCapabilities,
             @Transport int transportType,
@@ -573,9 +576,23 @@
     }
 
     private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) {
-        setUpVcnSubscription(subId, subGrp);
+        setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive, true /* hasCarrierPrivileges */);
+    }
+
+    private void setupSubscriptionAndStartVcn(
+            int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
+        mVcnMgmtSvc.systemReady();
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                Collections.singleton(subGrp),
+                Collections.singletonMap(subId, subGrp),
+                hasCarrierPrivileges);
+
         final Vcn vcn = startAndGetVcnInstance(subGrp);
         doReturn(isVcnActive).when(vcn).isActive();
+
+        doReturn(true)
+                .when(mLocationPermissionChecker)
+                .checkLocationPermission(eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), any());
     }
 
     private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport(
@@ -721,7 +738,7 @@
         verify(mMockPolicyListener).onPolicyChanged();
     }
 
-    private void verifyVcnCallback(
+    private void triggerVcnSafeMode(
             @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
             throws Exception {
         verify(mMockDeps)
@@ -732,20 +749,20 @@
                         eq(snapshot),
                         mVcnCallbackCaptor.capture());
 
-        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
-
         VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
         vcnCallback.onEnteredSafeMode();
-
-        verify(mMockPolicyListener).onPolicyChanged();
     }
 
     @Test
-    public void testVcnCallbackOnEnteredSafeMode() throws Exception {
+    public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception {
         TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
 
-        verifyVcnCallback(TEST_UUID_1, snapshot);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        triggerVcnSafeMode(TEST_UUID_1, snapshot);
+
+        verify(mMockPolicyListener).onPolicyChanged();
     }
 
     private void triggerVcnStatusCallbackOnEnteredSafeMode(
@@ -758,6 +775,9 @@
         TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
 
+        setupSubscriptionAndStartVcn(
+                TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup);
+
         doReturn(hasPermissionsforSubGroup)
                 .when(snapshot)
                 .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
@@ -768,10 +788,7 @@
 
         mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
 
-        // Trigger systemReady() to set up LocationPermissionChecker
-        mVcnMgmtSvc.systemReady();
-
-        verifyVcnCallback(subGroup, snapshot);
+        triggerVcnSafeMode(subGroup, snapshot);
     }
 
     @Test
@@ -825,6 +842,83 @@
         assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
         assertEquals(TEST_UID, cbInfo.mUid);
         verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+
+        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback_MissingPermission() throws Exception {
+        setupSubscriptionAndStartVcn(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_1,
+                true /* isActive */,
+                false /* hasCarrierPrivileges */);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback_VcnInactive() throws Exception {
+        setupSubscriptionAndStartVcn(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_1,
+                true /* isActive */,
+                true /* hasCarrierPrivileges */);
+
+        // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown
+        // timeout so the VCN goes inactive.
+        final TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        Collections.singleton(TEST_UUID_1),
+                        Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
+                        false /* hasCarrierPrivileges */);
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE
+        // when the status callback is registered). Instead, setup permissions for TEST_CB_PACKAGE
+        // so that it's permissioned to receive INACTIVE (instead of NOT_CONFIGURED) without
+        // reactivating the VCN.
+        doReturn(true)
+                .when(snapshot)
+                .packageHasPermissionsForSubscriptionGroup(
+                        eq(TEST_UUID_1), eq(TEST_CB_PACKAGE_NAME));
+        doReturn(true)
+                .when(mLocationPermissionChecker)
+                .checkLocationPermission(eq(TEST_CB_PACKAGE_NAME), any(), eq(TEST_UID), any());
+
+        mVcnMgmtSvc.registerVcnStatusCallback(
+                TEST_UUID_1, mMockStatusCallback, TEST_CB_PACKAGE_NAME);
+
+        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_INACTIVE);
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback_VcnActive() throws Exception {
+        setupSubscriptionAndStartVcn(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_1,
+                true /* isActive */,
+                true /* hasCarrierPrivileges */);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback_VcnSafeMode() throws Exception {
+        setupSubscriptionAndStartVcn(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_1,
+                false /* isActive */,
+                true /* hasCarrierPrivileges */);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE);
     }
 
     @Test(expected = IllegalStateException.class)