Merge "Fixes the flicker when transfer splash screen view to client"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index acc661e..1429c45 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -235,6 +235,14 @@
     public static final int STOP_REASON_USER = 13;
     /** The system is doing some processing that requires stopping this job. */
     public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
+    /**
+     * The system's estimate of when the app will be launched changed significantly enough to
+     * decide this job shouldn't be running right now. This will mostly apply to prefetch jobs.
+     *
+     * @see JobInfo#isPrefetch()
+     * @see JobInfo.Builder#setPrefetch(boolean)
+     */
+    public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15;
 
     /** @hide */
     @IntDef(prefix = {"STOP_REASON_"}, value = {
@@ -253,6 +261,7 @@
             STOP_REASON_APP_STANDBY,
             STOP_REASON_USER,
             STOP_REASON_SYSTEM_PROCESSING,
+            STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StopReason {
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 9c4cada..a23f6e1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import android.annotation.NonNull;
@@ -105,6 +106,7 @@
 import com.android.server.job.controllers.DeviceIdleJobsController;
 import com.android.server.job.controllers.IdleController;
 import com.android.server.job.controllers.JobStatus;
+import com.android.server.job.controllers.PrefetchController;
 import com.android.server.job.controllers.QuotaController;
 import com.android.server.job.controllers.RestrictingController;
 import com.android.server.job.controllers.StateController;
@@ -252,6 +254,8 @@
     private final StorageController mStorageController;
     /** Need directly for sending uid state changes */
     private final DeviceIdleJobsController mDeviceIdleJobsController;
+    /** Needed to get next estimated launch time. */
+    private final PrefetchController mPrefetchController;
     /** Needed to get remaining quota time. */
     private final QuotaController mQuotaController;
     /** Needed to get max execution time and expedited-job allowance. */
@@ -427,6 +431,9 @@
                         case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
                             mConstants.updateConnectivityConstantsLocked();
                             break;
+                        case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
+                            mConstants.updatePrefetchConstantsLocked();
+                            break;
                         case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
@@ -482,6 +489,8 @@
         private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
+        private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
+                "prefetch_force_batch_relax_threshold_ms";
         private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
         private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
         private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -503,6 +512,7 @@
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+        private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
         private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
         private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
         private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
@@ -557,6 +567,14 @@
         public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
 
         /**
+         * The amount of time within which we would consider the app to be launching relatively soon
+         * and will relax the force batching policy on prefetch jobs. If the app is not going to be
+         * launched within this amount of time from now, then we will force batch the prefetch job.
+         */
+        public long PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
+                DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
+
+        /**
          * Whether to enable quota limits on APIs.
          */
         public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS;
@@ -635,6 +653,13 @@
                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
         }
 
+        private void updatePrefetchConstantsLocked() {
+            PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+                    DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+        }
+
         private void updateApiQuotaConstantsLocked() {
             ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
@@ -700,6 +725,8 @@
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+            pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+                    PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
 
             pw.print(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
             pw.print(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
@@ -1577,6 +1604,8 @@
         mControllers.add(new ContentObserverController(this));
         mDeviceIdleJobsController = new DeviceIdleJobsController(this);
         mControllers.add(mDeviceIdleJobsController);
+        mPrefetchController = new PrefetchController(this);
+        mControllers.add(mPrefetchController);
         mQuotaController =
                 new QuotaController(this, backgroundJobsController, connectivityController);
         mControllers.add(mQuotaController);
@@ -2333,6 +2362,15 @@
                 } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
                     // Restricted jobs must always be batched
                     shouldForceBatchJob = true;
+                } else if (job.getJob().isPrefetch()) {
+                    // Only relax batching on prefetch jobs if we expect the app to be launched
+                    // relatively soon. PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS defines what
+                    // "relatively soon" means.
+                    final long relativelySoonCutoffTime = sSystemClock.millis()
+                            + mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
+                    shouldForceBatchJob =
+                            mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
+                                    > relativelySoonCutoffTime;
                 } else if (job.getNumFailures() > 0) {
                     shouldForceBatchJob = false;
                 } else {
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 3801885..d35c03d 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
@@ -93,6 +93,7 @@
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
+    static final int CONSTRAINT_PREFETCH = 1 << 23;
     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
@@ -147,6 +148,7 @@
     private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
             | CONSTRAINT_DEADLINE
             | CONSTRAINT_IDLE
+            | CONSTRAINT_PREFETCH
             | CONSTRAINT_TARE_WEALTH
             | CONSTRAINT_TIMING_DELAY
             | CONSTRAINT_WITHIN_QUOTA;
@@ -1176,6 +1178,11 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
+    boolean setPrefetchConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_PREFETCH, nowElapsed, state);
+    }
+
+    /** @return true if the constraint was changed, false otherwise. */
     boolean setTimingDelayConstraintSatisfied(final long nowElapsed, boolean state) {
         return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, nowElapsed, state);
     }
@@ -1387,6 +1394,9 @@
             case CONSTRAINT_DEVICE_NOT_DOZING:
                 return JobParameters.STOP_REASON_DEVICE_STATE;
 
+            case CONSTRAINT_PREFETCH:
+                return JobParameters.STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED;
+
             case CONSTRAINT_TARE_WEALTH:
             case CONSTRAINT_WITHIN_QUOTA:
                 return JobParameters.STOP_REASON_QUOTA;
@@ -1581,12 +1591,12 @@
     /** All constraints besides implicit and deadline. */
     static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
             | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
-            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
+            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER | CONSTRAINT_PREFETCH;
 
     // Soft override covers all non-"functional" constraints
     static final int SOFT_OVERRIDE_CONSTRAINTS =
             CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
-                    | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
+                    | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH;
 
     /** Returns true whenever all dynamically set constraints are satisfied. */
     public boolean areDynamicConstraintsSatisfied() {
@@ -1775,6 +1785,9 @@
         if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
             pw.print(" BACKGROUND_NOT_RESTRICTED");
         }
+        if ((constraints & CONSTRAINT_PREFETCH) != 0) {
+            pw.print(" PREFETCH");
+        }
         if ((constraints & CONSTRAINT_TARE_WEALTH) != 0) {
             pw.print(" TARE_WEALTH");
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
new file mode 100644
index 0000000..725092c
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArrayMap;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.job.JobSchedulerService;
+
+import java.util.function.Predicate;
+
+/**
+ * Controller to delay prefetch jobs until we get close to an expected app launch.
+ */
+public class PrefetchController extends StateController {
+    private static final String TAG = "JobScheduler.Prefetch";
+    private static final boolean DEBUG = JobSchedulerService.DEBUG
+            || Log.isLoggable(TAG, Log.DEBUG);
+
+    private final PcConstants mPcConstants;
+
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
+    /**
+     * Cached set of the estimated next launch times of each app. Time are in the current time
+     * millis ({@link CurrentTimeMillisLong}) timebase.
+     */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+
+    /**
+     * The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
+     * to launch within this amount of time into the future, then we will let a prefetch job run.
+     */
+    @GuardedBy("mLock")
+    @CurrentTimeMillisLong
+    private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
+
+    public PrefetchController(JobSchedulerService service) {
+        super(service);
+        mPcConstants = new PcConstants();
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        if (jobStatus.getJob().isPrefetch()) {
+            final int userId = jobStatus.getSourceUserId();
+            final String pkgName = jobStatus.getSourcePackageName();
+            ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+            if (jobs == null) {
+                jobs = new ArraySet<>();
+                mTrackedJobs.add(userId, pkgName, jobs);
+            }
+            jobs.add(jobStatus);
+            updateConstraintLocked(jobStatus,
+                    sSystemClock.millis(), sElapsedRealtimeClock.millis());
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+            boolean forUpdate) {
+        final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
+                jobStatus.getSourcePackageName());
+        if (jobs != null) {
+            jobs.remove(jobStatus);
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onAppRemovedLocked(String packageName, int uid) {
+        if (packageName == null) {
+            Slog.wtf(TAG, "Told app removed but given null package name.");
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        mTrackedJobs.delete(userId, packageName);
+        mEstimatedLaunchTimes.delete(userId, packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onUserRemovedLocked(int userId) {
+        mTrackedJobs.delete(userId);
+        mEstimatedLaunchTimes.delete(userId);
+    }
+
+    /** Return the app's next estimated launch time. */
+    @GuardedBy("mLock")
+    @CurrentTimeMillisLong
+    public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
+        final int userId = jobStatus.getSourceUserId();
+        final String pkgName = jobStatus.getSourcePackageName();
+        Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
+        final long now = sSystemClock.millis();
+        if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
+            // TODO(194532703): get estimated time from UsageStats
+            nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
+            mEstimatedLaunchTimes.add(userId, pkgName, nextEstimatedLaunchTime);
+        }
+        return nextEstimatedLaunchTime;
+    }
+
+    @GuardedBy("mLock")
+    private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
+            String pkgName) {
+        final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+        if (jobs == null) {
+            return false;
+        }
+        boolean changed = false;
+        for (int i = 0; i < jobs.size(); i++) {
+            final JobStatus js = jobs.valueAt(i);
+            changed |= updateConstraintLocked(js, now, nowElapsed);
+        }
+        return changed;
+    }
+
+    @GuardedBy("mLock")
+    private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
+            long nowElapsed) {
+        return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
+                getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void prepareForUpdatedConstantsLocked() {
+        mPcConstants.mShouldReevaluateConstraints = false;
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void processConstantLocked(DeviceConfig.Properties properties, String key) {
+        mPcConstants.processConstantLocked(properties, key);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onConstantsUpdatedLocked() {
+        if (mPcConstants.mShouldReevaluateConstraints) {
+            // Update job bookkeeping out of band.
+            JobSchedulerBackgroundThread.getHandler().post(() -> {
+                final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+                synchronized (mLock) {
+                    final long nowElapsed = sElapsedRealtimeClock.millis();
+                    final long now = sSystemClock.millis();
+                    for (int u = 0; u < mTrackedJobs.numMaps(); ++u) {
+                        final int userId = mTrackedJobs.keyAt(u);
+                        for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
+                            final String packageName = mTrackedJobs.keyAt(u, p);
+                            if (maybeUpdateConstraintForPkgLocked(
+                                    now, nowElapsed, userId, packageName)) {
+                                changedJobs.addAll(mTrackedJobs.valueAt(u, p));
+                            }
+                        }
+                    }
+                }
+                if (changedJobs.size() > 0) {
+                    mStateChangedListener.onControllerStateChanged(changedJobs);
+                }
+            });
+        }
+    }
+
+    @VisibleForTesting
+    class PcConstants {
+        private boolean mShouldReevaluateConstraints = false;
+
+        /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+        private static final String PC_CONSTANT_PREFIX = "pc_";
+
+        @VisibleForTesting
+        static final String KEY_LAUNCH_TIME_THRESHOLD_MS =
+                PC_CONSTANT_PREFIX + "launch_time_threshold_ms";
+
+        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS;
+
+        /** How much time each app will have to run jobs within their standby bucket window. */
+        public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
+
+        @GuardedBy("mLock")
+        public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+                @NonNull String key) {
+            switch (key) {
+                case KEY_LAUNCH_TIME_THRESHOLD_MS:
+                    LAUNCH_TIME_THRESHOLD_MS =
+                            properties.getLong(key, DEFAULT_LAUNCH_TIME_THRESHOLD_MS);
+                    // Limit the threshold to the range [1, 24] hours.
+                    long newLaunchTimeThresholdMs = Math.min(24 * HOUR_IN_MILLIS,
+                            Math.max(HOUR_IN_MILLIS, LAUNCH_TIME_THRESHOLD_MS));
+                    if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
+                        mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+            }
+        }
+
+        private void dump(IndentingPrintWriter pw) {
+            pw.println();
+            pw.print(PrefetchController.class.getSimpleName());
+            pw.println(":");
+            pw.increaseIndent();
+
+            pw.print(KEY_LAUNCH_TIME_THRESHOLD_MS, LAUNCH_TIME_THRESHOLD_MS).println();
+
+            pw.decreaseIndent();
+        }
+    }
+
+    //////////////////////// TESTING HELPERS /////////////////////////////
+
+    @VisibleForTesting
+    long getLaunchTimeThresholdMs() {
+        return mLaunchTimeThresholdMs;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    PcConstants getPcConstants() {
+        return mPcConstants;
+    }
+
+    //////////////////////////// DATA DUMP //////////////////////////////
+
+    @Override
+    @GuardedBy("mLock")
+    public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+        final long now = sSystemClock.millis();
+
+        pw.println("Cached launch times:");
+        pw.increaseIndent();
+        for (int u = 0; u < mEstimatedLaunchTimes.numMaps(); ++u) {
+            final int userId = mEstimatedLaunchTimes.keyAt(u);
+            for (int p = 0; p < mEstimatedLaunchTimes.numElementsForKey(userId); ++p) {
+                final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
+                final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);
+
+                pw.print("<" + userId + ">" + pkgName + ": ");
+                pw.print(estimatedLaunchTime);
+                pw.print(" (");
+                TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
+                        TimeUtils.HUNDRED_DAY_FIELD_LEN);
+                pw.println(" from now)");
+            }
+        }
+        pw.decreaseIndent();
+
+        pw.println();
+        mTrackedJobs.forEach((jobs) -> {
+            for (int j = 0; j < jobs.size(); j++) {
+                final JobStatus js = jobs.valueAt(j);
+                if (!predicate.test(js)) {
+                    continue;
+                }
+                pw.print("#");
+                js.printUniqueId(pw);
+                pw.print(" from ");
+                UserHandle.formatUid(pw, js.getSourceUid());
+                pw.println();
+            }
+        });
+    }
+
+    @Override
+    public void dumpConstants(IndentingPrintWriter pw) {
+        mPcConstants.dump(pw);
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index 1acb270..2b4938b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -94,6 +94,7 @@
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
 import static com.android.server.tare.TareUtils.arcToNarc;
+import static com.android.server.tare.TareUtils.narcToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -342,6 +343,11 @@
 
     @Override
     void dump(IndentingPrintWriter pw) {
+        pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println();
+        pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
+        pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+
+        pw.println();
         pw.println("Actions:");
         pw.increaseIndent();
         for (int i = 0; i < mActions.size(); ++i) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index fa732ea..728f5e8 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -103,6 +103,7 @@
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
 import static com.android.server.tare.TareUtils.arcToNarc;
+import static com.android.server.tare.TareUtils.narcToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -316,6 +317,11 @@
 
     @Override
     void dump(IndentingPrintWriter pw) {
+        pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println();
+        pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
+        pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+
+        pw.println();
         pw.println("Actions:");
         pw.increaseIndent();
         for (int i = 0; i < mActions.size(); ++i) {
diff --git a/core/api/current.txt b/core/api/current.txt
index f8891b4..7855667 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8055,6 +8055,7 @@
     field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
     field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
     field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+    field public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; // 0xf
     field public static final int STOP_REASON_PREEMPT = 2; // 0x2
     field public static final int STOP_REASON_QUOTA = 10; // 0xa
     field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
@@ -10003,6 +10004,7 @@
 
   public static final class AttributionSource.Builder {
     ctor public AttributionSource.Builder(int);
+    ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
@@ -22822,7 +22824,7 @@
 
   public class MediaMetadataRetriever implements java.lang.AutoCloseable {
     ctor public MediaMetadataRetriever();
-    method public void close();
+    method public void close() throws java.io.IOException;
     method @Nullable public String extractMetadata(int);
     method @Nullable public byte[] getEmbeddedPicture();
     method @Nullable public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
@@ -22839,7 +22841,7 @@
     method @Nullable public android.graphics.Bitmap getPrimaryImage();
     method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int);
     method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
-    method public void release();
+    method public void release() throws java.io.IOException;
     method public void setDataSource(String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.io.FileDescriptor, long, long) throws java.lang.IllegalArgumentException;
@@ -34948,6 +34950,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+    method @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
     field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9e47e96..3373d4b 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -262,6 +262,7 @@
     field public static final String SERIAL_PORT = "android.permission.SERIAL_PORT";
     field public static final String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
     field public static final String SET_CLIP_SOURCE = "android.permission.SET_CLIP_SOURCE";
+    field public static final String SET_DEFAULT_ACCOUNT_FOR_CONTACTS = "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
     field public static final String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS";
     field public static final String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
     field public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
@@ -9181,6 +9182,10 @@
     field @Deprecated public static final String STATE = "state";
   }
 
+  public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+    method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
+  }
+
   public static final class ContactsContract.SimContacts {
     method @RequiresPermission("android.contacts.permission.MANAGE_SIM_ACCOUNTS") public static void addSimAccount(@NonNull android.content.ContentResolver, @NonNull String, @NonNull String, int, int);
     method @RequiresPermission("android.contacts.permission.MANAGE_SIM_ACCOUNTS") public static void removeSimAccounts(@NonNull android.content.ContentResolver, int);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f5dd6bd..6011cc2 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -62,7 +62,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -80,6 +79,7 @@
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.pkg.PackageUserState;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -2130,7 +2130,7 @@
             return null;
         }
         return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
-                new PackageUserState(), UserHandle.getCallingUserId());
+                PackageUserState.DEFAULT, UserHandle.getCallingUserId());
     }
 
     @Override
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index c21362c..20152f3 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -88,8 +88,9 @@
                 uid = android.os.Process.SYSTEM_UID;
             }
             try {
-                res = new AttributionSource(uid,
-                        AppGlobals.getPackageManager().getPackagesForUid(uid)[0], null);
+                res = new AttributionSource.Builder(uid)
+                    .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
+                    .build();
             } catch (RemoteException ignored) {
             }
         }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bdb7900..6ae2bb5 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -474,6 +474,18 @@
             mAttributionSourceState.uid = uid;
         }
 
+        public Builder(@NonNull AttributionSource current) {
+            if (current == null) {
+                throw new IllegalArgumentException("current AttributionSource can not be null");
+            }
+            mAttributionSourceState.uid = current.getUid();
+            mAttributionSourceState.packageName = current.getPackageName();
+            mAttributionSourceState.attributionTag = current.getAttributionTag();
+            mAttributionSourceState.token = current.getToken();
+            mAttributionSourceState.renouncedPermissions =
+                current.mAttributionSourceState.renouncedPermissions;
+        }
+
         /**
          * The package that is accessing the permission protected data.
          */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3ab070f..232e220 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3001,7 +3001,7 @@
      * @param receiver The BroadcastReceiver to handle the broadcast.
      * @param filter Selects the Intent broadcasts to be received.
      * @param flags Additional options for the receiver. As of
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU}, either {@link #RECEIVER_EXPORTED} or
+     * Android T, either {@link #RECEIVER_EXPORTED} or
      * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
      *            for protected broadcasts, and may additionally specify
      *            {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS} if {@link #RECEIVER_EXPORTED} is
@@ -3078,7 +3078,7 @@
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
      * @param flags Additional options for the receiver. As of
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU}, either {@link #RECEIVER_EXPORTED} or
+     * Android T, either {@link #RECEIVER_EXPORTED} or
      * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
      *            for protected broadcasts, and may additionally specify
      *            {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS} if {@link #RECEIVER_EXPORTED} is
@@ -3142,7 +3142,7 @@
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent. If {@code null}, the main thread of the process will be used.
      * @param flags Additional options for the receiver. As of
-     *      {@link android.os.Build.VERSION_CODES#TIRAMISU}, either {@link #RECEIVER_EXPORTED} or
+     *      Android T, either {@link #RECEIVER_EXPORTED} or
      *      {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being
      *      registered for protected broadcasts
      *
@@ -3209,7 +3209,7 @@
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
      * @param flags Additional options for the receiver. As of
-     *      {@link android.os.Build.VERSION_CODES#TIRAMISU}, either {@link #RECEIVER_EXPORTED} or
+     *      Android T, either {@link #RECEIVER_EXPORTED} or
      *      {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being
      *      registered for protected broadcasts
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f9d3a14..07151ec 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -49,6 +49,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.pkg.PackageUserState;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1b34d9b..cbf54e6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -56,6 +56,8 @@
 import android.content.IntentFilter;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.content.pm.split.SplitAssetLoader;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -647,14 +649,14 @@
             ApplicationInfo appInfo) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
-                && !state.installed
+                && !state.isInstalled()
                 && appInfo != null && appInfo.hiddenUntilInstalled) {
             return false;
         }
 
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
-        return state.isAvailable(flags)
+        return PackageUserStateUtils.isAvailable(state, flags)
                 || (appInfo != null && appInfo.isSystemApp()
                         && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
                         || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
@@ -699,7 +701,7 @@
     public static PackageInfo generatePackageInfo(
             PackageParser.Package pkg, ApexInfo apexInfo, int flags) {
         return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0,
-                Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId());
+                Collections.emptySet(), PackageUserState.DEFAULT, UserHandle.getCallingUserId());
     }
 
     private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo,
@@ -764,7 +766,7 @@
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final Activity a = p.activities.get(i);
-                    if (state.isMatch(a.info, flags)) {
+                    if (PackageUserStateUtils.isMatch(state, a.info, flags)) {
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
                             continue;
                         }
@@ -781,7 +783,7 @@
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final Activity a = p.receivers.get(i);
-                    if (state.isMatch(a.info, flags)) {
+                    if (PackageUserStateUtils.isMatch(state, a.info, flags)) {
                         res[num++] = generateActivityInfo(a, flags, state, userId);
                     }
                 }
@@ -795,7 +797,7 @@
                 final ServiceInfo[] res = new ServiceInfo[N];
                 for (int i = 0; i < N; i++) {
                     final Service s = p.services.get(i);
-                    if (state.isMatch(s.info, flags)) {
+                    if (PackageUserStateUtils.isMatch(state, s.info, flags)) {
                         res[num++] = generateServiceInfo(s, flags, state, userId);
                     }
                 }
@@ -809,7 +811,7 @@
                 final ProviderInfo[] res = new ProviderInfo[N];
                 for (int i = 0; i < N; i++) {
                     final Provider pr = p.providers.get(i);
-                    if (state.isMatch(pr.info, flags)) {
+                    if (PackageUserStateUtils.isMatch(state, pr.info, flags)) {
                         res[num++] = generateProviderInfo(pr, flags, state, userId);
                     }
                 }
@@ -7887,23 +7889,24 @@
             // to fix up the uid.
             return true;
         }
-        if (state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
-            boolean enabled = state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        if (state.getEnabledState() != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+            boolean enabled =
+                    state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
             if (p.applicationInfo.enabled != enabled) {
                 return true;
             }
         }
         boolean suspended = (p.applicationInfo.flags & FLAG_SUSPENDED) != 0;
-        if (state.suspended != suspended) {
+        if (state.isSuspended() != suspended) {
             return true;
         }
-        if (!state.installed || state.hidden) {
+        if (!state.isInstalled() || state.isHidden()) {
             return true;
         }
-        if (state.stopped) {
+        if (state.isStopped()) {
             return true;
         }
-        if (state.instantApp != p.applicationInfo.isInstantApp()) {
+        if (state.isInstantApp() != p.applicationInfo.isInstantApp()) {
             return true;
         }
         if ((flags & PackageManager.GET_META_DATA) != 0
@@ -7936,40 +7939,42 @@
         if (!sCompatibilityModeEnabled) {
             ai.disableCompatibilityMode();
         }
-        if (state.installed) {
+        if (state.isInstalled()) {
             ai.flags |= ApplicationInfo.FLAG_INSTALLED;
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
-        if (state.suspended) {
+        if (state.isSuspended()) {
             ai.flags |= ApplicationInfo.FLAG_SUSPENDED;
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED;
         }
-        if (state.instantApp) {
+        if (state.isInstantApp()) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
         } else {
             ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_INSTANT;
         }
-        if (state.virtualPreload) {
+        if (state.isVirtualPreload()) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD;
         } else {
             ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD;
         }
-        if (state.hidden) {
+        if (state.isHidden()) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
         } else {
             ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN;
         }
-        if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+        if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
             ai.enabled = true;
-        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+        } else if (state.getEnabledState()
+                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
             ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
-        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+        } else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                || state.getEnabledState()
+                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
             ai.enabled = false;
         }
-        ai.enabledSetting = state.enabled;
+        ai.enabledSetting = state.getEnabledState();
         if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
@@ -7991,7 +7996,8 @@
         }
         if (!copyNeeded(flags, p, state, null, userId)
                 && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
-                        || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+                        || state.getEnabledState()
+                                != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
             // In this case it is safe to directly modify the internal ApplicationInfo state:
             // - CompatibilityMode is global state, so will be the same for every call.
             // - We only come in to here if the app should reported as installed; this is the
@@ -8013,7 +8019,7 @@
             ai.sharedLibraryFiles = p.usesLibraryFiles;
             ai.sharedLibraryInfos = p.usesLibraryInfos;
         }
-        if (state.stopped) {
+        if (state.isStopped()) {
             ai.flags |= ApplicationInfo.FLAG_STOPPED;
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
@@ -8032,7 +8038,7 @@
         // make a copy.
         ai = new ApplicationInfo(ai);
         ai.initForUser(userId);
-        if (state.stopped) {
+        if (state.isStopped()) {
             ai.flags |= ApplicationInfo.FLAG_STOPPED;
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
deleted file mode 100644
index 3fb1999..0000000
--- a/core/java/android/content/pm/PackageUserState.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2012 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;
-
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
-import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
-import android.content.pm.overlay.OverlayPaths;
-import android.content.pm.parsing.ParsingPackageRead;
-import android.content.pm.parsing.component.ParsedMainComponent;
-import android.os.BaseBundle;
-import android.os.Debug;
-import android.os.PersistableBundle;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.DebugUtils;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * Per-user state information about a package.
- * @hide
- */
-public class PackageUserState implements android.content.pm.pkg.PackageUserState {
-    private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "PackageUserState";
-
-    public long ceDataInode;
-    public boolean installed;
-    public boolean stopped;
-    public boolean notLaunched;
-    public boolean hidden; // Is the app restricted by owner / admin
-    public int distractionFlags;
-    public boolean suspended;
-    public ArrayMap<String, SuspendParams> suspendParams; // Suspending package to suspend params
-    public boolean instantApp;
-    public boolean virtualPreload;
-    public int enabled;
-    public String lastDisableAppCaller;
-    @PackageManager.InstallReason
-    public int installReason;
-    public @PackageManager.UninstallReason int uninstallReason;
-    public String harmfulAppWarning;
-    public String splashScreenTheme;
-
-    public ArraySet<String> disabledComponents;
-    public ArraySet<String> enabledComponents;
-
-    private OverlayPaths overlayPaths;
-    // Maps library name to overlay paths.
-    private ArrayMap<String, OverlayPaths> sharedLibraryOverlayPaths;
-    private OverlayPaths cachedOverlayPaths;
-
-    @Nullable
-    private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap;
-
-    @UnsupportedAppUsage
-    public PackageUserState() {
-        installed = true;
-        hidden = false;
-        suspended = false;
-        enabled = COMPONENT_ENABLED_STATE_DEFAULT;
-        installReason = PackageManager.INSTALL_REASON_UNKNOWN;
-        uninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
-    }
-
-    @VisibleForTesting
-    public PackageUserState(PackageUserState o) {
-        ceDataInode = o.ceDataInode;
-        installed = o.installed;
-        stopped = o.stopped;
-        notLaunched = o.notLaunched;
-        hidden = o.hidden;
-        distractionFlags = o.distractionFlags;
-        suspended = o.suspended;
-        suspendParams = new ArrayMap<>(o.suspendParams);
-        instantApp = o.instantApp;
-        virtualPreload = o.virtualPreload;
-        enabled = o.enabled;
-        lastDisableAppCaller = o.lastDisableAppCaller;
-        installReason = o.installReason;
-        uninstallReason = o.uninstallReason;
-        disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
-        enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
-        overlayPaths = o.overlayPaths;
-        if (o.sharedLibraryOverlayPaths != null) {
-            sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
-        }
-        harmfulAppWarning = o.harmfulAppWarning;
-        if (o.componentLabelIconOverrideMap != null) {
-            this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
-        }
-        splashScreenTheme = o.splashScreenTheme;
-    }
-
-    /**
-     * Sets the path of overlays currently enabled for this package and user combination.
-     * @return true if the path contents differ than what they were previously
-     */
-    @Nullable
-    public boolean setOverlayPaths(@Nullable OverlayPaths paths) {
-        if (Objects.equals(paths, overlayPaths)) {
-            return false;
-        }
-        if ((overlayPaths == null && paths.isEmpty())
-                || (paths == null && overlayPaths.isEmpty())) {
-            return false;
-        }
-        overlayPaths = paths;
-        cachedOverlayPaths = null;
-        return true;
-    }
-
-    /**
-     * Sets the path of overlays currently enabled for a library that this package uses.
-     *
-     * @return true if the path contents for the library differ than what they were previously
-     */
-    public boolean setSharedLibraryOverlayPaths(@NonNull String library,
-            @Nullable OverlayPaths paths) {
-        if (sharedLibraryOverlayPaths == null) {
-            sharedLibraryOverlayPaths = new ArrayMap<>();
-        }
-        final OverlayPaths currentPaths = sharedLibraryOverlayPaths.get(library);
-        if (Objects.equals(paths, currentPaths)) {
-            return false;
-        }
-        cachedOverlayPaths = null;
-        if (paths == null || paths.isEmpty()) {
-            return sharedLibraryOverlayPaths.remove(library) != null;
-        } else {
-            sharedLibraryOverlayPaths.put(library, paths);
-            return true;
-        }
-    }
-
-    /**
-     * Overrides the non-localized label and icon of a component.
-     *
-     * @return true if the label or icon was changed.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public boolean overrideLabelAndIcon(@NonNull ComponentName component,
-            @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
-        String existingLabel = null;
-        Integer existingIcon = null;
-
-        if (componentLabelIconOverrideMap != null) {
-            Pair<String, Integer> pair = componentLabelIconOverrideMap.get(component);
-            if (pair != null) {
-                existingLabel = pair.first;
-                existingIcon = pair.second;
-            }
-        }
-
-        boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel)
-                || !Objects.equals(existingIcon, icon);
-
-        if (changed) {
-            if (nonLocalizedLabel == null && icon == null) {
-                componentLabelIconOverrideMap.remove(component);
-                if (componentLabelIconOverrideMap.isEmpty()) {
-                    componentLabelIconOverrideMap = null;
-                }
-            } else {
-                if (componentLabelIconOverrideMap == null) {
-                    componentLabelIconOverrideMap = new ArrayMap<>(1);
-                }
-
-                componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon));
-            }
-        }
-
-        return changed;
-    }
-
-    /**
-     * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName,
-     * String, Integer)}.
-     *
-     * This is done when the package is updated as the components and resource IDs may have changed.
-     */
-    public void resetOverrideComponentLabelIcon() {
-        componentLabelIconOverrideMap = null;
-    }
-
-    @Nullable
-    public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
-        if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) {
-            return null;
-        }
-
-        return componentLabelIconOverrideMap.get(componentName);
-    }
-
-
-    /**
-     * Test if this package is installed.
-     */
-    public boolean isAvailable(int flags) {
-        // True if it is installed for this user and it is not hidden. If it is hidden,
-        // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES
-        final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
-        final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
-        return matchAnyUser
-                || (this.installed
-                        && (!this.hidden || matchUninstalled));
-    }
-
-    public boolean isMatch(ComponentInfo componentInfo, int flags) {
-        return isMatch(componentInfo.applicationInfo.isSystemApp(),
-                componentInfo.applicationInfo.enabled, componentInfo.enabled,
-                componentInfo.directBootAware, componentInfo.name, flags);
-    }
-
-    public boolean isMatch(boolean isSystem, boolean isPackageEnabled,
-            ParsedMainComponent component, int flags) {
-        return isMatch(isSystem, isPackageEnabled, component.isEnabled(),
-                component.isDirectBootAware(), component.getName(), flags);
-    }
-
-    /**
-     * Test if the given component is considered installed, enabled and a match
-     * for the given flags.
-     *
-     * <p>
-     * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and
-     * {@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
-     * </p>
-     *
-     */
-    public boolean isMatch(boolean isSystem, boolean isPackageEnabled, boolean isComponentEnabled,
-               boolean isComponentDirectBootAware, String componentName, int flags) {
-        final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
-        if (!isAvailable(flags) && !(isSystem && matchUninstalled)) {
-            return reportIfDebug(false, flags);
-        }
-
-        if (!isEnabled(isPackageEnabled, isComponentEnabled, componentName, flags)) {
-            return reportIfDebug(false, flags);
-        }
-
-        if ((flags & MATCH_SYSTEM_ONLY) != 0) {
-            if (!isSystem) {
-                return reportIfDebug(false, flags);
-            }
-        }
-
-        final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
-                && !isComponentDirectBootAware;
-        final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
-                && isComponentDirectBootAware;
-        return reportIfDebug(matchesUnaware || matchesAware, flags);
-    }
-
-    public boolean reportIfDebug(boolean result, int flags) {
-        if (DEBUG && !result) {
-            Slog.i(LOG_TAG, "No match!; flags: "
-                    + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
-                    + Debug.getCaller());
-        }
-        return result;
-    }
-
-    public boolean isPackageEnabled(@NonNull ParsingPackageRead pkg) {
-        switch (this.enabled) {
-            case COMPONENT_ENABLED_STATE_ENABLED:
-                return true;
-            case COMPONENT_ENABLED_STATE_DISABLED:
-            case COMPONENT_ENABLED_STATE_DISABLED_USER:
-            case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return false;
-            default:
-            case COMPONENT_ENABLED_STATE_DEFAULT:
-                return pkg.isEnabled();
-        }
-    }
-
-    public boolean isEnabled(ComponentInfo componentInfo, int flags) {
-        return isEnabled(componentInfo.applicationInfo.enabled, componentInfo.enabled,
-                componentInfo.name, flags);
-    }
-
-    public boolean isEnabled(boolean isPackageEnabled,
-            ParsedMainComponent parsedComponent, int flags) {
-        return isEnabled(isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(),
-                flags);
-    }
-
-    /**
-     * Test if the given component is considered enabled.
-     */
-    public boolean isEnabled(boolean isPackageEnabled, boolean isComponentEnabled,
-            String componentName, int flags) {
-        if ((flags & MATCH_DISABLED_COMPONENTS) != 0) {
-            return true;
-        }
-
-        // First check if the overall package is disabled; if the package is
-        // enabled then fall through to check specific component
-        switch (this.enabled) {
-            case COMPONENT_ENABLED_STATE_DISABLED:
-            case COMPONENT_ENABLED_STATE_DISABLED_USER:
-                return false;
-            case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) {
-                    return false;
-                }
-                // fallthrough
-            case COMPONENT_ENABLED_STATE_DEFAULT:
-                if (!isPackageEnabled) {
-                    return false;
-                }
-                // fallthrough
-            case COMPONENT_ENABLED_STATE_ENABLED:
-                break;
-        }
-
-        // Check if component has explicit state before falling through to
-        // the manifest default
-        if (ArrayUtils.contains(this.enabledComponents, componentName)) {
-            return true;
-        }
-        if (ArrayUtils.contains(this.disabledComponents, componentName)) {
-            return false;
-        }
-
-        return isComponentEnabled;
-    }
-
-    public OverlayPaths getAllOverlayPaths() {
-        if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
-            return null;
-        }
-        if (cachedOverlayPaths != null) {
-            return cachedOverlayPaths;
-        }
-        final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
-        newPaths.addAll(overlayPaths);
-        if (sharedLibraryOverlayPaths != null) {
-            for (final OverlayPaths libOverlayPaths : sharedLibraryOverlayPaths.values()) {
-                newPaths.addAll(libOverlayPaths);
-            }
-        }
-        cachedOverlayPaths = newPaths.build();
-        return cachedOverlayPaths;
-    }
-
-    @Override
-    final public boolean equals(@Nullable Object obj) {
-        if (!(obj instanceof PackageUserState)) {
-            return false;
-        }
-        final PackageUserState oldState = (PackageUserState) obj;
-        if (ceDataInode != oldState.ceDataInode) {
-            return false;
-        }
-        if (installed != oldState.installed) {
-            return false;
-        }
-        if (stopped != oldState.stopped) {
-            return false;
-        }
-        if (notLaunched != oldState.notLaunched) {
-            return false;
-        }
-        if (hidden != oldState.hidden) {
-            return false;
-        }
-        if (distractionFlags != oldState.distractionFlags) {
-            return false;
-        }
-        if (suspended != oldState.suspended) {
-            return false;
-        }
-        if (suspended) {
-            if (!Objects.equals(suspendParams, oldState.suspendParams)) {
-                return false;
-            }
-        }
-        if (instantApp != oldState.instantApp) {
-            return false;
-        }
-        if (virtualPreload != oldState.virtualPreload) {
-            return false;
-        }
-        if (enabled != oldState.enabled) {
-            return false;
-        }
-        if ((lastDisableAppCaller == null && oldState.lastDisableAppCaller != null)
-                || (lastDisableAppCaller != null
-                        && !lastDisableAppCaller.equals(oldState.lastDisableAppCaller))) {
-            return false;
-        }
-        if (installReason != oldState.installReason) {
-            return false;
-        }
-        if (uninstallReason != oldState.uninstallReason) {
-            return false;
-        }
-        if ((disabledComponents == null && oldState.disabledComponents != null)
-                || (disabledComponents != null && oldState.disabledComponents == null)) {
-            return false;
-        }
-        if (disabledComponents != null) {
-            if (disabledComponents.size() != oldState.disabledComponents.size()) {
-                return false;
-            }
-            for (int i = disabledComponents.size() - 1; i >=0; --i) {
-                if (!oldState.disabledComponents.contains(disabledComponents.valueAt(i))) {
-                    return false;
-                }
-            }
-        }
-        if ((enabledComponents == null && oldState.enabledComponents != null)
-                || (enabledComponents != null && oldState.enabledComponents == null)) {
-            return false;
-        }
-        if (enabledComponents != null) {
-            if (enabledComponents.size() != oldState.enabledComponents.size()) {
-                return false;
-            }
-            for (int i = enabledComponents.size() - 1; i >=0; --i) {
-                if (!oldState.enabledComponents.contains(enabledComponents.valueAt(i))) {
-                    return false;
-                }
-            }
-        }
-        if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
-                || (harmfulAppWarning != null
-                && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
-            return false;
-        }
-
-        if (!Objects.equals(splashScreenTheme, oldState.splashScreenTheme)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int hashCode = Long.hashCode(ceDataInode);
-        hashCode = 31 * hashCode + Boolean.hashCode(installed);
-        hashCode = 31 * hashCode + Boolean.hashCode(stopped);
-        hashCode = 31 * hashCode + Boolean.hashCode(notLaunched);
-        hashCode = 31 * hashCode + Boolean.hashCode(hidden);
-        hashCode = 31 * hashCode + distractionFlags;
-        hashCode = 31 * hashCode + Boolean.hashCode(suspended);
-        hashCode = 31 * hashCode + Objects.hashCode(suspendParams);
-        hashCode = 31 * hashCode + Boolean.hashCode(instantApp);
-        hashCode = 31 * hashCode + Boolean.hashCode(virtualPreload);
-        hashCode = 31 * hashCode + enabled;
-        hashCode = 31 * hashCode + Objects.hashCode(lastDisableAppCaller);
-        hashCode = 31 * hashCode + installReason;
-        hashCode = 31 * hashCode + uninstallReason;
-        hashCode = 31 * hashCode + Objects.hashCode(disabledComponents);
-        hashCode = 31 * hashCode + Objects.hashCode(enabledComponents);
-        hashCode = 31 * hashCode + Objects.hashCode(harmfulAppWarning);
-        hashCode = 31 * hashCode + Objects.hashCode(splashScreenTheme);
-        return hashCode;
-    }
-
-    @Override
-    public long getCeDataInode() {
-        return ceDataInode;
-    }
-
-    @NonNull
-    @Override
-    public Set<String> getDisabledComponents() {
-        return disabledComponents;
-    }
-
-    @PackageManager.DistractionRestriction
-    @Override
-    public int getDistractionFlags() {
-        return distractionFlags;
-    }
-
-    @NonNull
-    @Override
-    public Set<String> getEnabledComponents() {
-        return enabledComponents;
-    }
-
-    @Override
-    public int getEnabledState() {
-        return enabled;
-    }
-
-    @Nullable
-    @Override
-    public String getHarmfulAppWarning() {
-        return harmfulAppWarning;
-    }
-
-    @Override
-    public int getInstallReason() {
-        return installReason;
-    }
-
-    @Nullable
-    @Override
-    public String getLastDisableAppCaller() {
-        return lastDisableAppCaller;
-    }
-
-    @Nullable
-    @Override
-    public OverlayPaths getOverlayPaths() {
-        return overlayPaths;
-    }
-
-    @Nullable
-    @Override
-    public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
-        return sharedLibraryOverlayPaths;
-    }
-
-    @Override
-    public int getUninstallReason() {
-        return uninstallReason;
-    }
-
-    @Override
-    public boolean isHidden() {
-        return hidden;
-    }
-
-    @Override
-    public boolean isInstalled() {
-        return installed;
-    }
-
-    @Override
-    public boolean isInstantApp() {
-        return instantApp;
-    }
-
-    @Override
-    public boolean isNotLaunched() {
-        return notLaunched;
-    }
-
-    @Override
-    public boolean isStopped() {
-        return stopped;
-    }
-
-    @Override
-    public boolean isSuspended() {
-        return suspended;
-    }
-
-    @Override
-    public boolean isVirtualPreload() {
-        return virtualPreload;
-    }
-
-    /**
-     * Container to describe suspension parameters.
-     */
-    public static final class SuspendParams {
-        private static final String TAG_DIALOG_INFO = "dialog-info";
-        private static final String TAG_APP_EXTRAS = "app-extras";
-        private static final String TAG_LAUNCHER_EXTRAS = "launcher-extras";
-
-        public SuspendDialogInfo dialogInfo;
-        public PersistableBundle appExtras;
-        public PersistableBundle launcherExtras;
-
-        private SuspendParams() {
-        }
-
-        /**
-         * Returns a {@link SuspendParams} object with the given fields. Returns {@code null} if all
-         * the fields are {@code null}.
-         *
-         * @param dialogInfo
-         * @param appExtras
-         * @param launcherExtras
-         * @return A {@link SuspendParams} object or {@code null}.
-         */
-        public static SuspendParams getInstanceOrNull(SuspendDialogInfo dialogInfo,
-                PersistableBundle appExtras, PersistableBundle launcherExtras) {
-            if (dialogInfo == null && appExtras == null && launcherExtras == null) {
-                return null;
-            }
-            final SuspendParams instance = new SuspendParams();
-            instance.dialogInfo = dialogInfo;
-            instance.appExtras = appExtras;
-            instance.launcherExtras = launcherExtras;
-            return instance;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (!(obj instanceof SuspendParams)) {
-                return false;
-            }
-            final SuspendParams other = (SuspendParams) obj;
-            if (!Objects.equals(dialogInfo, other.dialogInfo)) {
-                return false;
-            }
-            if (!BaseBundle.kindofEquals(appExtras, other.appExtras)) {
-                return false;
-            }
-            if (!BaseBundle.kindofEquals(launcherExtras, other.launcherExtras)) {
-                return false;
-            }
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int hashCode = Objects.hashCode(dialogInfo);
-            hashCode = 31 * hashCode + ((appExtras != null) ? appExtras.size() : 0);
-            hashCode = 31 * hashCode + ((launcherExtras != null) ? launcherExtras.size() : 0);
-            return hashCode;
-        }
-
-        /**
-         * Serializes this object into an xml format
-         * @param out the {@link XmlSerializer} object
-         * @throws IOException
-         */
-        public void saveToXml(TypedXmlSerializer out) throws IOException {
-            if (dialogInfo != null) {
-                out.startTag(null, TAG_DIALOG_INFO);
-                dialogInfo.saveToXml(out);
-                out.endTag(null, TAG_DIALOG_INFO);
-            }
-            if (appExtras != null) {
-                out.startTag(null, TAG_APP_EXTRAS);
-                try {
-                    appExtras.saveToXml(out);
-                } catch (XmlPullParserException e) {
-                    Slog.e(LOG_TAG, "Exception while trying to write appExtras."
-                            + " Will be lost on reboot", e);
-                }
-                out.endTag(null, TAG_APP_EXTRAS);
-            }
-            if (launcherExtras != null) {
-                out.startTag(null, TAG_LAUNCHER_EXTRAS);
-                try {
-                    launcherExtras.saveToXml(out);
-                } catch (XmlPullParserException e) {
-                    Slog.e(LOG_TAG, "Exception while trying to write launcherExtras."
-                            + " Will be lost on reboot", e);
-                }
-                out.endTag(null, TAG_LAUNCHER_EXTRAS);
-            }
-        }
-
-        /**
-         * Parses this object from the xml format. Returns {@code null} if no object related
-         * information could be read.
-         * @param in the reader
-         * @return
-         */
-        public static SuspendParams restoreFromXml(TypedXmlPullParser in) throws IOException {
-            SuspendDialogInfo readDialogInfo = null;
-            PersistableBundle readAppExtras = null;
-            PersistableBundle readLauncherExtras = null;
-
-            final int currentDepth = in.getDepth();
-            int type;
-            try {
-                while ((type = in.next()) != XmlPullParser.END_DOCUMENT
-                        && (type != XmlPullParser.END_TAG
-                        || in.getDepth() > currentDepth)) {
-                    if (type == XmlPullParser.END_TAG
-                            || type == XmlPullParser.TEXT) {
-                        continue;
-                    }
-                    switch (in.getName()) {
-                        case TAG_DIALOG_INFO:
-                            readDialogInfo = SuspendDialogInfo.restoreFromXml(in);
-                            break;
-                        case TAG_APP_EXTRAS:
-                            readAppExtras = PersistableBundle.restoreFromXml(in);
-                            break;
-                        case TAG_LAUNCHER_EXTRAS:
-                            readLauncherExtras = PersistableBundle.restoreFromXml(in);
-                            break;
-                        default:
-                            Slog.w(LOG_TAG, "Unknown tag " + in.getName()
-                                    + " in SuspendParams. Ignoring");
-                            break;
-                    }
-                }
-            } catch (XmlPullParserException e) {
-                Slog.e(LOG_TAG, "Exception while trying to parse SuspendParams,"
-                        + " some fields may default", e);
-            }
-            return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras);
-        }
-    }
-}
diff --git a/core/java/android/content/pm/SELinuxUtil.java b/core/java/android/content/pm/SELinuxUtil.java
index d8d52b7..90dcd85 100644
--- a/core/java/android/content/pm/SELinuxUtil.java
+++ b/core/java/android/content/pm/SELinuxUtil.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import android.content.pm.pkg.PackageUserState;
+
 /**
  * Utility methods that need to be used in application space.
  * @hide
@@ -30,7 +32,7 @@
 
     /** @hide */
     public static String getSeinfoUser(PackageUserState userState) {
-        if (userState.instantApp) {
+        if (userState.isInstantApp()) {
            return INSTANT_APP_STR + COMPLETE_STR;
         }
         return COMPLETE_STR;
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index 747015d..cb26165 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -32,7 +32,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
@@ -53,6 +52,8 @@
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
 import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.os.Environment;
 import android.os.UserHandle;
 
@@ -65,7 +66,9 @@
 import java.util.List;
 import java.util.Set;
 
-/** @hide **/
+/**
+ * @hide
+ **/
 public class PackageInfoWithoutStateUtils {
 
     public static final String SYSTEM_DATA_PATH =
@@ -82,7 +85,7 @@
     @Nullable
     public static PackageInfo generate(ParsingPackageRead pkg, ApexInfo apexInfo, int flags) {
         return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                new PackageUserState(), UserHandle.getCallingUserId(), apexInfo);
+                PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo);
     }
 
     @Nullable
@@ -199,9 +202,9 @@
     }
 
     /**
-     * This bypasses critical checks that are necessary for usage with data passed outside of
-     * system server.
-     *
+     * This bypasses critical checks that are necessary for usage with data passed outside of system
+     * server.
+     * <p>
      * Prefer {@link #generateWithoutComponents(ParsingPackageRead, int[], int, long, long, Set,
      * PackageUserState, int, ApexInfo, ApplicationInfo)}.
      */
@@ -376,16 +379,16 @@
     }
 
     /**
-     * This bypasses critical checks that are necessary for usage with data passed outside of
-     * system server.
-     *
+     * This bypasses critical checks that are necessary for usage with data passed outside of system
+     * server.
+     * <p>
      * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}.
      *
      * @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user
      *                         specific fields. This can be skipped when building from a system
      *                         server package, as there are cached strings which can be used rather
-     *                         than querying and concatenating the comparatively expensive
-     *                         {@link Environment#getDataDirectory(String)}}.
+     *                         than querying and concatenating the comparatively expensive {@link
+     *                         Environment#getDataDirectory(String)}}.
      */
     @NonNull
     public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
@@ -418,22 +421,24 @@
             ai.disableCompatibilityMode();
         }
 
-        ai.flags |= flag(state.stopped, ApplicationInfo.FLAG_STOPPED)
-                | flag(state.installed, ApplicationInfo.FLAG_INSTALLED)
-                | flag(state.suspended, ApplicationInfo.FLAG_SUSPENDED);
-        ai.privateFlags |= flag(state.instantApp, ApplicationInfo.PRIVATE_FLAG_INSTANT)
-                | flag(state.virtualPreload, ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
-                | flag(state.hidden, ApplicationInfo.PRIVATE_FLAG_HIDDEN);
+        ai.flags |= flag(state.isStopped(), ApplicationInfo.FLAG_STOPPED)
+                | flag(state.isInstalled(), ApplicationInfo.FLAG_INSTALLED)
+                | flag(state.isSuspended(), ApplicationInfo.FLAG_SUSPENDED);
+        ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
+                | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
+                | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
 
-        if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+        if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
             ai.enabled = true;
-        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+        } else if (state.getEnabledState()
+                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
             ai.enabled = (flags & PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
-        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+        } else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                || state.getEnabledState()
+                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
             ai.enabled = false;
         }
-        ai.enabledSetting = state.enabled;
+        ai.enabledSetting = state.getEnabledState();
         if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
@@ -496,9 +501,9 @@
     }
 
     /**
-     * This bypasses critical checks that are necessary for usage with data passed outside of
-     * system server.
-     *
+     * This bypasses critical checks that are necessary for usage with data passed outside of system
+     * server.
+     * <p>
      * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, int,
      * PackageUserState, ApplicationInfo, int)}.
      */
@@ -568,9 +573,9 @@
     }
 
     /**
-     * This bypasses critical checks that are necessary for usage with data passed outside of
-     * system server.
-     *
+     * This bypasses critical checks that are necessary for usage with data passed outside of system
+     * server.
+     * <p>
      * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, int, PackageUserState,
      * ApplicationInfo, int)}.
      */
@@ -618,9 +623,9 @@
     }
 
     /**
-     * This bypasses critical checks that are necessary for usage with data passed outside of
-     * system server.
-     *
+     * This bypasses critical checks that are necessary for usage with data passed outside of system
+     * server.
+     * <p>
      * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, int,
      * PackageUserState, ApplicationInfo, int)}.
      */
@@ -752,14 +757,14 @@
             @Nullable ApplicationInfo appInfo) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
-                && !state.installed
+                && !state.isInstalled()
                 && appInfo != null && appInfo.hiddenUntilInstalled) {
             return false;
         }
 
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
-        return state.isAvailable(flags)
+        return PackageUserStateUtils.isAvailable(state, flags)
                 || (appInfo != null && appInfo.isSystemApp()
                 && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
                 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
@@ -796,7 +801,9 @@
         }
     }
 
-    /** @see ApplicationInfo#flags */
+    /**
+     * @see ApplicationInfo#flags
+     */
     public static int appInfoFlags(ParsingPackageRead pkg) {
         // @formatter:off
         return flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
@@ -878,7 +885,7 @@
     private static boolean checkUseInstalled(ParsingPackageRead pkg, PackageUserState state,
             @PackageManager.PackageInfoFlags int flags) {
         // If available for the target user
-        return state.isAvailable(flags);
+        return PackageUserStateUtils.isAvailable(state, flags);
     }
 
     @NonNull
diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
index e5d030c..ae6181d 100644
--- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
@@ -22,12 +22,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
-import android.content.pm.PackageUserState;
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -38,7 +39,9 @@
 
 import java.io.IOException;
 
-/** @hide */
+/**
+ * @hide
+ */
 public class ComponentParseUtils {
 
     public static boolean isImplicitlyExposedIntent(ParsedIntentInfo intentInfo) {
@@ -157,7 +160,7 @@
 
     /**
      * This is not state aware. Avoid and access through PackageInfoUtils in the system server.
-     *
+     * <p>
      * This is a method of the utility class to discourage use.
      */
     public static int getIcon(ParsedComponent component) {
@@ -166,13 +169,13 @@
 
     public static boolean isMatch(PackageUserState state, boolean isSystem,
             boolean isPackageEnabled, ParsedMainComponent component, int flags) {
-        return state.isMatch(isSystem, isPackageEnabled, component.isEnabled(),
-                component.isDirectBootAware(), component.getName(), flags);
+        return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled,
+                component.isEnabled(), component.isDirectBootAware(), component.getName(), flags);
     }
 
     public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled,
             ParsedMainComponent parsedComponent, int flags) {
-        return state.isEnabled(isPackageEnabled, parsedComponent.isEnabled(),
+        return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(),
                 parsedComponent.getName(), flags);
     }
 }
diff --git a/core/java/android/content/pm/pkg/PackageUserState.java b/core/java/android/content/pm/pkg/PackageUserState.java
index e5853f0..e0d1eea 100644
--- a/core/java/android/content/pm/pkg/PackageUserState.java
+++ b/core/java/android/content/pm/pkg/PackageUserState.java
@@ -16,19 +16,30 @@
 
 package android.content.pm.pkg;
 
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.pm.PackageManager;
+import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.os.BaseBundle;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
 
-import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
- * The API surface for a {@link android.content.pm.PackageUserState}. Methods are expected to return
+ * The API surface for a {@link PackageUserStateImpl}. Methods are expected to return
  * immutable objects. This may mean copying data on each invocation until related classes are
  * refactored to be immutable.
  * <p>
@@ -40,6 +51,15 @@
 // TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageUserState {
+
+    PackageUserState DEFAULT = new PackageUserStateDefault();
+
+    /**
+     * {@link #getOverlayPaths()} but also include shared library overlay paths.
+     */
+    @Nullable
+    OverlayPaths getAllOverlayPaths();
+
     /**
      * Credential encrypted /data partition inode.
      */
@@ -74,6 +94,10 @@
     @PackageManager.UninstallReason
     int getUninstallReason();
 
+    boolean isComponentEnabled(@NonNull String componentName);
+
+    boolean isComponentDisabled(@NonNull String componentName);
+
     boolean isHidden();
 
     boolean isInstalled();
@@ -87,4 +111,152 @@
     boolean isSuspended();
 
     boolean isVirtualPreload();
+
+    @Nullable
+    String getSplashScreenTheme();
+
+    /**
+     * Container to describe suspension parameters.
+     */
+    final class SuspendParams {
+
+        private static final String LOG_TAG = "PackageUserState";
+        private static final String TAG_DIALOG_INFO = "dialog-info";
+        private static final String TAG_APP_EXTRAS = "app-extras";
+        private static final String TAG_LAUNCHER_EXTRAS = "launcher-extras";
+
+        public SuspendDialogInfo dialogInfo;
+        public PersistableBundle appExtras;
+        public PersistableBundle launcherExtras;
+
+        private SuspendParams() {
+        }
+
+        /**
+         * Returns a {@link SuspendParams} object with the given fields. Returns {@code null} if all
+         * the fields are {@code null}.
+         *
+         * @param dialogInfo
+         * @param appExtras
+         * @param launcherExtras
+         * @return A {@link SuspendParams} object or {@code null}.
+         */
+        public static SuspendParams getInstanceOrNull(SuspendDialogInfo dialogInfo,
+                PersistableBundle appExtras, PersistableBundle launcherExtras) {
+            if (dialogInfo == null && appExtras == null && launcherExtras == null) {
+                return null;
+            }
+            final SuspendParams instance = new SuspendParams();
+            instance.dialogInfo = dialogInfo;
+            instance.appExtras = appExtras;
+            instance.launcherExtras = launcherExtras;
+            return instance;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof SuspendParams)) {
+                return false;
+            }
+            final SuspendParams other = (SuspendParams) obj;
+            if (!Objects.equals(dialogInfo, other.dialogInfo)) {
+                return false;
+            }
+            if (!BaseBundle.kindofEquals(appExtras, other.appExtras)) {
+                return false;
+            }
+            if (!BaseBundle.kindofEquals(launcherExtras, other.launcherExtras)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int hashCode = Objects.hashCode(dialogInfo);
+            hashCode = 31 * hashCode + ((appExtras != null) ? appExtras.size() : 0);
+            hashCode = 31 * hashCode + ((launcherExtras != null) ? launcherExtras.size() : 0);
+            return hashCode;
+        }
+
+        /**
+         * Serializes this object into an xml format
+         * @param out the {@link XmlSerializer} object
+         * @throws IOException
+         */
+        public void saveToXml(TypedXmlSerializer out) throws IOException {
+            if (dialogInfo != null) {
+                out.startTag(null, TAG_DIALOG_INFO);
+                dialogInfo.saveToXml(out);
+                out.endTag(null, TAG_DIALOG_INFO);
+            }
+            if (appExtras != null) {
+                out.startTag(null, TAG_APP_EXTRAS);
+                try {
+                    appExtras.saveToXml(out);
+                } catch (XmlPullParserException e) {
+                    Slog.e(LOG_TAG, "Exception while trying to write appExtras."
+                            + " Will be lost on reboot", e);
+                }
+                out.endTag(null, TAG_APP_EXTRAS);
+            }
+            if (launcherExtras != null) {
+                out.startTag(null, TAG_LAUNCHER_EXTRAS);
+                try {
+                    launcherExtras.saveToXml(out);
+                } catch (XmlPullParserException e) {
+                    Slog.e(LOG_TAG, "Exception while trying to write launcherExtras."
+                            + " Will be lost on reboot", e);
+                }
+                out.endTag(null, TAG_LAUNCHER_EXTRAS);
+            }
+        }
+
+        /**
+         * Parses this object from the xml format. Returns {@code null} if no object related
+         * information could be read.
+         * @param in the reader
+         * @return
+         */
+        public static SuspendParams restoreFromXml(TypedXmlPullParser in) throws IOException {
+            SuspendDialogInfo readDialogInfo = null;
+            PersistableBundle readAppExtras = null;
+            PersistableBundle readLauncherExtras = null;
+
+            final int currentDepth = in.getDepth();
+            int type;
+            try {
+                while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG
+                        || in.getDepth() > currentDepth)) {
+                    if (type == XmlPullParser.END_TAG
+                            || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    switch (in.getName()) {
+                        case TAG_DIALOG_INFO:
+                            readDialogInfo = SuspendDialogInfo.restoreFromXml(in);
+                            break;
+                        case TAG_APP_EXTRAS:
+                            readAppExtras = PersistableBundle.restoreFromXml(in);
+                            break;
+                        case TAG_LAUNCHER_EXTRAS:
+                            readLauncherExtras = PersistableBundle.restoreFromXml(in);
+                            break;
+                        default:
+                            Slog.w(LOG_TAG, "Unknown tag " + in.getName()
+                                    + " in SuspendParams. Ignoring");
+                            break;
+                    }
+                }
+            } catch (XmlPullParserException e) {
+                Slog.e(LOG_TAG, "Exception while trying to parse SuspendParams,"
+                        + " some fields may default", e);
+            }
+            return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras);
+        }
+    }
 }
diff --git a/core/java/android/content/pm/pkg/PackageUserStateDefault.java b/core/java/android/content/pm/pkg/PackageUserStateDefault.java
new file mode 100644
index 0000000..6bee8c8
--- /dev/null
+++ b/core/java/android/content/pm/pkg/PackageUserStateDefault.java
@@ -0,0 +1,146 @@
+/*
+ * 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.content.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+import android.content.pm.overlay.OverlayPaths;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+class PackageUserStateDefault implements PackageUserState {
+
+    @Override
+    public int getEnabledState() {
+        return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+    }
+
+    @Override
+    public int getInstallReason() {
+        return PackageManager.INSTALL_REASON_UNKNOWN;
+    }
+
+    @NonNull
+    @Override
+    public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public int getUninstallReason() {
+        return PackageManager.UNINSTALL_REASON_UNKNOWN;
+    }
+
+    @Override
+    public boolean isInstalled() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getDisabledComponents() {
+        return Collections.emptySet();
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getEnabledComponents() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public long getCeDataInode() {
+        return 0;
+    }
+
+    @Override
+    public int getDistractionFlags() {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public String getHarmfulAppWarning() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getLastDisableAppCaller() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public OverlayPaths getOverlayPaths() {
+        return null;
+    }
+
+    @Override
+    public boolean isHidden() {
+        return false;
+    }
+
+    @Override
+    public boolean isInstantApp() {
+        return false;
+    }
+
+    @Override
+    public boolean isNotLaunched() {
+        return false;
+    }
+
+    @Override
+    public boolean isStopped() {
+        return false;
+    }
+
+    @Override
+    public boolean isSuspended() {
+        return false;
+    }
+
+    @Override
+    public boolean isVirtualPreload() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public String getSplashScreenTheme() {
+        return null;
+    }
+
+    @Override
+    public boolean isComponentEnabled(String componentName) {
+        return false;
+    }
+
+    @Override
+    public boolean isComponentDisabled(String componentName) {
+        return false;
+    }
+
+    @Override
+    public OverlayPaths getAllOverlayPaths() {
+        return null;
+    }
+}
diff --git a/core/java/android/content/pm/pkg/PackageUserStateImpl.java b/core/java/android/content/pm/pkg/PackageUserStateImpl.java
new file mode 100644
index 0000000..4d1b236
--- /dev/null
+++ b/core/java/android/content/pm/pkg/PackageUserStateImpl.java
@@ -0,0 +1,429 @@
+/*
+ * 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.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+import android.content.pm.overlay.OverlayPaths;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+
+import java.util.Map;
+import java.util.Set;
+
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
+@DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
+        "mSharedLibraryOverlayPaths", "setOverlayPaths"})
+public class PackageUserStateImpl implements PackageUserState {
+
+    @Nullable
+    protected ArraySet<String> mDisabledComponents;
+    @Nullable
+    protected ArraySet<String> mEnabledComponents;
+
+    private long mCeDataInode;
+    private boolean mInstalled = true;
+    private boolean mStopped;
+    private boolean mNotLaunched;
+    private boolean mHidden; // Is the app restricted by owner / admin
+    private int mDistractionFlags;
+    private boolean mSuspended;
+    private boolean mInstantApp;
+    private boolean mVirtualPreload;
+    private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+    @PackageManager.InstallReason
+    private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
+    @PackageManager.UninstallReason
+    private int mUninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
+    @Nullable
+    private String mHarmfulAppWarning;
+    @Nullable
+    private String mLastDisableAppCaller;
+
+    @Nullable
+    protected OverlayPaths mOverlayPaths;
+
+    // Lib name to overlay paths
+    @Nullable
+    protected ArrayMap<String, OverlayPaths> mSharedLibraryOverlayPaths;
+
+    @Nullable
+    private String mSplashScreenTheme;
+
+    public PackageUserStateImpl() {
+    }
+
+    @VisibleForTesting
+    public PackageUserStateImpl(PackageUserStateImpl other) {
+        mDisabledComponents = ArrayUtils.cloneOrNull(other.mDisabledComponents);
+        mEnabledComponents = ArrayUtils.cloneOrNull(other.mEnabledComponents);
+        mOverlayPaths = other.mOverlayPaths;
+        if (other.mSharedLibraryOverlayPaths != null) {
+            mSharedLibraryOverlayPaths = new ArrayMap<>(other.mSharedLibraryOverlayPaths);
+        }
+        this.mDisabledComponents = other.mDisabledComponents;
+        this.mEnabledComponents = other.mEnabledComponents;
+        this.mCeDataInode = other.mCeDataInode;
+        this.mInstalled = other.mInstalled;
+        this.mStopped = other.mStopped;
+        this.mNotLaunched = other.mNotLaunched;
+        this.mHidden = other.mHidden;
+        this.mDistractionFlags = other.mDistractionFlags;
+        this.mSuspended = other.mSuspended;
+        this.mInstantApp = other.mInstantApp;
+        this.mVirtualPreload = other.mVirtualPreload;
+        this.mEnabledState = other.mEnabledState;
+        this.mInstallReason = other.mInstallReason;
+        this.mUninstallReason = other.mUninstallReason;
+        this.mHarmfulAppWarning = other.mHarmfulAppWarning;
+        this.mLastDisableAppCaller = other.mLastDisableAppCaller;
+        this.mOverlayPaths = other.mOverlayPaths;
+        this.mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths;
+        this.mSplashScreenTheme = other.mSplashScreenTheme;
+    }
+
+    @Override
+    public boolean isComponentEnabled(String componentName) {
+        // TODO: Not locked
+        return ArrayUtils.contains(mEnabledComponents, componentName);
+    }
+
+    @Override
+    public boolean isComponentDisabled(String componentName) {
+        // TODO: Not locked
+        return ArrayUtils.contains(mDisabledComponents, componentName);
+    }
+
+    @Override
+    public OverlayPaths getAllOverlayPaths() {
+        if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) {
+            return null;
+        }
+        final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
+        newPaths.addAll(mOverlayPaths);
+        if (mSharedLibraryOverlayPaths != null) {
+            for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) {
+                newPaths.addAll(libOverlayPaths);
+            }
+        }
+        return newPaths.build();
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/pkg/PackageUserStateImpl.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public @Nullable ArraySet<String> getDisabledComponents() {
+        return mDisabledComponents;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable ArraySet<String> getEnabledComponents() {
+        return mEnabledComponents;
+    }
+
+    @DataClass.Generated.Member
+    public long getCeDataInode() {
+        return mCeDataInode;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isInstalled() {
+        return mInstalled;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isStopped() {
+        return mStopped;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isNotLaunched() {
+        return mNotLaunched;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isHidden() {
+        return mHidden;
+    }
+
+    @DataClass.Generated.Member
+    public int getDistractionFlags() {
+        return mDistractionFlags;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isSuspended() {
+        return mSuspended;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isInstantApp() {
+        return mInstantApp;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isVirtualPreload() {
+        return mVirtualPreload;
+    }
+
+    @DataClass.Generated.Member
+    public int getEnabledState() {
+        return mEnabledState;
+    }
+
+    @DataClass.Generated.Member
+    public @PackageManager.InstallReason int getInstallReason() {
+        return mInstallReason;
+    }
+
+    @DataClass.Generated.Member
+    public @PackageManager.UninstallReason int getUninstallReason() {
+        return mUninstallReason;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getHarmfulAppWarning() {
+        return mHarmfulAppWarning;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getLastDisableAppCaller() {
+        return mLastDisableAppCaller;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable OverlayPaths getOverlayPaths() {
+        return mOverlayPaths;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable ArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() {
+        return mSharedLibraryOverlayPaths;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getSplashScreenTheme() {
+        return mSplashScreenTheme;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setDisabledComponents(@NonNull ArraySet<String> value) {
+        mDisabledComponents = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setEnabledComponents(@NonNull ArraySet<String> value) {
+        mEnabledComponents = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setCeDataInode( long value) {
+        mCeDataInode = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setInstalled( boolean value) {
+        mInstalled = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setStopped( boolean value) {
+        mStopped = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setNotLaunched( boolean value) {
+        mNotLaunched = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setHidden( boolean value) {
+        mHidden = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setDistractionFlags( int value) {
+        mDistractionFlags = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setSuspended( boolean value) {
+        mSuspended = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setInstantApp( boolean value) {
+        mInstantApp = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setVirtualPreload( boolean value) {
+        mVirtualPreload = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setEnabledState( int value) {
+        mEnabledState = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setInstallReason(@PackageManager.InstallReason int value) {
+        mInstallReason = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.InstallReason.class, null, mInstallReason);
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setUninstallReason(@PackageManager.UninstallReason int value) {
+        mUninstallReason = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.UninstallReason.class, null, mUninstallReason);
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setHarmfulAppWarning(@NonNull String value) {
+        mHarmfulAppWarning = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setLastDisableAppCaller(@NonNull String value) {
+        mLastDisableAppCaller = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setSharedLibraryOverlayPaths(@NonNull ArrayMap<String,OverlayPaths> value) {
+        mSharedLibraryOverlayPaths = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setSplashScreenTheme(@NonNull String value) {
+        mSplashScreenTheme = value;
+        return this;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        PackageUserStateImpl that = (PackageUserStateImpl) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mDisabledComponents, that.mDisabledComponents)
+                && java.util.Objects.equals(mEnabledComponents, that.mEnabledComponents)
+                && mCeDataInode == that.mCeDataInode
+                && mInstalled == that.mInstalled
+                && mStopped == that.mStopped
+                && mNotLaunched == that.mNotLaunched
+                && mHidden == that.mHidden
+                && mDistractionFlags == that.mDistractionFlags
+                && mSuspended == that.mSuspended
+                && mInstantApp == that.mInstantApp
+                && mVirtualPreload == that.mVirtualPreload
+                && mEnabledState == that.mEnabledState
+                && mInstallReason == that.mInstallReason
+                && mUninstallReason == that.mUninstallReason
+                && java.util.Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
+                && java.util.Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
+                && java.util.Objects.equals(mOverlayPaths, that.mOverlayPaths)
+                && java.util.Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
+                && java.util.Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mDisabledComponents);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledComponents);
+        _hash = 31 * _hash + Long.hashCode(mCeDataInode);
+        _hash = 31 * _hash + Boolean.hashCode(mInstalled);
+        _hash = 31 * _hash + Boolean.hashCode(mStopped);
+        _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
+        _hash = 31 * _hash + Boolean.hashCode(mHidden);
+        _hash = 31 * _hash + mDistractionFlags;
+        _hash = 31 * _hash + Boolean.hashCode(mSuspended);
+        _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
+        _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
+        _hash = 31 * _hash + mEnabledState;
+        _hash = 31 * _hash + mInstallReason;
+        _hash = 31 * _hash + mUninstallReason;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHarmfulAppWarning);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mLastDisableAppCaller);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mOverlayPaths);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSharedLibraryOverlayPaths);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSplashScreenTheme);
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1633391914126L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/content/pm/pkg/PackageUserStateImpl.java",
+            inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mSuspended\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass PackageUserStateImpl extends java.lang.Object implements [android.content.pm.pkg.PackageUserState]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/pkg/PackageUserStateInternal.java b/core/java/android/content/pm/pkg/PackageUserStateInternal.java
new file mode 100644
index 0000000..b3f849b
--- /dev/null
+++ b/core/java/android/content/pm/pkg/PackageUserStateInternal.java
@@ -0,0 +1,42 @@
+/*
+ * 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.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+
+/** @hide */
+public interface PackageUserStateInternal extends PackageUserState {
+
+    PackageUserStateInternal DEFAULT = new PackageUserStateInternalDefault();
+
+    @Nullable
+    ArrayMap<String, SuspendParams> getSuspendParams();
+
+    @Nullable
+    ArraySet<String> getDisabledComponentsNoCopy();
+
+    @Nullable
+    ArraySet<String> getEnabledComponentsNoCopy();
+
+    @Nullable
+    Pair<String, Integer> getOverrideLabelIconForComponent(@NonNull ComponentName componentName);
+}
diff --git a/core/java/android/content/pm/pkg/PackageUserStateInternalDefault.java b/core/java/android/content/pm/pkg/PackageUserStateInternalDefault.java
new file mode 100644
index 0000000..b72ed13
--- /dev/null
+++ b/core/java/android/content/pm/pkg/PackageUserStateInternalDefault.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+
+class PackageUserStateInternalDefault extends PackageUserStateDefault implements
+        PackageUserStateInternal {
+
+    @Nullable
+    @Override
+    public ArrayMap<String, SuspendParams> getSuspendParams() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ArraySet<String> getDisabledComponentsNoCopy() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ArraySet<String> getEnabledComponentsNoCopy() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Pair<String, Integer> getOverrideLabelIconForComponent(
+            @NonNull ComponentName componentName) {
+        return null;
+    }
+}
diff --git a/core/java/android/content/pm/pkg/PackageUserStateUtils.java b/core/java/android/content/pm/pkg/PackageUserStateUtils.java
new file mode 100644
index 0000000..e46a10c
--- /dev/null
+++ b/core/java/android/content/pm/pkg/PackageUserStateUtils.java
@@ -0,0 +1,170 @@
+/*
+ * 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.pkg;
+
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+
+import android.annotation.NonNull;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.os.Debug;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+/** @hide */
+public class PackageUserStateUtils {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PackageUserStateUtils";
+
+    public static boolean isMatch(@NonNull PackageUserState state, ComponentInfo componentInfo,
+            int flags) {
+        return isMatch(state, componentInfo.applicationInfo.isSystemApp(),
+                componentInfo.applicationInfo.enabled, componentInfo.enabled,
+                componentInfo.directBootAware, componentInfo.name, flags);
+    }
+
+    public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem,
+            boolean isPackageEnabled, ParsedMainComponent component, int flags) {
+        return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(),
+                component.isDirectBootAware(), component.getName(), flags);
+    }
+
+    /**
+     * Test if the given component is considered installed, enabled and a match for the given
+     * flags.
+     *
+     * <p>
+     * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
+     * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+     * </p>
+     */
+    public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem,
+            boolean isPackageEnabled, boolean isComponentEnabled,
+            boolean isComponentDirectBootAware, String componentName, int flags) {
+        final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
+        if (!isAvailable(state, flags) && !(isSystem && matchUninstalled)) {
+            return reportIfDebug(false, flags);
+        }
+
+        if (!isEnabled(state, isPackageEnabled, isComponentEnabled, componentName, flags)) {
+            return reportIfDebug(false, flags);
+        }
+
+        if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+            if (!isSystem) {
+                return reportIfDebug(false, flags);
+            }
+        }
+
+        final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0)
+                && !isComponentDirectBootAware;
+        final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0)
+                && isComponentDirectBootAware;
+        return reportIfDebug(matchesUnaware || matchesAware, flags);
+    }
+
+    public static boolean isAvailable(@NonNull PackageUserState state, int flags) {
+        // True if it is installed for this user and it is not hidden. If it is hidden,
+        // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES
+        final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
+        final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
+        return matchAnyUser
+                || (state.isInstalled()
+                && (!state.isHidden() || matchUninstalled));
+    }
+
+    public static boolean reportIfDebug(boolean result, int flags) {
+        if (DEBUG && !result) {
+            Slog.i(TAG, "No match!; flags: "
+                    + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+                    + Debug.getCaller());
+        }
+        return result;
+    }
+
+    public static boolean isEnabled(@NonNull PackageUserState state, ComponentInfo componentInfo,
+            int flags) {
+        return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled,
+                componentInfo.name, flags);
+    }
+
+    public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled,
+            ParsedMainComponent parsedComponent, int flags) {
+        return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(),
+                parsedComponent.getName(), flags);
+    }
+
+    /**
+     * Test if the given component is considered enabled.
+     */
+    public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled,
+            boolean isComponentEnabled, String componentName, int flags) {
+        if ((flags & MATCH_DISABLED_COMPONENTS) != 0) {
+            return true;
+        }
+
+        // First check if the overall package is disabled; if the package is
+        // enabled then fall through to check specific component
+        switch (state.getEnabledState()) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+                return false;
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) {
+                    return false;
+                }
+                // fallthrough
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                if (!isPackageEnabled) {
+                    return false;
+                }
+                // fallthrough
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                break;
+        }
+
+        // Check if component has explicit state before falling through to
+        // the manifest default
+        if (state.isComponentEnabled(componentName)) {
+            return true;
+        } else if (state.isComponentDisabled(componentName)) {
+            return false;
+        }
+
+        return isComponentEnabled;
+    }
+
+    public static boolean isPackageEnabled(@NonNull PackageUserState state,
+            @NonNull ParsingPackageRead pkg) {
+        switch (state.getEnabledState()) {
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                return true;
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return false;
+            default:
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                return pkg.isEnabled();
+        }
+    }
+}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 518b22b..f5b2ac5 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -24,6 +24,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.Arrays;
+
 import com.android.internal.R;
 
 /**
@@ -258,10 +260,11 @@
             String defaultValue,
             int posture) {
         String sensorType = defaultValue;
-        if (posture < postureMapping.length) {
+        if (postureMapping != null && posture < postureMapping.length) {
             sensorType = postureMapping[posture];
         } else {
-            Log.e(TAG, "Unsupported doze posture " + posture);
+            Log.e(TAG, "Unsupported doze posture " + posture
+                  + " postureMapping=" + Arrays.toString(postureMapping));
         }
 
         return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType;
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c5d37c2..0dc8f92 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,12 +60,18 @@
     /** Brightness */
     public final float brightness;
 
+    /** Brightness after {@link DisplayPowerController} adjustments */
+    public final float adjustedBrightness;
+
     /** Current minimum supported brightness. */
     public final float brightnessMinimum;
 
     /** Current maximum supported brightness. */
     public final float brightnessMaximum;
 
+    /** Brightness values greater than this point are only used in High Brightness Mode. */
+    public final float highBrightnessTransitionPoint;
+
     /**
      * Current state of high brightness mode.
      * Can be any of HIGH_BRIGHTNESS_MODE_* values.
@@ -73,11 +79,20 @@
     public final int highBrightnessMode;
 
     public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
-            @HighBrightnessMode int highBrightnessMode) {
+            @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+        this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
+                highBrightnessTransitionPoint);
+    }
+
+    public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
+            float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
+            float highBrightnessTransitionPoint) {
         this.brightness = brightness;
+        this.adjustedBrightness = adjustedBrightness;
         this.brightnessMinimum = brightnessMinimum;
         this.brightnessMaximum = brightnessMaximum;
         this.highBrightnessMode = highBrightnessMode;
+        this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
     }
 
     /**
@@ -103,9 +118,11 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeFloat(brightness);
+        dest.writeFloat(adjustedBrightness);
         dest.writeFloat(brightnessMinimum);
         dest.writeFloat(brightnessMaximum);
         dest.writeInt(highBrightnessMode);
+        dest.writeFloat(highBrightnessTransitionPoint);
     }
 
     public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -123,9 +140,11 @@
 
     private BrightnessInfo(Parcel source) {
         brightness = source.readFloat();
+        adjustedBrightness = source.readFloat();
         brightnessMinimum = source.readFloat();
         brightnessMaximum = source.readFloat();
         highBrightnessMode = source.readInt();
+        highBrightnessTransitionPoint = source.readFloat();
     }
 
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fb99118..dd387da 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1908,6 +1908,10 @@
         public final HistoryTag localWakeReasonTag = new HistoryTag();
         public final HistoryTag localEventTag = new HistoryTag();
 
+        // Includes a tag's first occurrence in the parcel, so the value of the tag is written
+        // rather than just its index in the history tag pool.
+        public boolean tagsFirstOccurrence;
+
         @UnsupportedAppUsage
         public HistoryItem() {
         }
@@ -2014,6 +2018,7 @@
             wakeReasonTag = null;
             eventCode = EVENT_NONE;
             eventTag = null;
+            tagsFirstOccurrence = false;
         }
 
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2061,6 +2066,7 @@
             } else {
                 eventTag = null;
             }
+            tagsFirstOccurrence = o.tagsFirstOccurrence;
             currentTime = o.currentTime;
         }
 
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 591b665..d37c0eb 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -133,7 +133,6 @@
     private final List<UserBatteryConsumer> mUserBatteryConsumers;
     private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
     private final Parcel mHistoryBuffer;
-    private final List<BatteryStats.HistoryTag> mHistoryTagPool;
     private CursorWindow mBatteryConsumersCursorWindow;
 
     private BatteryUsageStats(@NonNull Builder builder) {
@@ -145,7 +144,6 @@
         mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
         mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
         mHistoryBuffer = builder.mHistoryBuffer;
-        mHistoryTagPool = builder.mHistoryTagPool;
         mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
         mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
         mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
@@ -283,7 +281,7 @@
     /**
      * Returns the names of custom power components in order, so the first name in the array
      * corresponds to the custom componentId
-     * {@link BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID}.
+     * {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}.
      */
     @NonNull
     public String[] getCustomPowerComponentNames() {
@@ -299,8 +297,7 @@
             throw new IllegalStateException(
                     "Battery history was not requested in the BatteryUsageStatsQuery");
         }
-        return new BatteryStatsHistoryIterator(new BatteryStatsHistory(mHistoryBuffer),
-                mHistoryTagPool);
+        return new BatteryStatsHistoryIterator(new BatteryStatsHistory(mHistoryBuffer));
     }
 
     @Override
@@ -361,19 +358,8 @@
 
             mHistoryBuffer = Parcel.obtain();
             mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
-
-            int historyTagCount = source.readInt();
-            mHistoryTagPool = new ArrayList<>(historyTagCount);
-            for (int i = 0; i < historyTagCount; i++) {
-                BatteryStats.HistoryTag tag = new BatteryStats.HistoryTag();
-                tag.string = source.readString();
-                tag.uid = source.readInt();
-                tag.poolIdx = source.readInt();
-                mHistoryTagPool.add(tag);
-            }
         } else {
             mHistoryBuffer = null;
-            mHistoryTagPool = null;
         }
     }
 
@@ -395,13 +381,6 @@
         if (mHistoryBuffer != null) {
             dest.writeBoolean(true);
             dest.writeBlob(mHistoryBuffer.marshall());
-            dest.writeInt(mHistoryTagPool.size());
-            for (int i = mHistoryTagPool.size() - 1; i >= 0; i--) {
-                final BatteryStats.HistoryTag tag = mHistoryTagPool.get(i);
-                dest.writeString(tag.string);
-                dest.writeInt(tag.uid);
-                dest.writeInt(tag.poolIdx);
-            }
         } else {
             dest.writeBoolean(false);
         }
@@ -769,7 +748,6 @@
         private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
                 new SparseArray<>();
         private Parcel mHistoryBuffer;
-        private List<BatteryStats.HistoryTag> mHistoryTagPool;
 
         public Builder(@NonNull String[] customPowerComponentNames) {
             this(customPowerComponentNames, false);
@@ -888,10 +866,8 @@
          * Sets the parceled recent history.
          */
         @NonNull
-        public Builder setBatteryHistory(Parcel historyBuffer,
-                List<BatteryStats.HistoryTag> historyTagPool) {
+        public Builder setBatteryHistory(Parcel historyBuffer) {
             mHistoryBuffer = historyBuffer;
-            mHistoryTagPool = historyTagPool;
             return this;
         }
 
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index d120264..5e66bff 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2952,10 +2952,7 @@
          */
         @Nullable
         public static String getLocalAccountName(@NonNull Context context) {
-            //  config_rawContactsLocalAccountName is defined in
-            //  platform/frameworks/base/core/res/res/values/config.xml
-            return TextUtils.nullIfEmpty(context.getString(
-                    com.android.internal.R.string.config_rawContactsLocalAccountName));
+            return null;
         }
 
         /**
@@ -2971,10 +2968,7 @@
          */
         @Nullable
         public static String getLocalAccountType(@NonNull Context context) {
-            //  config_rawContactsLocalAccountType is defined in
-            //  platform/frameworks/base/core/res/res/values/config.xml
-            return TextUtils.nullIfEmpty(context.getString(
-                    com.android.internal.R.string.config_rawContactsLocalAccountType));
+            return null;
         }
 
         /**
@@ -8698,6 +8692,56 @@
         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
         public static final String ACTION_SET_DEFAULT_ACCOUNT =
                 "android.provider.action.SET_DEFAULT_ACCOUNT";
+
+        /**
+         * The method to invoke in order to set the default account for new contacts.
+         *
+         * @hide
+         */
+        public static final String SET_DEFAULT_ACCOUNT_METHOD = "setDefaultAccount";
+
+        /**
+         * The method to invoke in order to query the default account for new contacts.
+         *
+         * @hide
+         */
+        public static final String QUERY_DEFAULT_ACCOUNT_METHOD = "queryDefaultAccount";
+
+        /**
+         * Key in the incoming Bundle for the default account.
+         *
+         * @hide
+         */
+        public static final String KEY_DEFAULT_ACCOUNT = "key_default_account";
+
+        /**
+         * Return the account that was set to default account for new contacts.
+         */
+        @Nullable
+        public static Account getDefaultAccount(@NonNull ContentResolver resolver) {
+            Bundle response = resolver.call(ContactsContract.AUTHORITY_URI,
+                    QUERY_DEFAULT_ACCOUNT_METHOD, null, null);
+            return response.getParcelable(KEY_DEFAULT_ACCOUNT);
+        }
+
+        /**
+         * Set the account to be the default account for new contacts.
+         *
+         * @param account the account to be set to default.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
+        public static void setDefaultAccount(@NonNull ContentResolver resolver,
+                @Nullable Account account) {
+            Bundle extras = new Bundle();
+            if (account != null) {
+                extras.putString(ACCOUNT_NAME, account.name);
+                extras.putString(ACCOUNT_TYPE, account.type);
+            }
+
+            resolver.call(ContactsContract.AUTHORITY_URI, SET_DEFAULT_ACCOUNT_METHOD, null, extras);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 91b4b7f..b8dbfb3 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -314,6 +314,15 @@
         return intent;
     }
 
+    /**
+     * Call {@link Activity#onCreate} without initializing anything further. This should
+     * only be used when the activity is about to be immediately finished to avoid wasting
+     * initializing steps and leaking resources.
+     */
+    protected void super_onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // Use a specialized prompt when we're handling the 'Home' app startActivity()
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 1871ac5..c5f1848 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -21,8 +21,7 @@
 import android.os.BatteryStats;
 import android.os.Parcel;
 import android.util.Slog;
-
-import java.util.List;
+import android.util.SparseArray;
 
 /**
  * An iterator for {@link BatteryStats.HistoryItem}'s.
@@ -33,23 +32,11 @@
     private final BatteryStatsHistory mBatteryStatsHistory;
     private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
             new BatteryStats.HistoryStepDetails();
-    private final String[] mReadHistoryStrings;
-    private final int[] mReadHistoryUids;
+    private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
 
-    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
-            @NonNull List<BatteryStats.HistoryTag> historyTagPool) {
+    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
-
         mBatteryStatsHistory.startIteratingHistory();
-
-        mReadHistoryStrings = new String[historyTagPool.size()];
-        mReadHistoryUids = new int[historyTagPool.size()];
-        for (int i = historyTagPool.size() - 1; i >= 0; i--) {
-            BatteryStats.HistoryTag tag = historyTagPool.get(i);
-            final int idx = tag.poolIdx;
-            mReadHistoryStrings[idx] = tag.string;
-            mReadHistoryUids[idx] = tag.uid;
-        }
     }
 
     /**
@@ -161,26 +148,16 @@
         }
 
         if ((firstToken & BatteryStatsImpl.DELTA_WAKELOCK_FLAG) != 0) {
-            int indexes = src.readInt();
-            int wakeLockIndex = indexes & 0xffff;
-            int wakeReasonIndex = (indexes >> 16) & 0xffff;
-            if (wakeLockIndex != 0xffff) {
+            final int indexes = src.readInt();
+            final int wakeLockIndex = indexes & 0xffff;
+            final int wakeReasonIndex = (indexes >> 16) & 0xffff;
+            if (readHistoryTag(src, wakeLockIndex, cur.localWakelockTag)) {
                 cur.wakelockTag = cur.localWakelockTag;
-                readHistoryTag(wakeLockIndex, cur.wakelockTag);
-                if (DEBUG) {
-                    Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
-                            + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
-                }
             } else {
                 cur.wakelockTag = null;
             }
-            if (wakeReasonIndex != 0xffff) {
+            if (readHistoryTag(src, wakeReasonIndex, cur.localWakeReasonTag)) {
                 cur.wakeReasonTag = cur.localWakeReasonTag;
-                readHistoryTag(wakeReasonIndex, cur.wakeReasonTag);
-                if (DEBUG) {
-                    Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
-                            + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
-                }
             } else {
                 cur.wakeReasonTag = null;
             }
@@ -195,7 +172,11 @@
             final int codeAndIndex = src.readInt();
             cur.eventCode = (codeAndIndex & 0xffff);
             final int index = ((codeAndIndex >> 16) & 0xffff);
-            readHistoryTag(index, cur.eventTag);
+            if (readHistoryTag(src, index, cur.localEventTag)) {
+                cur.eventTag = cur.localEventTag;
+            } else {
+                cur.eventTag = null;
+            }
             cur.numReadInts += 1;
             if (DEBUG) {
                 Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#"
@@ -220,40 +201,29 @@
         cur.wifiRailChargeMah = src.readDouble();
     }
 
-    int getHistoryStringPoolSize() {
-        return mReadHistoryStrings.length;
-    }
-
-    int getHistoryStringPoolBytes() {
-        int totalChars = 0;
-        for (int i = mReadHistoryStrings.length - 1; i >= 0; i--) {
-            if (mReadHistoryStrings[i] != null) {
-                totalChars += mReadHistoryStrings[i].length() + 1;
-            }
+    private boolean readHistoryTag(Parcel src, int index, BatteryStats.HistoryTag outTag) {
+        if (index == 0xffff) {
+            return false;
         }
 
-        // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size
-        // Each string character is 2 bytes.
-        return (mReadHistoryStrings.length * 12) + (totalChars * 2);
-    }
+        if ((index & BatteryStatsImpl.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+            BatteryStats.HistoryTag tag = new BatteryStats.HistoryTag();
+            tag.readFromParcel(src);
+            tag.poolIdx = index & ~BatteryStatsImpl.TAG_FIRST_OCCURRENCE_FLAG;
+            mHistoryTags.put(tag.poolIdx, tag);
 
-    String getHistoryTagPoolString(int index) {
-        return mReadHistoryStrings[index];
-    }
-
-    int getHistoryTagPoolUid(int index) {
-        return mReadHistoryUids[index];
-    }
-
-    private void readHistoryTag(int index, BatteryStats.HistoryTag tag) {
-        if (index < mReadHistoryStrings.length) {
-            tag.string = mReadHistoryStrings[index];
-            tag.uid = mReadHistoryUids[index];
+            outTag.setTo(tag);
         } else {
-            tag.string = null;
-            tag.uid = 0;
+            BatteryStats.HistoryTag historyTag = mHistoryTags.get(index);
+            if (historyTag != null) {
+                outTag.setTo(historyTag);
+            } else {
+                outTag.string = null;
+                outTag.uid = 0;
+            }
+            outTag.poolIdx = index;
         }
-        tag.poolIdx = index;
+        return true;
     }
 
     private static void readBatteryLevelInt(int batteryLevelInt, BatteryStats.HistoryItem out) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 94a1efe..63cce0a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -160,7 +160,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 201;
+    static final int VERSION = 202;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -757,7 +757,11 @@
     protected boolean mRecordingHistory = false;
     int mNumHistoryItems;
 
+    private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 256;
+
     final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+    private SparseArray<HistoryTag> mHistoryTags;
     final Parcel mHistoryBuffer = Parcel.obtain();
     final HistoryItem mHistoryLastWritten = new HistoryItem();
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
@@ -816,7 +820,6 @@
 
     private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
     private HistoryItem mHistoryIterator;
-    private boolean mReadOverflow;
 
     int mStartCount;
 
@@ -1191,12 +1194,21 @@
     }
 
     public BatteryStatsImpl(Clock clock) {
+        this(clock, (File) null);
+    }
+
+    public BatteryStatsImpl(Clock clock, File historyDirectory) {
         init(clock);
         mStartClockTimeMs = clock.currentTimeMillis();
-        mStatsFile = null;
         mCheckinFile = null;
         mDailyFile = null;
-        mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+        if (historyDirectory == null) {
+            mStatsFile = null;
+            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+        } else {
+            mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
+            mBatteryStatsHistory = new BatteryStatsHistory(this, historyDirectory, mHistoryBuffer);
+        }
         mHandler = null;
         mPlatformIdleStateCallback = null;
         mMeasuredEnergyRetriever = null;
@@ -3321,21 +3333,43 @@
         return kmt;
     }
 
+    /**
+     * Returns the index for the specified tag. If this is the first time the tag is encountered
+     * while writing the current history buffer, the method returns
+     * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+     */
     private int writeHistoryTag(HistoryTag tag) {
         Integer idxObj = mHistoryTagPool.get(tag);
         int idx;
         if (idxObj != null) {
             idx = idxObj;
-        } else {
+            if ((idx & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                idx &= ~TAG_FIRST_OCCURRENCE_FLAG;
+                mHistoryTagPool.put(tag, idx);
+            }
+            return idx;
+        } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
             idx = mNextHistoryTagIdx;
             HistoryTag key = new HistoryTag();
             key.setTo(tag);
             tag.poolIdx = idx;
             mHistoryTagPool.put(key, idx);
             mNextHistoryTagIdx++;
-            mNumHistoryTagChars += key.string.length() + 1;
+            final int stringLength = key.string.length();
+
+            if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+                Slog.wtf(TAG, "Long battery history tag: " + key.string);
+            }
+
+            mNumHistoryTagChars += stringLength + 1;
+            if (mHistoryTags != null) {
+                mHistoryTags.put(idx, key);
+            }
+            return idx | TAG_FIRST_OCCURRENCE_FLAG;
+        } else {
+            // Tag pool overflow: include the tag itself in the parcel
+            return HISTORY_TAG_INDEX_LIMIT | TAG_FIRST_OCCURRENCE_FLAG;
         }
-        return idx;
     }
 
     /*
@@ -3439,6 +3473,10 @@
     // These upper bits are the frequently changing state bits.
     static final int DELTA_STATE_MASK                       = 0xfe000000;
 
+    // Flag in history tag index: indicates that this is the first occurrence of this tag,
+    // therefore the tag value is written in the parcel
+    static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+
     // These are the pieces of battery state that are packed in to the upper bits of
     // the state int that have been packed in to the first delta int.  They must fit
     // in STATE_BATTERY_MASK.
@@ -3556,11 +3594,23 @@
                 wakeReasonIndex = 0xffff;
             }
             dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
+            if (cur.wakelockTag != null && (wakeLockIndex & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakelockTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+            if (cur.wakeReasonTag != null && (wakeReasonIndex & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakeReasonTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
         }
         if (cur.eventCode != HistoryItem.EVENT_NONE) {
-            int index = writeHistoryTag(cur.eventTag);
-            int codeAndIndex = (cur.eventCode&0xffff) | (index<<16);
+            final int index = writeHistoryTag(cur.eventTag);
+            final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
             dest.writeInt(codeAndIndex);
+            if ((index & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.eventTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
             if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
                     + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
                     + cur.eventTag.string);
@@ -3750,6 +3800,7 @@
         if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
                 && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
                 && (diffStates2&lastDiffStates2) == 0
+                && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
                 && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
                 && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
                 && mHistoryLastWritten.stepDetails == null
@@ -3809,9 +3860,17 @@
             mHistoryBuffer.setDataPosition(0);
             mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
             mHistoryBufferLastPos = -1;
+            mHistoryLastWritten.clear();
+            mHistoryLastLastWritten.clear();
+
+            // Mark every entry in the pool with a flag indicating that the tag
+            // has not yet been encountered while writing the current history buffer.
+            for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
+                entry.setValue(entry.getValue() | TAG_FIRST_OCCURRENCE_FLAG);
+            }
+            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
             HistoryItem newItem = new HistoryItem();
             newItem.setTo(cur);
-            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
             addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, newItem);
             return;
         }
@@ -3830,7 +3889,9 @@
         }
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
         mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
         mHistoryLastWritten.states &= mActiveHistoryStates;
         mHistoryLastWritten.states2 &= mActiveHistoryStates2;
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
@@ -3839,6 +3900,7 @@
         cur.wakeReasonTag = null;
         cur.eventCode = HistoryItem.EVENT_NONE;
         cur.eventTag = null;
+        cur.tagsFirstOccurrence = false;
         if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
                 + " now " + mHistoryBuffer.dataPosition()
                 + " size is now " + mHistoryBuffer.dataSize());
@@ -11281,7 +11343,6 @@
     @Override
     @UnsupportedAppUsage
     public boolean startIteratingHistoryLocked() {
-        mReadOverflow = false;
         mBatteryStatsHistoryIterator = createBatteryStatsHistoryIterator();
         return true;
     }
@@ -11291,34 +11352,42 @@
      */
     @VisibleForTesting
     public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
-        ArrayList<HistoryTag> tags = new ArrayList<>(mHistoryTagPool.size());
-        for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
-            final HistoryTag tag = entry.getKey();
-            tag.poolIdx = entry.getValue();
-            tags.add(tag);
-        }
-
-        return new BatteryStatsHistoryIterator(mBatteryStatsHistory, tags);
+        return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
     }
 
     @Override
     public int getHistoryStringPoolSize() {
-        return mBatteryStatsHistoryIterator.getHistoryStringPoolSize();
+        return mHistoryTagPool.size();
     }
 
     @Override
     public int getHistoryStringPoolBytes() {
-        return mBatteryStatsHistoryIterator.getHistoryStringPoolBytes();
+        return mNumHistoryTagChars;
     }
 
     @Override
     public String getHistoryTagPoolString(int index) {
-        return mBatteryStatsHistoryIterator.getHistoryTagPoolString(index);
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.string : null;
     }
 
     @Override
     public int getHistoryTagPoolUid(int index) {
-        return mBatteryStatsHistoryIterator.getHistoryTagPoolUid(index);
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+    }
+
+    private void ensureHistoryTagArray() {
+        if (mHistoryTags != null) {
+            return;
+        }
+
+        mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+        for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
+            mHistoryTags.put(entry.getValue(), entry.getKey());
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index e9e489d..615ab63 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -31,7 +31,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Uses accumulated battery stats data and PowerCalculators to produce power
@@ -186,16 +185,7 @@
             }
 
             BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-            ArrayList<BatteryStats.HistoryTag> tags = new ArrayList<>(
-                    batteryStatsImpl.mHistoryTagPool.size());
-            for (Map.Entry<BatteryStats.HistoryTag, Integer> entry :
-                    batteryStatsImpl.mHistoryTagPool.entrySet()) {
-                final BatteryStats.HistoryTag tag = entry.getKey();
-                tag.poolIdx = entry.getValue();
-                tags.add(tag);
-            }
-
-            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.mHistoryBuffer, tags);
+            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.mHistoryBuffer);
         }
 
         return batteryUsageStatsBuilder.build();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c8d70a8..4f8b317 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -254,6 +254,8 @@
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -751,7 +753,16 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_writeContacts"
         android:description="@string/permdesc_writeContacts"
-        android:protectionLevel="dangerous" />
+      android:protectionLevel="dangerous" />
+
+    <!-- Allows an application to set default account for new contacts.
+        <p> This permission is only granted to system applications fulfilling the Contacts app role.
+        <p>Protection level: internal|role
+        @SystemApi
+        @hide
+    -->
+    <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+        android:protectionLevel="internal|role" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing user's calendar                              -->
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
index 263daf0..9c641e6 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
@@ -18,46 +18,65 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Process;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.Rule;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BatteryStatsHistoryIteratorTest {
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+    private MockClock mMockClock = new MockClock();
+    private MockBatteryStatsImpl mBatteryStats;
+
+    @Before
+    public void setup() {
+        Context context = InstrumentationRegistry.getContext();
+
+        File historyDir = new File(context.getDataDir(), BatteryStatsHistory.HISTORY_DIR);
+        String[] files = historyDir.list();
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
+                new File(historyDir, files[i]).delete();
+            }
+        }
+        historyDir.delete();
+        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
+    }
 
     @Test
     public void testIterator() {
-        MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
-        batteryStats.setRecordAllHistoryLocked(true);
-        batteryStats.forceRecordAllHistory();
+        mBatteryStats.setRecordAllHistoryLocked(true);
+        mBatteryStats.forceRecordAllHistory();
 
-        mStatsRule.setTime(1000, 1000);
-        batteryStats.setNoAutoReset(true);
+        mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
+        mBatteryStats.setNoAutoReset(true);
 
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
                 /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
                 1_000_000, 1_000_000);
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
                 /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
                 2_000_000, 2_000_000);
 
-        batteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
-        batteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
+        mBatteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
+        mBatteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
 
         final BatteryStatsHistoryIterator iterator =
-                batteryStats.createBatteryStatsHistoryIterator();
+                mBatteryStats.createBatteryStatsHistoryIterator();
 
         BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
 
@@ -96,6 +115,57 @@
         assertThat(iterator.next(item)).isFalse();
     }
 
+    // Test history that spans multiple buffers and uses more than 32k different strings.
+    @Test
+    public void tagsLongHistory() {
+        mBatteryStats.setRecordAllHistoryLocked(true);
+        mBatteryStats.forceRecordAllHistory();
+
+        mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
+        mBatteryStats.setNoAutoReset(true);
+
+        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
+                1_000_000, 1_000_000);
+
+        // More than 32k strings
+        final int eventCount = 0x7FFF + 100;
+        for (int i = 0; i < eventCount; i++) {
+            mBatteryStats.noteAlarmStartLocked("a" + i, null, APP_UID, 3_000_000, 2_000_000);
+        }
+
+        final BatteryStatsHistoryIterator iterator =
+                mBatteryStats.createBatteryStatsHistoryIterator();
+
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        assertThat(iterator.next(item)).isTrue();
+        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
+        assertThat(item.eventTag).isNull();
+
+        assertThat(iterator.next(item)).isTrue();
+        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
+        assertThat(item.eventTag).isNull();
+        assertThat(item.time).isEqualTo(1_000_000);
+
+        assertThat(iterator.next(item)).isTrue();
+        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
+        assertThat(item.eventTag).isNull();
+        assertThat(item.time).isEqualTo(2_000_000);
+
+        for (int i = 0; i < eventCount; i++) {
+            assertThat(iterator.next(item)).isTrue();
+            assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM
+                    | BatteryStats.HistoryItem.EVENT_FLAG_START);
+            assertThat(item.eventTag.string).isEqualTo("a" + i);
+        }
+
+        assertThat(iterator.next(item)).isFalse();
+    }
+
     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
             String tag, int uid, int batteryChargeUah, int batteryLevel,
             long elapsedTimeMs) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 74b6dbe..7db31fb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -163,6 +163,73 @@
         assertThat(iterator.next(item)).isFalse();
     }
 
+    @Test
+    public void testWriteAndReadHistoryTags() {
+        MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        batteryStats.setRecordAllHistoryLocked(true);
+        batteryStats.forceRecordAllHistory();
+
+        batteryStats.setNoAutoReset(true);
+
+        mStatsRule.setTime(1_000_000, 1_000_000);
+
+        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
+                1_000_000, 1_000_000);
+
+        // Add a large number of different history tags with strings of increasing length.
+        // These long strings will overflow the history buffer, at which point
+        // history will be written to disk and a new buffer started.
+        // As a result, we will only see a tail end of the sequence of events included
+        // in history.
+        for (int i = 1; i < 200; i++) {
+            StringBuilder sb = new StringBuilder().append(i).append(" ");
+            for (int j = 0; j <= i; j++) {
+                sb.append("word ");
+            }
+            batteryStats.noteJobStartLocked(sb.toString(), i);
+        }
+
+        Context context = InstrumentationRegistry.getContext();
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+
+        final BatteryUsageStats batteryUsageStats =
+                provider.getBatteryUsageStats(
+                        new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
+
+        Parcel parcel = Parcel.obtain();
+        batteryUsageStats.writeToParcel(parcel, 0);
+
+        assertThat(parcel.dataSize()).isAtMost(128_000);
+
+        parcel.setDataPosition(0);
+        BatteryUsageStats unparceled = BatteryUsageStats.CREATOR.createFromParcel(parcel);
+
+        BatteryStatsHistoryIterator iterator = unparceled.iterateBatteryStatsHistory();
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+
+        assertThat(iterator.next(item)).isTrue();
+        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_CURRENT_TIME);
+
+        int lastUid = 0;
+        while (iterator.next(item)) {
+            int uid = item.eventTag.uid;
+            // We expect the history buffer to have been reset in the middle of the run because
+            // there were many different history tags written, exceeding the limit of 128k.
+            assertThat(uid).isGreaterThan(150);
+            assertThat(item.eventCode).isEqualTo(
+                    BatteryStats.HistoryItem.EVENT_JOB | BatteryStats.HistoryItem.EVENT_FLAG_START);
+            assertThat(item.eventTag.string).startsWith(uid + " ");
+            assertThat(item.batteryChargeUah).isEqualTo(3_600_000);
+            assertThat(item.batteryLevel).isEqualTo(90);
+            assertThat(item.time).isEqualTo((long) 1_000_000);
+
+            lastUid = uid;
+        }
+
+        assertThat(lastUid).isEqualTo(199);
+    }
+
     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
             String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) {
         assertThat(item.cmd).isEqualTo(command);
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index d57eb65a..b31587b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -27,6 +27,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.power.MeasuredEnergyStats;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Queue;
@@ -36,13 +37,19 @@
  * Mocks a BatteryStatsImpl object.
  */
 public class MockBatteryStatsImpl extends BatteryStatsImpl {
-    public Clock mClock;
     public boolean mForceOnBattery;
     private NetworkStats mNetworkStats;
 
+    MockBatteryStatsImpl() {
+        this(new MockClock());
+    }
+
     MockBatteryStatsImpl(Clock clock) {
-        super(clock);
-        this.mClock = mClock;
+        this(clock, null);
+    }
+
+    MockBatteryStatsImpl(Clock clock, File historyDirectory) {
+        super(clock, historyDirectory);
         initTimersAndCounters();
 
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
@@ -53,10 +60,6 @@
         };
     }
 
-    MockBatteryStatsImpl() {
-        this(new MockClock());
-    }
-
     public void initMeasuredEnergyStats(String[] customBucketNames) {
         final boolean[] supportedStandardBuckets =
                 new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 5858e39..b77865f 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -312,7 +312,10 @@
     }
 
     /**
-     * Offsets the Outline by (dx,dy)
+     * Offsets the Outline by (dx,dy). Offsetting is cumulative, so additional calls to
+     * offset() will add to previous offset values. Offset only applies to the current
+     * geometry (setRect(), setPath(), etc.); setting new geometry resets any existing
+     * offset.
      */
     public void offset(int dx, int dy) {
         if (mMode == MODE_ROUND_RECT) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 990d7b6..bdf703c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -39,11 +39,12 @@
         return 1;
     }
 
-    @Override
-    public boolean isWindowLayoutComponentAvailable() {
-        return true;
-    }
-
+    /**
+     * Returns a reference implementation of {@link WindowLayoutComponent} if available,
+     * {@code null} otherwise. The implementation must match the API level reported in
+     * {@link WindowExtensions#getWindowLayoutComponent()}.
+     * @return {@link WindowLayoutComponent} OEM implementation
+     */
     @Override
     public WindowLayoutComponent getWindowLayoutComponent() {
         if (mWindowLayoutComponent == null) {
@@ -58,24 +59,10 @@
     }
 
     /**
-     * Returns {@code true} if {@link ActivityEmbeddingComponent} is present on the device,
-     * {@code false} otherwise. If the component is not available the developer will receive a
-     * single callback with empty data or default values where possible.
-     */
-    @Override
-    public boolean isEmbeddingComponentAvailable() {
-        return true;
-    }
-
-    /**
-     * Returns the OEM implementation of {@link ActivityEmbeddingComponent} if it is supported on
-     * the device. The implementation must match the API level reported in
-     * {@link androidx.window.extensions.WindowExtensions}. An
-     * {@link UnsupportedOperationException} will be thrown if the device does not support
-     * Activity Embedding. Use
-     * {@link WindowExtensions#isEmbeddingComponentAvailable()} to determine if
-     * {@link ActivityEmbeddingComponent} is present.
-     * @return the OEM implementation of {@link ActivityEmbeddingComponent}
+     * Returns a reference implementation of {@link ActivityEmbeddingComponent} if available,
+     * {@code null} otherwise. The implementation must match the API level reported in
+     * {@link WindowExtensions#getWindowLayoutComponent()}.
+     * @return {@link ActivityEmbeddingComponent} OEM implementation.
      */
     @NonNull
     public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 25292b9..81be21c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -252,7 +252,8 @@
         // Getting the parent bounds using the updated container - it will have the recent value.
         final Rect parentBounds = getParentContainerBounds(updatedContainer);
         final SplitRule rule = splitContainer.getSplitRule();
-        final Activity activity = splitContainer.getPrimaryContainer().getTopNonFinishingActivity();
+        final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
+        final Activity activity = primaryContainer.getTopNonFinishingActivity();
         if (activity == null) {
             return;
         }
@@ -264,10 +265,12 @@
 
         // If the task fragments are not registered yet, the positions will be updated after they
         // are created again.
-        resizeTaskFragmentIfRegistered(wct, splitContainer.getPrimaryContainer(),
-                primaryRectBounds);
-        resizeTaskFragmentIfRegistered(wct, splitContainer.getSecondaryContainer(),
-                secondaryRectBounds);
+        resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
+        final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
+        resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
+
+        setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+                secondaryContainer.getTaskFragmentToken(), rule);
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 42e829e..4f36c9c 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
new file mode 100644
index 0000000..63289a3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Matches taskbar color -->
+    <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
+</selector>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
deleted file mode 100644
index 006730d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ /dev/null
@@ -1,128 +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 com.android.wm.shell;
-
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
-
-import android.app.ActivityManager;
-import android.graphics.Point;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-
-/**
-  * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
-  */
-public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
-    private static final String TAG = "FullscreenTaskListener";
-
-    private final SyncTransactionQueue mSyncQueue;
-
-    private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
-
-    public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        mSyncQueue = syncQueue;
-    }
-
-    @Override
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        if (mDataByTaskId.get(taskInfo.taskId) != null) {
-            throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
-                taskInfo.taskId);
-        final Point positionInParent = taskInfo.positionInParent;
-        mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
-        mSyncQueue.runInSync(t -> {
-            // Reset several properties back to fullscreen (PiP, for example, leaves all these
-            // properties in a bad state).
-            t.setWindowCrop(leash, null);
-            t.setPosition(leash, positionInParent.x, positionInParent.y);
-            t.setAlpha(leash, 1f);
-            t.setMatrix(leash, 1, 0, 0, 1);
-            t.show(leash);
-        });
-    }
-
-    @Override
-    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
-        final TaskData data = mDataByTaskId.get(taskInfo.taskId);
-        final Point positionInParent = taskInfo.positionInParent;
-        if (!positionInParent.equals(data.positionInParent)) {
-            data.positionInParent.set(positionInParent.x, positionInParent.y);
-            mSyncQueue.runInSync(t -> {
-                t.setPosition(data.surface, positionInParent.x, positionInParent.y);
-            });
-        }
-    }
-
-    @Override
-    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mDataByTaskId.get(taskInfo.taskId) == null) {
-            Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
-            return;
-        }
-        mDataByTaskId.remove(taskInfo.taskId);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
-                taskInfo.taskId);
-    }
-
-    @Override
-    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
-        if (!mDataByTaskId.contains(taskId)) {
-            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
-        }
-        b.setParent(mDataByTaskId.get(taskId).surface);
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        pw.println(prefix + this);
-        pw.println(innerPrefix + mDataByTaskId.size() + " Tasks");
-    }
-
-    @Override
-    public String toString() {
-        return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
-    }
-
-    /**
-     * Per-task data for each managed task.
-     */
-    private static class TaskData {
-        public final SurfaceControl surface;
-        public final Point positionInParent;
-
-        public TaskData(SurfaceControl surface, Point positionInParent) {
-            this.surface = surface;
-            this.positionInParent = positionInParent;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index df4f238..fa58fcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -27,6 +27,8 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -52,6 +54,7 @@
     private final Optional<AppPairsController> mAppPairsOptional;
     private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
+    private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
     private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -71,6 +74,7 @@
             Optional<AppPairsController> appPairsOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
+            Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
             Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
             Transitions transitions,
             StartingWindowController startingWindow,
@@ -86,6 +90,7 @@
         mAppPairsOptional = appPairsOptional;
         mFullscreenTaskListener = fullscreenTaskListener;
         mPipTouchHandlerOptional = pipTouchHandlerOptional;
+        mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
         mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
         mTransitions = transitions;
         mMainExecutor = mainExecutor;
@@ -128,6 +133,8 @@
         mFreeformTaskListenerOptional.ifPresent(f ->
                 mShellTaskOrganizer.addListenerForType(
                         f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
+
+        mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
     }
 
     @ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
new file mode 100644
index 0000000..3f17f2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.fullscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
+import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.graphics.Point;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+  * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
+  */
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
+    private static final String TAG = "FullscreenTaskListener";
+
+    private final SyncTransactionQueue mSyncQueue;
+
+    private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
+    private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
+    private final FullscreenUnfoldController mFullscreenUnfoldController;
+
+    public FullscreenTaskListener(SyncTransactionQueue syncQueue,
+            Optional<FullscreenUnfoldController> unfoldController) {
+        mSyncQueue = syncQueue;
+        mFullscreenUnfoldController = unfoldController.orElse(null);
+    }
+
+    @Override
+    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        if (mDataByTaskId.get(taskInfo.taskId) != null) {
+            throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
+                taskInfo.taskId);
+        final Point positionInParent = taskInfo.positionInParent;
+        mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        mSyncQueue.runInSync(t -> {
+            // Reset several properties back to fullscreen (PiP, for example, leaves all these
+            // properties in a bad state).
+            t.setWindowCrop(leash, null);
+            t.setPosition(leash, positionInParent.x, positionInParent.y);
+            t.setAlpha(leash, 1f);
+            t.setMatrix(leash, 1, 0, 0, 1);
+            t.show(leash);
+        });
+
+        mAnimatableTasksListener.onTaskAppeared(taskInfo);
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+
+        mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
+
+        final TaskData data = mDataByTaskId.get(taskInfo.taskId);
+        final Point positionInParent = taskInfo.positionInParent;
+        if (!positionInParent.equals(data.positionInParent)) {
+            data.positionInParent.set(positionInParent.x, positionInParent.y);
+            mSyncQueue.runInSync(t -> {
+                t.setPosition(data.surface, positionInParent.x, positionInParent.y);
+            });
+        }
+    }
+
+    @Override
+    public void onTaskVanished(RunningTaskInfo taskInfo) {
+        if (mDataByTaskId.get(taskInfo.taskId) == null) {
+            Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+            return;
+        }
+
+        mAnimatableTasksListener.onTaskVanished(taskInfo);
+        mDataByTaskId.remove(taskInfo.taskId);
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
+                taskInfo.taskId);
+    }
+
+    @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (!mDataByTaskId.contains(taskId)) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mDataByTaskId.get(taskId).surface);
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + this);
+        pw.println(innerPrefix + mDataByTaskId.size() + " Tasks");
+    }
+
+    @Override
+    public String toString() {
+        return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
+    }
+
+    /**
+     * Per-task data for each managed task.
+     */
+    private static class TaskData {
+        public final SurfaceControl surface;
+        public final Point positionInParent;
+
+        public TaskData(SurfaceControl surface, Point positionInParent) {
+            this.surface = surface;
+            this.positionInParent = positionInParent;
+        }
+    }
+
+    class AnimatableTasksListener {
+        private final SparseBooleanArray mTaskIds = new SparseBooleanArray();
+
+        public void onTaskAppeared(RunningTaskInfo taskInfo) {
+            final boolean isApplicable = isAnimatable(taskInfo);
+            if (isApplicable) {
+                mTaskIds.put(taskInfo.taskId, true);
+
+                if (mFullscreenUnfoldController != null) {
+                    SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
+                    mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
+                }
+            }
+        }
+
+        public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+            final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
+            final boolean isApplicable = isAnimatable(taskInfo);
+
+            if (isCurrentlyApplicable) {
+                if (isApplicable) {
+                    // Still applicable, send update
+                    if (mFullscreenUnfoldController != null) {
+                        mFullscreenUnfoldController.onTaskInfoChanged(taskInfo);
+                    }
+                } else {
+                    // Became inapplicable
+                    if (mFullscreenUnfoldController != null) {
+                        mFullscreenUnfoldController.onTaskVanished(taskInfo);
+                    }
+                    mTaskIds.put(taskInfo.taskId, false);
+                }
+            } else {
+                if (isApplicable) {
+                    // Became applicable
+                    mTaskIds.put(taskInfo.taskId, true);
+
+                    if (mFullscreenUnfoldController != null) {
+                        SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
+                        mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
+                    }
+                }
+            }
+        }
+
+        public void onTaskVanished(RunningTaskInfo taskInfo) {
+            final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
+            if (isCurrentlyApplicable && mFullscreenUnfoldController != null) {
+                mFullscreenUnfoldController.onTaskVanished(taskInfo);
+            }
+            mTaskIds.put(taskInfo.taskId, false);
+        }
+
+        private boolean isAnimatable(TaskInfo taskInfo) {
+            // Filter all visible tasks that are not launcher tasks
+            // We do not animate launcher as it handles the animation by itself
+            return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration()
+                    .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
new file mode 100644
index 0000000..08ab85c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
@@ -0,0 +1,268 @@
+/*
+ * 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.wm.shell.fullscreen;
+
+import static android.graphics.Color.blue;
+import static android.graphics.Color.green;
+import static android.graphics.Color.red;
+import static android.util.MathUtils.lerp;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls full screen app unfold transition: animating cropping window and scaling when
+ * folding or unfolding a foldable device.
+ */
+public final class FullscreenUnfoldController implements UnfoldListener,
+        OnInsetsChangedListener {
+
+    private static final float[] FLOAT_9 = new float[9];
+    private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+
+    private static final float HORIZONTAL_START_MARGIN = 0.08f;
+    private static final float VERTICAL_START_MARGIN = 0.03f;
+    private static final float END_SCALE = 1f;
+    private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
+    private static final int BACKGROUND_LAYER_Z_INDEX = -1;
+
+    private final Context mContext;
+    private final Executor mExecutor;
+    private final ShellUnfoldProgressProvider mProgressProvider;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final DisplayInsetsController mDisplayInsetsController;
+
+    private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+
+    private SurfaceControl mBackgroundLayer;
+    private InsetsSource mTaskbarInsetsSource;
+
+    private final float mWindowCornerRadiusPx;
+    private final float[] mBackgroundColor;
+    private final float mExpandedTaskBarHeight;
+
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    public FullscreenUnfoldController(
+            @NonNull Context context,
+            @NonNull Executor executor,
+            @NonNull ShellUnfoldProgressProvider progressProvider,
+            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            @NonNull DisplayInsetsController displayInsetsController
+    ) {
+        mContext = context;
+        mExecutor = executor;
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mProgressProvider = progressProvider;
+        mDisplayInsetsController = displayInsetsController;
+        mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.taskbar_frame_height);
+        mBackgroundColor = getBackgroundColor();
+    }
+
+    /**
+     * Initializes the controller
+     */
+    public void init() {
+        mProgressProvider.addListener(mExecutor, this);
+        mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+    }
+
+    @Override
+    public void onStateChangeProgress(float progress) {
+        if (mAnimationContextByTaskId.size() == 0) return;
+
+        ensureBackground();
+
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+            context.mCurrentCropRect.set(RECT_EVALUATOR
+                    .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+            float scale = lerp(START_SCALE, END_SCALE, progress);
+            context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
+                    context.mCurrentCropRect.exactCenterY());
+
+            mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+                    .setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
+                    .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+        }
+
+        mTransaction.apply();
+    }
+
+    @Override
+    public void onStateChangeFinished() {
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+            resetSurface(context);
+        }
+
+        removeBackground();
+        mTransaction.apply();
+    }
+
+    @Override
+    public void insetsChanged(InsetsState insetsState) {
+        mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+            context.update(mTaskbarInsetsSource, context.mTaskInfo);
+        }
+    }
+
+    /**
+     * Called when a new matching task appeared
+     */
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+        AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
+                taskInfo);
+        mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
+    }
+
+    /**
+     * Called when matching task changed
+     */
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+        if (animationContext != null) {
+            animationContext.update(mTaskbarInsetsSource, taskInfo);
+        }
+    }
+
+    /**
+     * Called when matching task vanished
+     */
+    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+        if (animationContext != null) {
+            resetSurface(animationContext);
+            mAnimationContextByTaskId.remove(taskInfo.taskId);
+        }
+
+        if (mAnimationContextByTaskId.size() == 0) {
+            removeBackground();
+        }
+
+        mTransaction.apply();
+    }
+
+    private void resetSurface(AnimationContext context) {
+        mTransaction
+                .setWindowCrop(context.mLeash, null)
+                .setCornerRadius(context.mLeash, 0.0F)
+                .setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
+                .setPosition(context.mLeash,
+                        (float) context.mTaskInfo.positionInParent.x,
+                        (float) context.mTaskInfo.positionInParent.y);
+    }
+
+    private void ensureBackground() {
+        if (mBackgroundLayer != null) return;
+
+        SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                .setName("app-unfold-background")
+                .setCallsite("AppUnfoldTransitionController")
+                .setColorLayer();
+        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+        mBackgroundLayer = colorLayerBuilder.build();
+
+        mTransaction
+                .setColor(mBackgroundLayer, mBackgroundColor)
+                .show(mBackgroundLayer)
+                .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
+    }
+
+    private void removeBackground() {
+        if (mBackgroundLayer == null) return;
+        if (mBackgroundLayer.isValid()) {
+            mTransaction.remove(mBackgroundLayer);
+        }
+        mBackgroundLayer = null;
+    }
+
+    private float[] getBackgroundColor() {
+        int colorInt = mContext.getResources().getColor(R.color.unfold_transition_background);
+        return new float[]{
+                (float) red(colorInt) / 255.0F,
+                (float) green(colorInt) / 255.0F,
+                (float) blue(colorInt) / 255.0F
+        };
+    }
+
+    private class AnimationContext {
+        final SurfaceControl mLeash;
+        final Rect mStartCropRect = new Rect();
+        final Rect mEndCropRect = new Rect();
+        final Rect mCurrentCropRect = new Rect();
+        final Matrix mMatrix = new Matrix();
+
+        TaskInfo mTaskInfo;
+
+        private AnimationContext(SurfaceControl leash,
+                                InsetsSource taskBarInsetsSource,
+                                TaskInfo taskInfo) {
+            this.mLeash = leash;
+            update(taskBarInsetsSource, taskInfo);
+        }
+
+        private void update(InsetsSource taskBarInsetsSource, TaskInfo taskInfo) {
+            mTaskInfo = taskInfo;
+            mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
+
+            if (taskBarInsetsSource != null) {
+                // Only insets the cropping window with task bar when it's expanded
+                if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+                    mStartCropRect.inset(taskBarInsetsSource
+                            .calculateVisibleInsets(mStartCropRect));
+                }
+            }
+
+            mEndCropRect.set(mStartCropRect);
+
+            int horizontalMargin = (int) (mEndCropRect.width() * HORIZONTAL_START_MARGIN);
+            mStartCropRect.left = mEndCropRect.left + horizontalMargin;
+            mStartCropRect.right = mEndCropRect.right - horizontalMargin;
+            int verticalMargin = (int) (mEndCropRect.height() * VERTICAL_START_MARGIN);
+            mStartCropRect.top = mEndCropRect.top + verticalMargin;
+            mStartCropRect.bottom = mEndCropRect.bottom - verticalMargin;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
new file mode 100644
index 0000000..74e4812
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wm.shell.unfold;
+
+import android.annotation.FloatRange;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper interface for unfold transition progress provider for the Shell
+ * @see com.android.systemui.unfold.UnfoldTransitionProgressProvider
+ */
+public interface ShellUnfoldProgressProvider {
+
+    /**
+     * Adds a transition listener
+     */
+    void addListener(Executor executor, UnfoldListener listener);
+
+    /**
+     * Listener for receiving unfold updates
+     */
+    interface UnfoldListener {
+        default void onStateChangeStarted() {}
+
+        default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
+
+        default void onStateChangeFinished() {}
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
new file mode 100644
index 0000000..d6f7e54
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.wm.shell.fullscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+public class FullscreenTaskListenerTest {
+
+    @Mock
+    private SyncTransactionQueue mSyncQueue;
+    @Mock
+    private FullscreenUnfoldController mUnfoldController;
+    @Mock
+    private SurfaceControl mSurfaceControl;
+
+    private Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
+
+    private FullscreenTaskListener mListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mFullscreenUnfoldController = Optional.of(mUnfoldController);
+        mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController);
+    }
+
+    @Test
+    public void testAnimatableTaskAppeared_notifiesUnfoldController() {
+        RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0);
+
+        mListener.onTaskAppeared(info, mSurfaceControl);
+
+        verify(mUnfoldController).onTaskAppeared(eq(info), any());
+    }
+
+    @Test
+    public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() {
+        RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0);
+        RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1);
+
+        mListener.onTaskAppeared(animatable1, mSurfaceControl);
+        mListener.onTaskAppeared(animatable2, mSurfaceControl);
+
+        InOrder order = inOrder(mUnfoldController);
+        order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any());
+        order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any());
+    }
+
+    @Test
+    public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() {
+        RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+
+        mListener.onTaskAppeared(info, mSurfaceControl);
+
+        verifyNoMoreInteractions(mUnfoldController);
+    }
+
+    @Test
+    public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() {
+        RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+        mListener.onTaskAppeared(info, mSurfaceControl);
+
+        mListener.onTaskInfoChanged(info);
+
+        verifyNoMoreInteractions(mUnfoldController);
+    }
+
+    @Test
+    public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() {
+        RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+        mListener.onTaskAppeared(info, mSurfaceControl);
+
+        mListener.onTaskVanished(info);
+
+        verifyNoMoreInteractions(mUnfoldController);
+    }
+
+    @Test
+    public void testAnimatableTaskBecameInactive_notifiesUnfoldController() {
+        RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0);
+        mListener.onTaskAppeared(animatableTask, mSurfaceControl);
+        RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0);
+
+        mListener.onTaskInfoChanged(notAnimatableTask);
+
+        verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask));
+    }
+
+    @Test
+    public void testAnimatableTaskVanished_notifiesUnfoldController() {
+        RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0);
+        mListener.onTaskAppeared(taskInfo, mSurfaceControl);
+
+        mListener.onTaskVanished(taskInfo);
+
+        verify(mUnfoldController).onTaskVanished(eq(taskInfo));
+    }
+
+    private RunningTaskInfo createTaskInfo(boolean visible, int taskId) {
+        final RunningTaskInfo info = spy(new RunningTaskInfo());
+        info.isVisible = visible;
+        info.positionInParent = new Point();
+        when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        final Configuration configuration = new Configuration();
+        configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+        when(info.getConfiguration()).thenReturn(configuration);
+        info.taskId = taskId;
+        return info;
+    }
+}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 9a82ab1..49477b9 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -28,6 +28,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.SystemProperties;
+import android.sysprop.MediaProperties;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
@@ -196,13 +197,20 @@
     private static final Range<Rational> POSITIVE_RATIONALS =
             Range.create(new Rational(1, Integer.MAX_VALUE),
                          new Rational(Integer.MAX_VALUE, 1));
-    private static final Range<Integer> SIZE_RANGE =
-            Process.is64Bit() ? Range.create(1, 32768) : Range.create(1, 4096);
     private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
     private static final Range<Integer> BITRATE_RANGE = Range.create(0, 500000000);
     private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32;
     private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256;
 
+    private static final class LazyHolder {
+        private static final Range<Integer> SIZE_RANGE = Process.is64Bit()
+                ? Range.create(1, 32768)
+                : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
+    }
+    private static Range<Integer> getSizeRange() {
+        return LazyHolder.SIZE_RANGE;
+    }
+
     // found stuff that is not supported by framework (=> this should not happen)
     private static final int ERROR_UNRECOGNIZED   = (1 << 0);
     // found profile/level for which we don't have capability estimates
@@ -2234,12 +2242,12 @@
         private void initWithPlatformLimits() {
             mBitrateRange = BITRATE_RANGE;
 
-            mWidthRange  = SIZE_RANGE;
-            mHeightRange = SIZE_RANGE;
+            mWidthRange  = getSizeRange();
+            mHeightRange = getSizeRange();
             mFrameRateRange = FRAME_RATE_RANGE;
 
-            mHorizontalBlockRange = SIZE_RANGE;
-            mVerticalBlockRange   = SIZE_RANGE;
+            mHorizontalBlockRange = getSizeRange();
+            mVerticalBlockRange   = getSizeRange();
 
             // full positive ranges are supported as these get calculated
             mBlockCountRange      = POSITIVE_INTEGERS;
@@ -2253,7 +2261,7 @@
             mHeightAlignment = 2;
             mBlockWidth = 2;
             mBlockHeight = 2;
-            mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+            mSmallerDimensionUpperLimit = getSizeRange().getUpper();
         }
 
         private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
@@ -2494,10 +2502,10 @@
                 // codec supports profiles that we don't know.
                 // Use supplied values clipped to platform limits
                 if (widths != null) {
-                    mWidthRange = SIZE_RANGE.intersect(widths);
+                    mWidthRange = getSizeRange().intersect(widths);
                 }
                 if (heights != null) {
-                    mHeightRange = SIZE_RANGE.intersect(heights);
+                    mHeightRange = getSizeRange().intersect(heights);
                 }
                 if (counts != null) {
                     mBlockCountRange = POSITIVE_INTEGERS.intersect(
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 458821e..60d21c0 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -1073,16 +1073,24 @@
     @UnsupportedAppUsage
     private native byte[] getEmbeddedPicture(int pictureType);
 
+    /**
+     * Releases any acquired resources. Call it when done with the object.
+     *
+     * @throws IOException When an {@link IOException} is thrown while closing a {@link
+     * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}.
+     */
     @Override
-    public void close() {
+    public void close() throws IOException {
         release();
     }
 
     /**
-     * Call it when one is done with the object. This method releases the memory
-     * allocated internally.
+     * Releases any acquired resources. Call it when done with the object.
+     *
+     * @throws IOException When an {@link IOException} is thrown while closing a {@link
+     * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}.
      */
-    public native void release();
+    public native void release() throws IOException;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private native void native_setup();
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
index 380de9c..da106be 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
@@ -19,120 +19,138 @@
 import android.media.MediaMetadataRetriever;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
-/**
- * This metadata test suite test the basic functionality of the 
- * MediaMetadataRetriever
- * 
- */
+
+import java.io.IOException;
+
+/** This metadata test suite test the basic functionality of the MediaMetadataRetriever */
 public class MediaMetadataTest extends AndroidTestCase {
-    
+
     private static final String TAG = "MediaMetadataTest";
 
-    public static enum METADATA_EXPECTEDRESULT{
-        FILE_PATH,CD_TRACK, ALBUM,
-        ARTIST, AUTHOR, COMPOSER,
-        DATE, GENRE, TITLE,
-        YEAR, DURATION, NUM_TRACKS, WRITER
+    public enum METADATA_EXPECTEDRESULT {
+        FILE_PATH,
+        CD_TRACK,
+        ALBUM,
+        ARTIST,
+        AUTHOR,
+        COMPOSER,
+        DATE,
+        GENRE,
+        TITLE,
+        YEAR,
+        DURATION,
+        NUM_TRACKS,
+        WRITER
     }
-    
-    public static enum MP3_TEST_FILE{
-        ID3V1V2, ID3V2, ID3V1
+
+    public enum MP3_TEST_FILE {
+        ID3V1V2,
+        ID3V2,
+        ID3V1
     }
-    
+
     public static METADATA_EXPECTEDRESULT meta;
     public static MP3_TEST_FILE mp3_test_file;
-   
+
     @MediumTest
     public static void testID3V1V2Metadata() throws Exception {
         validateMetatData(mp3_test_file.ID3V1V2.ordinal(), MediaNames.META_DATA_MP3);
     }
-    
+
     @MediumTest
     public static void testID3V2Metadata() throws Exception {
         validateMetatData(mp3_test_file.ID3V2.ordinal(), MediaNames.META_DATA_MP3);
     }
-    
+
     @MediumTest
     public static void testID3V1Metadata() throws Exception {
         validateMetatData(mp3_test_file.ID3V1.ordinal(), MediaNames.META_DATA_MP3);
     }
 
-    private static void validateMetatData(int fileIndex, String meta_data_file[][]) {
-        Log.v(TAG, "filePath = "+ meta_data_file[fileIndex][0]);
-        if ((meta_data_file[fileIndex][0].endsWith("wma") && !MediaProfileReader.getWMAEnable()) ||
-            (meta_data_file[fileIndex][0].endsWith("wmv") && !MediaProfileReader.getWMVEnable())) {
+    private static void validateMetatData(int fileIndex, String[][] metadataFile)
+            throws IOException {
+        Log.v(TAG, "filePath = " + metadataFile[fileIndex][0]);
+        if ((metadataFile[fileIndex][0].endsWith("wma") && !MediaProfileReader.getWMAEnable())
+                || (metadataFile[fileIndex][0].endsWith("wmv")
+                        && !MediaProfileReader.getWMVEnable())) {
             return;
         }
         String value = null;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         try {
-            retriever.setDataSource(meta_data_file[fileIndex][0]);
-        } catch(Exception e) {
-            Log.v(TAG, "Failed: "+meta_data_file[fileIndex][0] + " " + e.toString());
-            //Set the test case failure whenever it failed to setDataSource
+            retriever.setDataSource(metadataFile[fileIndex][0]);
+        } catch (Exception e) {
+            Log.v(TAG, "Failed: " + metadataFile[fileIndex][0] + " " + e.toString());
+            // Set the test case failure whenever it failed to setDataSource
             assertTrue("Failed to setDataSource ", false);
         }
-        
-        //METADATA_KEY_CD_TRACK_NUMBER should return the TCRK value
+
+        // METADATA_KEY_CD_TRACK_NUMBER should return the TCRK value
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER);
         Log.v(TAG, "CD_TRACK_NUMBER : " + value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.CD_TRACK.ordinal()], value);
-       
+        assertEquals(TAG, metadataFile[fileIndex][meta.CD_TRACK.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
-        Log.v(TAG, "Album : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.ALBUM.ordinal()], value); 
-        
+        Log.v(TAG, "Album : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.ALBUM.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
-        Log.v(TAG, "Artist : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.ARTIST.ordinal()], value);
-        
+        Log.v(TAG, "Artist : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.ARTIST.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR);
-        Log.v(TAG, "Author : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.AUTHOR.ordinal()], value);
-        
+        Log.v(TAG, "Author : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.AUTHOR.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER);
-        Log.v(TAG, "Composer : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.COMPOSER.ordinal()], value);
-        
+        Log.v(TAG, "Composer : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.COMPOSER.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE);
-        Log.v(TAG, "Date : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.DATE.ordinal()], value);
-        
+        Log.v(TAG, "Date : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.DATE.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE);
-        Log.v(TAG, "Genre : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.GENRE.ordinal()], value);
-        
+        Log.v(TAG, "Genre : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.GENRE.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
-        Log.v(TAG, "Title : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.TITLE.ordinal()], value);
-        
+        Log.v(TAG, "Title : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.TITLE.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR);
-        Log.v(TAG, "Year : "+ value);
-        assertEquals(TAG, meta_data_file[fileIndex][meta.YEAR.ordinal()], value);
-        
+        Log.v(TAG, "Year : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.YEAR.ordinal()], value);
+
         value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
-        Log.v(TAG, "Expected = " + meta_data_file[fileIndex][meta.DURATION.ordinal()] + "reult = " + value);
+        Log.v(
+                TAG,
+                "Expected = "
+                        + metadataFile[fileIndex][meta.DURATION.ordinal()]
+                        + "reult = "
+                        + value);
         // Only require that the returned duration is within 100ms of the expected
         // one as PV and stagefright differ slightly in their implementation.
-        assertTrue(TAG, Math.abs(Integer.parseInt(
-                        meta_data_file[fileIndex][meta.DURATION.ordinal()])
-                            - Integer.parseInt(value)) < 100);
-        
-        //METADATA_KEY_NUM_TRACKS should return the total number of tracks in the media
-        //include the video and audio
-        value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS);
-        Log.v(TAG, "Track : "+ value);
-        assertEquals(TAG,meta_data_file[fileIndex][meta.NUM_TRACKS.ordinal()], value);
-     
-        value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER);
-        Log.v(TAG, "Writer : "+ value);
-        assertEquals(TAG,meta_data_file[fileIndex][meta.WRITER.ordinal()], value);
+        int durationDifferenceMs =
+                Math.abs(
+                        Integer.parseInt(metadataFile[fileIndex][meta.DURATION.ordinal()])
+                                - Integer.parseInt(value));
+        assertTrue(TAG, durationDifferenceMs < 100);
 
-        retriever.release();        
+        // METADATA_KEY_NUM_TRACKS should return the total number of tracks in the media
+        // include the video and audio
+        value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS);
+        Log.v(TAG, "Track : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.NUM_TRACKS.ordinal()], value);
+
+        value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER);
+        Log.v(TAG, "Writer : " + value);
+        assertEquals(TAG, metadataFile[fileIndex][meta.WRITER.ordinal()], value);
+
+        retriever.release();
     }
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index 8eb75f3..bdca474 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,19 +16,23 @@
 
 package com.android.mediaframeworktest.unit;
 
-import android.util.Log;
-import android.media.MediaMetadataRetriever;
 import android.graphics.Bitmap;
-import java.io.FileOutputStream;
+import android.media.MediaMetadataRetriever;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
-import android.test.suitebuilder.annotation.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
 
 public class MediaMetadataRetrieverTest extends AndroidTestCase {
-    
-    private static final String TAG         = "MediaMetadataRetrieverTest";
-   
+
+    private static final String TAG = "MediaMetadataRetrieverTest";
+
     // Test album art extraction.
     @MediumTest
     public static void testGetEmbeddedPicture() throws Exception {
@@ -40,10 +44,12 @@
         for (int i = 0, n = MediaNames.ALBUMART_TEST_FILES.length; i < n; ++i) {
             try {
                 Log.v(TAG, "File " + i + ": " + MediaNames.ALBUMART_TEST_FILES[i]);
-                if ((MediaNames.ALBUMART_TEST_FILES[i].endsWith(".wma") && !supportWMA) ||
-                    (MediaNames.ALBUMART_TEST_FILES[i].endsWith(".wmv") && !supportWMV)
-                   ) {
-                    Log.v(TAG, "windows media is not supported and thus we will skip the test for this file");
+                if ((MediaNames.ALBUMART_TEST_FILES[i].endsWith(".wma") && !supportWMA)
+                        || (MediaNames.ALBUMART_TEST_FILES[i].endsWith(".wmv") && !supportWMV)) {
+                    Log.v(
+                            TAG,
+                            "windows media is not supported and thus we will skip the test for this"
+                                    + " file");
                     continue;
                 }
                 retriever.setDataSource(MediaNames.ALBUMART_TEST_FILES[i]);
@@ -52,15 +58,18 @@
                 // TODO:
                 // A better test would be to compare the retrieved album art with the
                 // known result.
-                if (albumArt == null) {  // Do we have expect in JUnit?
-                    Log.e(TAG, "Fails to get embedded picture for " + MediaNames.ALBUMART_TEST_FILES[i]);
+                if (albumArt == null) { // Do we have expect in JUnit?
+                    Log.e(
+                            TAG,
+                            "Fails to get embedded picture for "
+                                    + MediaNames.ALBUMART_TEST_FILES[i]);
                     hasFailed = true;
                 }
-            } catch(Exception e) {
+            } catch (Exception e) {
                 Log.e(TAG, "Fails to setDataSource for " + MediaNames.ALBUMART_TEST_FILES[i]);
                 hasFailed = true;
             }
-            Thread.yield();  // Don't be evil
+            Thread.yield(); // Don't be evil
         }
         retriever.release();
         Log.v(TAG, "testGetEmbeddedPicture completes.");
@@ -76,61 +85,82 @@
         boolean hasFailed = false;
         Log.v(TAG, "Thumbnail processing starts");
         long startedAt = System.currentTimeMillis();
-        for(int i = 0, n = MediaNames.THUMBNAIL_METADATA_TEST_FILES.length; i < n; ++i) {
+        for (int i = 0, n = MediaNames.THUMBNAIL_METADATA_TEST_FILES.length; i < n; ++i) {
             try {
                 Log.v(TAG, "File " + i + ": " + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
-                if ((MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wma") && !supportWMA) ||
-                    (MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wmv") && !supportWMV)
-                   ) {
-                    Log.v(TAG, "windows media is not supported and thus we will skip the test for this file");
+                if ((MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wma") && !supportWMA)
+                        || (MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wmv")
+                                && !supportWMV)) {
+                    Log.v(
+                            TAG,
+                            "windows media is not supported and thus we will skip the test for this"
+                                    + " file");
                     continue;
                 }
                 retriever.setDataSource(MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
                 Bitmap bitmap = retriever.getFrameAtTime(-1);
                 assertTrue(bitmap != null);
                 try {
-                    java.io.OutputStream stream = new FileOutputStream(MediaNames.THUMBNAIL_METADATA_TEST_FILES[i] + ".jpg");
+                    java.io.OutputStream stream =
+                            new FileOutputStream(
+                                    MediaNames.THUMBNAIL_METADATA_TEST_FILES[i] + ".jpg");
                     bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream);
                     stream.close();
                 } catch (Exception e) {
-                    Log.e(TAG, "Fails to convert the bitmap to a JPEG file for " + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
+                    Log.e(
+                            TAG,
+                            "Fails to convert the bitmap to a JPEG file for "
+                                    + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
                     hasFailed = true;
                     Log.e(TAG, e.toString());
                 }
-            } catch(Exception e) {
-                Log.e(TAG, "Fails to setDataSource for file " + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
+            } catch (Exception e) {
+                Log.e(
+                        TAG,
+                        "Fails to setDataSource for file "
+                                + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
                 hasFailed = true;
             }
-            Thread.yield();  // Don't be evil
+            Thread.yield(); // Don't be evil
         }
         long endedAt = System.currentTimeMillis();
         retriever.release();
         assertTrue(!hasFailed);
-        Log.v(TAG, "Average processing time per thumbnail: " + (endedAt - startedAt)/MediaNames.THUMBNAIL_METADATA_TEST_FILES.length + " ms");
+        Log.v(
+                TAG,
+                "Average processing time per thumbnail: "
+                        + (endedAt - startedAt) / MediaNames.THUMBNAIL_METADATA_TEST_FILES.length
+                        + " ms");
     }
-    
+
     @LargeTest
     public static void testMetadataRetrieval() throws Exception {
         boolean supportWMA = MediaProfileReader.getWMAEnable();
         boolean supportWMV = MediaProfileReader.getWMVEnable();
         boolean hasFailed = false;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        for(int i = 0, n = MediaNames.THUMBNAIL_METADATA_TEST_FILES.length; i < n; ++i) {
+        for (int i = 0, n = MediaNames.THUMBNAIL_METADATA_TEST_FILES.length; i < n; ++i) {
             try {
                 Log.v(TAG, "File " + i + ": " + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
-                if ((MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wma") && !supportWMA) ||
-                    (MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wmv") && !supportWMV)
-                   ) {
-                    Log.v(TAG, "windows media is not supported and thus we will skip the test for this file");
+                if ((MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wma") && !supportWMA)
+                        || (MediaNames.THUMBNAIL_METADATA_TEST_FILES[i].endsWith(".wmv")
+                                && !supportWMV)) {
+                    Log.v(
+                            TAG,
+                            "windows media is not supported and thus we will skip the test for this"
+                                    + " file");
                     continue;
                 }
                 retriever.setDataSource(MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
                 extractAllSupportedMetadataValues(retriever);
-            } catch(Exception e) {
-                Log.e(TAG, "Fails to setDataSource for file " + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
+            } catch (Exception e) {
+                Log.e(
+                        TAG,
+                        "Fails to setDataSource for file "
+                                + MediaNames.THUMBNAIL_METADATA_TEST_FILES[i]);
                 hasFailed = true;
             }
-            Thread.yield();  // Don't be evil
+            Thread.yield(); // Don't be evil
         }
         retriever.release();
         assertTrue(!hasFailed);
@@ -151,10 +181,12 @@
                 bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream);
                 stream.close();
             } catch (Exception e) {
-                throw new Exception("Fails to convert the bitmap to a JPEG file for " + MediaNames.TEST_PATH_1, e);
+                throw new Exception(
+                        "Fails to convert the bitmap to a JPEG file for " + MediaNames.TEST_PATH_1,
+                        e);
             }
             extractAllSupportedMetadataValues(retriever);
-        } catch(Exception e) {
+        } catch (Exception e) {
             Log.e(TAG, "Fails to setDataSource for " + MediaNames.TEST_PATH_1, e);
             hasFailed = true;
         }
@@ -181,7 +213,7 @@
 
     // Test setDataSource()
     @MediumTest
-    public static void testSetDataSource() {
+    public static void testSetDataSource() throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean hasFailed = false;
 
@@ -191,7 +223,7 @@
             retriever.setDataSource(path);
             Log.e(TAG, "IllegalArgumentException failed to be thrown.");
             hasFailed = true;
-        } catch(Exception e) {
+        } catch (Exception e) {
             if (!(e instanceof IllegalArgumentException)) {
                 Log.e(TAG, "Expected a IllegalArgumentException, but got a different exception");
                 hasFailed = true;
@@ -203,7 +235,7 @@
             retriever.setDataSource(MediaNames.TEST_PATH_5);
             Log.e(TAG, "IllegalArgumentException failed to be thrown.");
             hasFailed = true;
-        } catch(Exception e) {
+        } catch (Exception e) {
             if (!(e instanceof IllegalArgumentException)) {
                 Log.e(TAG, "Expected a IllegalArgumentException, but got a different exception");
                 hasFailed = true;
@@ -215,7 +247,7 @@
             retriever.setDataSource(MediaNames.TEST_PATH_4);
             Log.e(TAG, "RuntimeException failed to be thrown.");
             hasFailed = true;
-        } catch(Exception e) {
+        } catch (Exception e) {
             if (!(e instanceof RuntimeException)) {
                 Log.e(TAG, "Expected a RuntimeException, but got a different exception");
                 hasFailed = true;
@@ -228,13 +260,13 @@
             retriever.setDataSource(MediaNames.TEST_PATH_3);
             Log.e(TAG, "RuntimeException failed to be thrown.");
             hasFailed = true;
-        } catch(Exception e) {
+        } catch (Exception e) {
             if (!(e instanceof RuntimeException)) {
                 Log.e(TAG, "Expected a RuntimeException, but got a different exception");
                 hasFailed = true;
             }
         }
-        
+
         retriever.release();
         assertTrue(!hasFailed);
     }
@@ -244,17 +276,23 @@
     // We should be able to compare the actual returned metadata with the expected metadata
     // with each given sample test file.
     private static void extractAllSupportedMetadataValues(MediaMetadataRetriever retriever) {
-        String value = null;
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)) == null? "not found": value);
-        Log.v(TAG, (value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR)) == null? "not found": value);
+        int[] metadataRetrieverKeys =
+                new int[] {
+                    MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
+                    MediaMetadataRetriever.METADATA_KEY_DURATION,
+                    MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
+                    MediaMetadataRetriever.METADATA_KEY_ALBUM,
+                    MediaMetadataRetriever.METADATA_KEY_ARTIST,
+                    MediaMetadataRetriever.METADATA_KEY_AUTHOR,
+                    MediaMetadataRetriever.METADATA_KEY_COMPOSER,
+                    MediaMetadataRetriever.METADATA_KEY_DATE,
+                    MediaMetadataRetriever.METADATA_KEY_GENRE,
+                    MediaMetadataRetriever.METADATA_KEY_TITLE,
+                    MediaMetadataRetriever.METADATA_KEY_YEAR
+                };
+        for (int metadataRetrieverKey : metadataRetrieverKeys) {
+            String value = retriever.extractMetadata(metadataRetrieverKey);
+            Log.v(TAG, value == null ? "not found" : value);
+        }
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 8f3e4bd..220cf6b 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -18,6 +18,7 @@
 
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -329,6 +330,18 @@
     }
 
     /**
+     * Whether the {@link Activity} should be launched in a separate task.
+     */
+    public boolean isNewTask(Context context) {
+        ensureMetadataNotStale(context);
+        if (mMetaData != null
+                && mMetaData.containsKey(META_DATA_NEW_TASK)) {
+            return mMetaData.getBoolean(META_DATA_NEW_TASK);
+        }
+        return false;
+    }
+
+    /**
      * Ensures metadata is not stale for this tile.
      */
     private void ensureMetadataNotStale(Context context) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index a2bec33..acc0087 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -230,6 +230,13 @@
     public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
 
     /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml
+     * to specify whether the {@link android.app.Activity} should be launched in a separate task.
+     * This should be a boolean value {@code true} or {@code false}, set using {@code android:value}
+     */
+    public static final String META_DATA_NEW_TASK = "com.android.settings.new_task";
+
+    /**
      * Build a list of DashboardCategory.
      */
     public static List<DashboardCategory> getCategories(Context context,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 0256615..1f75ae3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -139,14 +139,14 @@
      */
     public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
-        if (memberDevices != null) {
+        // TODO: check the CSIP group size instead of the real member device set size, and adjust
+        // the size restriction.
+        if (memberDevices.size() == 1) {
             for (CachedBluetoothDevice memberDevice : memberDevices) {
-                if (!memberDevice.isConnected()) {
-                    return null;
+                if (memberDevice.isConnected()) {
+                    return memberDevice.getConnectionSummary();
                 }
             }
-
-            return device.getConnectionSummary();
         }
         CachedBluetoothDevice subDevice = device.getSubDevice();
         if (subDevice != null && subDevice.isConnected()) {
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 786b371..c2901a5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -224,15 +224,23 @@
          display brightness, suitable to listen to while the device is asleep (e.g. during
          always-on display) -->
     <string-array name="doze_brightness_sensor_name_posture_mapping" translatable="false">
-        <item></item> <!-- UNKNOWN -->
-        <item></item> <!-- CLOSED -->
-        <item></item> <!-- HALF_OPENED -->
-        <item></item> <!-- OPENED -->
+        <!-- UNKNOWN -->
+        <!-- CLOSED -->
+        <!-- HALF_OPENED -->
+        <!-- OPENED -->
     </string-array>
 
     <!-- Override value to use for proximity sensor.  -->
     <string name="proximity_sensor_type" translatable="false"></string>
 
+    <!-- Sensor type per posture state to use for proximity sensor -->
+    <string-array name="proximity_sensor_posture_mapping" translatable="false">
+        <!-- UNKNOWN -->
+        <!-- CLOSED -->
+        <!-- HALF_OPENED -->
+        <!-- OPENED -->
+    </string-array>
+
     <!-- If using proximity_sensor_type, specifies a threshold value to distinguish near and
          far break points. A sensor value less than this is considered "near". -->
     <item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item>
@@ -246,6 +254,15 @@
     <!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. -->
     <string name="proximity_sensor_secondary_type" translatable="false"></string>
 
+    <!-- Sensor type per posture state to use for proximity sensor as a confirmation for
+        proximity_sensor_type. -->
+    <string-array name="proximity_sensor_secondary_posture_mapping" translatable="false">
+        <!-- UNKNOWN -->
+        <!-- CLOSED -->
+        <!-- HALF_OPENED -->
+        <!-- OPENED -->
+    </string-array>
+
     <!-- If using proximity_sensor_secondary_type, specifies a threshold value to distinguish
          near and far break points. A sensor value less than this is considered "near". -->
     <item name="proximity_sensor_secondary_threshold" translatable="false" format="float"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 62411db..11eeac2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -153,6 +153,10 @@
             colorState = mNextMessageColorState;
             mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
         }
+        if (mAltBouncerShowing) {
+            // alt bouncer has a black scrim, so always show the text in white
+            colorState = ColorStateList.valueOf(Color.WHITE);
+        }
         setTextColor(colorState);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 14e5991..be326da 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -37,6 +37,7 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
+import com.android.systemui.util.sensors.ThresholdSensorEvent;
 import com.android.systemui.util.time.SystemClock;
 
 import java.util.Collections;
@@ -405,7 +406,7 @@
         mProximitySensor.unregister(mSensorEventListener);
     }
 
-    private void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+    private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
         // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
         // make these calls.
         mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
@@ -423,9 +424,9 @@
     }
 
     private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
-        private ThresholdSensor.ThresholdSensorEvent mThresholdSensorEvent;
+        private ThresholdSensorEvent mThresholdSensorEvent;
 
-        ProximityEventImpl(ThresholdSensor.ThresholdSensorEvent thresholdSensorEvent) {
+        ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
             mThresholdSensorEvent = thresholdSensorEvent;
         }
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index e117405..fa23842 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -24,8 +24,6 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.om.OverlayManager;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.ColorDisplayManager;
 import android.os.Handler;
@@ -56,7 +54,6 @@
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.LifecycleScreenStatusProvider;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -69,9 +66,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.theme.ThemeOverlayApplier;
-import com.android.systemui.unfold.UnfoldTransitionFactory;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -287,37 +281,6 @@
     /** */
     @Provides
     @SysUISingleton
-    public UnfoldTransitionProgressProvider provideUnfoldTransitionProgressProvider(
-            Context context,
-            UnfoldTransitionConfig config,
-            LifecycleScreenStatusProvider screenStatusProvider,
-            DeviceStateManager deviceStateManager,
-            SensorManager sensorManager,
-            @Main Executor executor,
-            @Main Handler handler
-    ) {
-        return UnfoldTransitionFactory
-                .createUnfoldTransitionProgressProvider(
-                        context,
-                        config,
-                        screenStatusProvider,
-                        deviceStateManager,
-                        sensorManager,
-                        handler,
-                        executor
-                );
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    public UnfoldTransitionConfig provideUnfoldTransitionConfig(Context context) {
-        return UnfoldTransitionFactory.createConfig(context);
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
     public Choreographer providesChoreographer() {
         return Choreographer.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 648f345..18f8519 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -24,6 +24,7 @@
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.dagger.qualifiers.TestHarness;
 import com.android.systemui.plugins.PluginsModule;
+import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
 
 import javax.inject.Singleton;
@@ -49,6 +50,7 @@
 @Module(includes = {
         FrameworkServicesModule.class,
         GlobalConcurrencyModule.class,
+        UnfoldTransitionModule.class,
         PluginsModule.class,
 })
 public class GlobalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 1e7f0d9..b17f078 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -46,6 +46,7 @@
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximityCheck;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -90,7 +91,7 @@
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
     private final DockEventListener mDockEventListener = new DockEventListener();
     private final DockManager mDockManager;
-    private final ProximitySensor.ProximityCheck mProxCheck;
+    private final ProximityCheck mProxCheck;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final AuthController mAuthController;
     private final DelayableExecutor mMainExecutor;
@@ -181,7 +182,8 @@
             AmbientDisplayConfiguration config,
             DozeParameters dozeParameters, AsyncSensorManager sensorManager,
             WakeLock wakeLock, DockManager dockManager,
-            ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck,
+            ProximitySensor proximitySensor,
+            ProximityCheck proxCheck,
             DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
             SecureSettings secureSettings, AuthController authController,
             @Main DelayableExecutor mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index b0f3959..21a1b75 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,11 +18,11 @@
 
 import android.util.ArrayMap
 import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
+import javax.inject.Singleton
 
 /**
  * Maintains a registry of things that should be dumped when a bug report is taken
@@ -33,7 +33,7 @@
  *
  * See [DumpHandler] for more information on how and when this information is dumped.
  */
-@SysUISingleton
+@Singleton
 open class DumpManager @Inject constructor() {
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index f25ec55..044a57c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -15,12 +15,12 @@
  */
 package com.android.systemui.keyguard
 
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
 import javax.inject.Inject
+import javax.inject.Singleton
 
-@SysUISingleton
+@Singleton
 class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenLifecycle) :
     ScreenStatusProvider, ScreenLifecycle.Observer {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 30983aa..d17c39a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -19,18 +19,18 @@
 import android.os.Trace;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Tracks the screen lifecycle.
  */
-@SysUISingleton
+@Singleton
 public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable {
 
     public static final int SCREEN_OFF = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index a1a630a..7622d66 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -195,7 +195,8 @@
         mNavigationModeController.addListener(this);
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setOverviewProxyService(commandQueue, overviewProxyService,
-                navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer);
+                navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
+                dumpManager);
         mIsTablet = isTablet(mContext);
         mUserTracker = userTracker;
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index fa616921..4d29612 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -46,8 +46,12 @@
 import android.view.View;
 import android.view.WindowInsetsController.Behavior;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
@@ -55,13 +59,16 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
 @Singleton
 public class TaskbarDelegate implements CommandQueue.Callbacks,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
-        ComponentCallbacks {
+        ComponentCallbacks, Dumpable {
 
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
 
@@ -93,13 +100,14 @@
             OverviewProxyService overviewProxyService,
             NavigationBarA11yHelper navigationBarA11yHelper,
             NavigationModeController navigationModeController,
-            SysUiState sysUiState) {
+            SysUiState sysUiState, DumpManager dumpManager) {
         // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mCommandQueue = commandQueue;
         mOverviewProxyService = overviewProxyService;
         mNavigationBarA11yHelper = navigationBarA11yHelper;
         mNavigationModeController = navigationModeController;
         mSysUiState = sysUiState;
+        dumpManager.registerDumpable(this);
     }
 
     public void destroy() {
@@ -220,4 +228,14 @@
 
     @Override
     public void onLowMemory() {}
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
+        pw.println("  mNavigationIconHints=" + mNavigationIconHints);
+        pw.println("  mDisabledFlags=" + mDisabledFlags);
+        pw.println("  mTaskBarWindowState=" + mTaskBarWindowState);
+        pw.println("  mBehavior=" + mBehavior);
+        mEdgeBackGestureHandler.dump(pw);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5c5dd6b..562d7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -253,6 +253,7 @@
     private boolean mExpandedInThisMotion;
     private boolean mShouldShowShelfOnly;
     protected boolean mScrollingEnabled;
+    private boolean mIsCurrentUserSetup;
     protected FooterView mFooterView;
     protected EmptyShadeView mEmptyShadeView;
     private boolean mDismissAllInProgress;
@@ -682,6 +683,7 @@
         boolean showDismissView = mClearAllEnabled &&
                 mController.hasActiveClearableNotifications(ROWS_ALL);
         boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
+                && mIsCurrentUserSetup  // see: b/193149550
                 && mStatusBarState != StatusBarState.KEYGUARD
                 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
                 && !mIsRemoteInputActive;
@@ -5608,6 +5610,16 @@
     }
 
     /**
+     * Sets whether the current user is set up, which is required to show the footer (b/193149550)
+     */
+    public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+        if (mIsCurrentUserSetup != isCurrentUserSetup) {
+            mIsCurrentUserSetup = isCurrentUserSetup;
+            updateFooter();
+        }
+    }
+
+    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index c9033b9..216115e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -119,6 +119,8 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
@@ -146,6 +148,7 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
     private final TunerService mTunerService;
+    private final DeviceProvisionedController mDeviceProvisionedController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final ConfigurationController mConfigurationController;
     private final ZenModeController mZenModeController;
@@ -226,6 +229,28 @@
                 }
             };
 
+    private final DeviceProvisionedListener mDeviceProvisionedListener =
+            new DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    updateCurrentUserIsSetup();
+                }
+
+                @Override
+                public void onUserSwitched() {
+                    updateCurrentUserIsSetup();
+                }
+
+                @Override
+                public void onUserSetupChanged() {
+                    updateCurrentUserIsSetup();
+                }
+
+                private void updateCurrentUserIsSetup() {
+                    mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
+                }
+            };
+
     private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
         if (mView.isExpanded()) {
             // The bottom might change because we're using the final actual height of the view
@@ -600,6 +625,7 @@
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
             TunerService tunerService,
+            DeviceProvisionedController deviceProvisionedController,
             DynamicPrivacyController dynamicPrivacyController,
             ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
@@ -636,6 +662,7 @@
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
         mTunerService = tunerService;
+        mDeviceProvisionedController = deviceProvisionedController;
         mDynamicPrivacyController = dynamicPrivacyController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
@@ -788,6 +815,9 @@
             return Unit.INSTANCE;
         });
 
+        // callback is invoked synchronously, updating mView immediately
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5bc97a5..18fd9d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1149,7 +1149,6 @@
     public void showBouncerMessage(String message, ColorStateList colorState) {
         if (isShowingAlternateAuth()) {
             if (mKeyguardMessageAreaController != null) {
-                mKeyguardMessageAreaController.setNextMessageColor(colorState);
                 mKeyguardMessageAreaController.setMessage(message);
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
index 8596875..bbba19d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
@@ -46,6 +46,7 @@
     int DEVICE_POSTURE_HALF_OPENED = 2;
     int DEVICE_POSTURE_OPENED = 3;
     int DEVICE_POSTURE_FLIPPED = 4;
+    int SUPPORTED_POSTURES_SIZE = DEVICE_POSTURE_FLIPPED + 1;
 
     /** Return the current device posture. */
     @DevicePostureInt int getDevicePosture();
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt
new file mode 100644
index 0000000..4a88435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener
+import java.util.concurrent.Executor
+
+class ShellUnfoldProgressProvider(
+    private val unfoldProgressProvider: UnfoldTransitionProgressProvider
+) : ShellUnfoldProgressProvider {
+
+    override fun addListener(executor: Executor, listener: UnfoldListener) {
+        unfoldProgressProvider.addCallback(object : TransitionProgressListener {
+            override fun onTransitionStarted() {
+                executor.execute {
+                    listener.onStateChangeStarted()
+                }
+            }
+
+            override fun onTransitionProgress(progress: Float) {
+                executor.execute {
+                    listener.onStateChangeProgress(progress)
+                }
+            }
+
+            override fun onTransitionFinished() {
+                executor.execute {
+                    listener.onStateChangeFinished()
+                }
+            }
+        })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
new file mode 100644
index 0000000..b23aa20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.LifecycleScreenStatusProvider
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Singleton
+
+@Module
+class UnfoldTransitionModule {
+
+    @Provides
+    @Singleton
+    fun provideUnfoldTransitionProgressProvider(
+        context: Context,
+        config: UnfoldTransitionConfig,
+        screenStatusProvider: LifecycleScreenStatusProvider,
+        deviceStateManager: DeviceStateManager,
+        sensorManager: SensorManager,
+        @Main executor: Executor,
+        @Main handler: Handler
+    ): UnfoldTransitionProgressProvider =
+        createUnfoldTransitionProgressProvider(
+            context,
+            config,
+            screenStatusProvider,
+            deviceStateManager,
+            sensorManager,
+            handler,
+            executor
+        )
+
+    @Provides
+    @Singleton
+    fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
+        createConfig(context)
+
+    @Provides
+    @Singleton
+    fun provideShellProgressProvider(
+        config: UnfoldTransitionConfig,
+        provider: Lazy<UnfoldTransitionProgressProvider>
+    ): Optional<ShellUnfoldProgressProvider> =
+        if (config.isEnabled) {
+            Optional.ofNullable(ShellUnfoldProgressProvider(provider.get()))
+        } else {
+            Optional.empty()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index 6c35433..da4ccd0 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -63,8 +63,7 @@
         Intent intent = getIntent();
         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
         if (!(targetParcelable instanceof Intent)) {
-            super.onCreate(savedInstanceState);
-            onStop(); // unregister receiver registered in onCreate (PackageMonitor)
+            super_onCreate(savedInstanceState);
             Log.w("UsbResolverActivity", "Target is not an intent: " + targetParcelable);
             finish();
             return;
@@ -97,8 +96,7 @@
         } else {
             mAccessory = (UsbAccessory)target.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
             if (mAccessory == null) {
-                super.onCreate(savedInstanceState);
-                onStop(); // unregister receiver registered in onCreate (PackageMonitor)
+                super_onCreate(savedInstanceState);
                 Log.e(TAG, "no device or accessory");
                 finish();
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
new file mode 100644
index 0000000..28cba8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
+
+import javax.inject.Inject;
+
+/**
+ * Proximity sensor that changes proximity sensor usage based on the current posture.
+ * Posture -> prox sensor mapping can be found in SystemUI config overlays at:
+ *   - proximity_sensor_posture_mapping
+ *   - proximity_sensor_secondary_posture_mapping.
+ * where the array indices correspond to the following postures:
+ *     [UNKNOWN, CLOSED, HALF_OPENED, OPENED]
+ */
+class PostureDependentProximitySensor extends ProximitySensorImpl {
+    private final ThresholdSensor[] mPostureToPrimaryProxSensorMap;
+    private final ThresholdSensor[] mPostureToSecondaryProxSensorMap;
+
+    @Inject
+    PostureDependentProximitySensor(
+            @PrimaryProxSensor ThresholdSensor[] postureToPrimaryProxSensorMap,
+            @SecondaryProxSensor ThresholdSensor[] postureToSecondaryProxSensorMap,
+            @NonNull DelayableExecutor delayableExecutor,
+            @NonNull Execution execution,
+            @NonNull DevicePostureController devicePostureController
+    ) {
+        super(
+                postureToPrimaryProxSensorMap[0],
+                postureToSecondaryProxSensorMap[0],
+                delayableExecutor,
+                execution
+        );
+        mPostureToPrimaryProxSensorMap = postureToPrimaryProxSensorMap;
+        mPostureToSecondaryProxSensorMap = postureToSecondaryProxSensorMap;
+        mDevicePosture = devicePostureController.getDevicePosture();
+        devicePostureController.addCallback(mDevicePostureCallback);
+
+        chooseSensors();
+    }
+    private void chooseSensors() {
+        if (mDevicePosture >= mPostureToPrimaryProxSensorMap.length
+                || mDevicePosture >= mPostureToSecondaryProxSensorMap.length) {
+            Log.e("PostureDependentProxSensor",
+                    "unsupported devicePosture=" + mDevicePosture);
+            return;
+        }
+
+        ThresholdSensor newPrimaryProx = mPostureToPrimaryProxSensorMap[mDevicePosture];
+        ThresholdSensor newSecondaryProx = mPostureToSecondaryProxSensorMap[mDevicePosture];
+
+        if (newPrimaryProx != mPrimaryThresholdSensor
+                || newSecondaryProx != mSecondaryThresholdSensor) {
+            logDebug("Register new proximity sensors newPosture="
+                    + DevicePostureController.devicePostureToString(mDevicePosture));
+            unregisterInternal();
+
+            if (mPrimaryThresholdSensor != null) {
+                mPrimaryThresholdSensor.unregister(mPrimaryEventListener);
+            }
+            if (mSecondaryThresholdSensor != null) {
+                mSecondaryThresholdSensor.unregister(mSecondaryEventListener);
+            }
+
+            mPrimaryThresholdSensor = newPrimaryProx;
+            mSecondaryThresholdSensor = newSecondaryProx;
+
+            mInitializedListeners = false;
+            registerInternal();
+        }
+    }
+
+    private final DevicePostureController.Callback mDevicePostureCallback =
+            posture -> {
+                if (mDevicePosture == posture) {
+                    return;
+                }
+
+                mDevicePosture = posture;
+                chooseSensors();
+            };
+
+    @Override
+    public String toString() {
+        return String.format("{posture=%s, proximitySensor=%s}",
+                DevicePostureController.devicePostureToString(mDevicePosture), super.toString());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
new file mode 100644
index 0000000..a8a6341
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Convenience class allowing for briefly checking the proximity sensor.
+ */
+public class ProximityCheck implements Runnable {
+
+    private final ProximitySensor mSensor;
+    private final DelayableExecutor mDelayableExecutor;
+    private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+    private final ThresholdSensor.Listener mListener;
+    private final AtomicBoolean mRegistered = new AtomicBoolean();
+
+    @Inject
+    public ProximityCheck(
+            ProximitySensor sensor,
+            @Main DelayableExecutor delayableExecutor) {
+        mSensor = sensor;
+        mSensor.setTag("prox_check");
+        mDelayableExecutor = delayableExecutor;
+        mListener = this::onProximityEvent;
+    }
+
+    /** Set a descriptive tag for the sensors registration. */
+    public void setTag(String tag) {
+        mSensor.setTag(tag);
+    }
+
+    @Override
+    public void run() {
+        unregister();
+        onProximityEvent(null);
+    }
+
+    /**
+     * Query the proximity sensor, timing out if no result.
+     */
+    public void check(long timeoutMs, Consumer<Boolean> callback) {
+        if (!mSensor.isLoaded()) {
+            callback.accept(null);
+            return;
+        }
+        mCallbacks.add(callback);
+        if (!mRegistered.getAndSet(true)) {
+            mSensor.register(mListener);
+            mDelayableExecutor.executeDelayed(this, timeoutMs);
+        }
+    }
+
+    private void unregister() {
+        mSensor.unregister(mListener);
+        mRegistered.set(false);
+    }
+
+    private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
+        mCallbacks.forEach(
+                booleanConsumer ->
+                        booleanConsumer.accept(
+                                proximityEvent == null ? null : proximityEvent.getBelow()));
+        mCallbacks.clear();
+        unregister();
+        mRegistered.set(false);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index bd11039..d3f1c93 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,149 +16,24 @@
 
 package com.android.systemui.util.sensors;
 
-import android.hardware.SensorManager;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.Execution;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
 /**
- * Wrapper around SensorManager customized for the Proximity sensor.
- *
- * The ProximitySensor supports the concept of a primary and a
- * secondary hardware sensor. The primary sensor is used for a first
- * pass check if the phone covered. When triggered, it then checks
- * the secondary sensor for confirmation (if there is one). It does
- * not send a proximity event until the secondary sensor confirms (or
- * rejects) the reading. The secondary sensor is, in fact, the source
- * of truth.
- *
- * This is necessary as sometimes keeping the secondary sensor on for
- * extends periods is undesirable. It may, however, result in increased
- * latency for proximity readings.
- *
- * Phones should configure this via a config.xml overlay. If no
- * proximity sensor is set (primary or secondary) we fall back to the
- * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in
- * config.xml, that will be used as the primary sensor. If
- * proximity_sensor_secondary_type is set, that will function as the
- * secondary sensor. If no secondary is set, only the primary will be
- * used.
+ * Wrapper class for a dual ProximitySensor which supports a secondary sensor gated upon the
+ * primary).
  */
-public class ProximitySensor implements ThresholdSensor {
-    private static final String TAG = "ProxSensor";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final long SECONDARY_PING_INTERVAL_MS = 5000;
-
-    private final ThresholdSensor mPrimaryThresholdSensor;
-    private final ThresholdSensor mSecondaryThresholdSensor;
-    private final DelayableExecutor mDelayableExecutor;
-    private final Execution mExecution;
-    private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
-    private String mTag = null;
-    @VisibleForTesting protected boolean mPaused;
-    private ThresholdSensorEvent mLastPrimaryEvent;
-    @VisibleForTesting
-    ThresholdSensorEvent mLastEvent;
-    private boolean mRegistered;
-    private final AtomicBoolean mAlerting = new AtomicBoolean();
-    private Runnable mCancelSecondaryRunnable;
-    private boolean mInitializedListeners = false;
-    private boolean mSecondarySafe = false;
-
-    private final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
-
-    private final ThresholdSensor.Listener mSecondaryEventListener =
-            new ThresholdSensor.Listener() {
-        @Override
-        public void onThresholdCrossed(ThresholdSensorEvent event) {
-            // If we no longer have a "below" signal and the secondary sensor is not
-            // considered "safe", then we need to turn it off.
-            if (!mSecondarySafe
-                    && (mLastPrimaryEvent == null
-                    || !mLastPrimaryEvent.getBelow()
-                    || !event.getBelow())) {
-                chooseSensor();
-                if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
-                    // Only check the secondary as long as the primary thinks we're near.
-                    if (mCancelSecondaryRunnable != null) {
-                        mCancelSecondaryRunnable.run();
-                        mCancelSecondaryRunnable = null;
-                    }
-                    return;
-                } else {
-                    // Check this sensor again in a moment.
-                    mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
-                        // This is safe because we know that mSecondaryThresholdSensor
-                        // is loaded, otherwise we wouldn't be here.
-                        mPrimaryThresholdSensor.pause();
-                        mSecondaryThresholdSensor.resume();
-                    },
-                        SECONDARY_PING_INTERVAL_MS);
-                }
-            }
-            logDebug("Secondary sensor event: " + event.getBelow() + ".");
-
-            if (!mPaused) {
-                onSensorEvent(event);
-            }
-        }
-    };
-
-    @Inject
-    public ProximitySensor(
-            @PrimaryProxSensor ThresholdSensor primary,
-            @SecondaryProxSensor ThresholdSensor  secondary,
-            @Main DelayableExecutor delayableExecutor,
-            Execution execution) {
-        mPrimaryThresholdSensor = primary;
-        mSecondaryThresholdSensor = secondary;
-        mDelayableExecutor = delayableExecutor;
-        mExecution = execution;
-    }
-
-    @Override
-    public void setTag(String tag) {
-        mTag = tag;
-        mPrimaryThresholdSensor.setTag(tag + ":primary");
-        mSecondaryThresholdSensor.setTag(tag + ":secondary");
-    }
-
-    @Override
-    public void setDelay(int delay) {
-        mExecution.assertIsMainThread();
-        mPrimaryThresholdSensor.setDelay(delay);
-        mSecondaryThresholdSensor.setDelay(delay);
-    }
+public interface ProximitySensor extends ThresholdSensor {
+    /**
+     * Returns true if we are registered with the SensorManager.
+     */
+    boolean isRegistered();
 
     /**
-     * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+     * Whether the proximity sensor reports near. Can return null if no information has been
+     * received yet.
      */
-    @Override
-    public void pause() {
-        mExecution.assertIsMainThread();
-        mPaused = true;
-        unregisterInternal();
-    }
+    Boolean isNear();
 
-    /**
-     * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
-     */
-    @Override
-    public void resume() {
-        mExecution.assertIsMainThread();
-        mPaused = false;
-        registerInternal();
-    }
+    /** Update all listeners with the last value this class received from the sensor. */
+    void alertListeners();
 
     /**
      * Sets that it is safe to leave the secondary sensor on indefinitely.
@@ -166,249 +41,5 @@
      * The secondary sensor will be turned on if there are any registered listeners, regardless
      * of what is reported by the primary sensor.
      */
-    public void setSecondarySafe(boolean safe) {
-        mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
-        chooseSensor();
-    }
-
-    /**
-     * Returns true if we are registered with the SensorManager.
-     */
-    public boolean isRegistered() {
-        return mRegistered;
-    }
-
-    /**
-     * Returns {@code false} if a Proximity sensor is not available.
-     */
-    @Override
-    public boolean isLoaded() {
-        return mPrimaryThresholdSensor.isLoaded();
-    }
-
-    /**
-     * Add a listener.
-     *
-     * Registers itself with the {@link SensorManager} if this is the first listener
-     * added. If the ProximitySensor is paused, it will be registered when resumed.
-     */
-    @Override
-    public void register(ThresholdSensor.Listener listener) {
-        mExecution.assertIsMainThread();
-        if (!isLoaded()) {
-            return;
-        }
-
-        if (mListeners.contains(listener)) {
-            logDebug("ProxListener registered multiple times: " + listener);
-        } else {
-            mListeners.add(listener);
-        }
-        registerInternal();
-    }
-
-    protected void registerInternal() {
-        mExecution.assertIsMainThread();
-        if (mRegistered || mPaused || mListeners.isEmpty()) {
-            return;
-        }
-        if (!mInitializedListeners) {
-            mPrimaryThresholdSensor.pause();
-            mSecondaryThresholdSensor.pause();
-            mPrimaryThresholdSensor.register(mPrimaryEventListener);
-            mSecondaryThresholdSensor.register(mSecondaryEventListener);
-            mInitializedListeners = true;
-        }
-        logDebug("Registering sensor listener");
-
-        mRegistered = true;
-        chooseSensor();
-    }
-
-    private void chooseSensor() {
-        mExecution.assertIsMainThread();
-        if (!mRegistered || mPaused || mListeners.isEmpty()) {
-            return;
-        }
-        if (mSecondarySafe) {
-            mSecondaryThresholdSensor.resume();
-            mPrimaryThresholdSensor.pause();
-        } else {
-            mPrimaryThresholdSensor.resume();
-            mSecondaryThresholdSensor.pause();
-        }
-    }
-
-    /**
-     * Remove a listener.
-     *
-     * If all listeners are removed from an instance of this class,
-     * it will unregister itself with the SensorManager.
-     */
-    @Override
-    public void unregister(ThresholdSensor.Listener listener) {
-        mExecution.assertIsMainThread();
-        mListeners.remove(listener);
-        if (mListeners.size() == 0) {
-            unregisterInternal();
-        }
-    }
-
-    protected void unregisterInternal() {
-        mExecution.assertIsMainThread();
-        if (!mRegistered) {
-            return;
-        }
-        logDebug("unregistering sensor listener");
-        mPrimaryThresholdSensor.pause();
-        mSecondaryThresholdSensor.pause();
-        if (mCancelSecondaryRunnable != null) {
-            mCancelSecondaryRunnable.run();
-            mCancelSecondaryRunnable = null;
-        }
-        mLastPrimaryEvent = null;  // Forget what we know.
-        mLastEvent = null;
-        mRegistered = false;
-    }
-
-    public Boolean isNear() {
-        return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
-    }
-
-    /** Update all listeners with the last value this class received from the sensor. */
-    public void alertListeners() {
-        mExecution.assertIsMainThread();
-        if (mAlerting.getAndSet(true)) {
-            return;
-        }
-        if (mLastEvent != null) {
-            ThresholdSensorEvent lastEvent = mLastEvent;  // Listeners can null out mLastEvent.
-            List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
-            listeners.forEach(proximitySensorListener ->
-                    proximitySensorListener.onThresholdCrossed(lastEvent));
-        }
-
-        mAlerting.set(false);
-    }
-
-    private void onPrimarySensorEvent(ThresholdSensorEvent event) {
-        mExecution.assertIsMainThread();
-        if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
-            return;
-        }
-
-        mLastPrimaryEvent = event;
-
-        if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
-            logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
-                    + ". Checking secondary.");
-            if (mCancelSecondaryRunnable == null) {
-                mSecondaryThresholdSensor.resume();
-            }
-            return;
-        }
-
-
-        if (!mSecondaryThresholdSensor.isLoaded()) {  // No secondary
-            logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
-            onSensorEvent(event);
-        } else if (event.getBelow()) {  // Covered? Check secondary.
-            logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
-            if (mCancelSecondaryRunnable != null) {
-                mCancelSecondaryRunnable.run();
-            }
-            mSecondaryThresholdSensor.resume();
-        } else {  // Uncovered. Report immediately.
-            onSensorEvent(event);
-        }
-    }
-
-    private void onSensorEvent(ThresholdSensorEvent event) {
-        mExecution.assertIsMainThread();
-        if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
-            return;
-        }
-
-        if (!mSecondarySafe && !event.getBelow()) {
-            chooseSensor();
-        }
-
-        mLastEvent = event;
-        alertListeners();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, "
-                + "secondarySensor=%s secondarySafe=%s}",
-                isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor,
-                mSecondaryThresholdSensor, mSecondarySafe);
-    }
-
-    /**
-     * Convenience class allowing for briefly checking the proximity sensor.
-     */
-    public static class ProximityCheck implements Runnable {
-
-        private final ProximitySensor mSensor;
-        private final DelayableExecutor mDelayableExecutor;
-        private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
-        private final ThresholdSensor.Listener mListener;
-        private final AtomicBoolean mRegistered = new AtomicBoolean();
-
-        @Inject
-        public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
-            mSensor = sensor;
-            mSensor.setTag("prox_check");
-            mDelayableExecutor = delayableExecutor;
-            mListener = this::onProximityEvent;
-        }
-
-        /** Set a descriptive tag for the sensors registration. */
-        public void setTag(String tag) {
-            mSensor.setTag(tag);
-        }
-
-        @Override
-        public void run() {
-            unregister();
-            onProximityEvent(null);
-        }
-
-        /**
-         * Query the proximity sensor, timing out if no result.
-         */
-        public void check(long timeoutMs, Consumer<Boolean> callback) {
-            if (!mSensor.isLoaded()) {
-                callback.accept(null);
-                return;
-            }
-            mCallbacks.add(callback);
-            if (!mRegistered.getAndSet(true)) {
-                mSensor.register(mListener);
-                mDelayableExecutor.executeDelayed(this, timeoutMs);
-            }
-        }
-
-        private void unregister() {
-            mSensor.unregister(mListener);
-            mRegistered.set(false);
-        }
-
-        private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
-            mCallbacks.forEach(
-                    booleanConsumer ->
-                            booleanConsumer.accept(
-                                    proximityEvent == null ? null : proximityEvent.getBelow()));
-            mCallbacks.clear();
-            unregister();
-            mRegistered.set(false);
-        }
-    }
-
-    private void logDebug(String msg) {
-        if (DEBUG) {
-            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
-        }
-    }
+    void setSecondarySafe(boolean safe);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
new file mode 100644
index 0000000..e639313a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Wrapper around SensorManager customized for the Proximity sensor.
+ *
+ * The ProximitySensor supports the concept of a primary and a
+ * secondary hardware sensor. The primary sensor is used for a first
+ * pass check if the phone covered. When triggered, it then checks
+ * the secondary sensor for confirmation (if there is one). It does
+ * not send a proximity event until the secondary sensor confirms (or
+ * rejects) the reading. The secondary sensor is, in fact, the source
+ * of truth.
+ *
+ * This is necessary as sometimes keeping the secondary sensor on for
+ * extends periods is undesirable. It may, however, result in increased
+ * latency for proximity readings.
+ *
+ * Phones should configure this via a config.xml overlay. If no
+ * proximity sensor is set (primary or secondary) we fall back to the
+ * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in
+ * config.xml, that will be used as the primary sensor. If
+ * proximity_sensor_secondary_type is set, that will function as the
+ * secondary sensor. If no secondary is set, only the primary will be
+ * used.
+ */
+class ProximitySensorImpl implements ProximitySensor {
+    private static final String TAG = "ProxSensor";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final long SECONDARY_PING_INTERVAL_MS = 5000;
+
+    ThresholdSensor mPrimaryThresholdSensor;
+    ThresholdSensor mSecondaryThresholdSensor;
+    private final DelayableExecutor mDelayableExecutor;
+    private final Execution mExecution;
+    private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
+    private String mTag = null;
+    @VisibleForTesting protected boolean mPaused;
+    private ThresholdSensorEvent mLastPrimaryEvent;
+    @VisibleForTesting
+    ThresholdSensorEvent mLastEvent;
+    private boolean mRegistered;
+    private final AtomicBoolean mAlerting = new AtomicBoolean();
+    private Runnable mCancelSecondaryRunnable;
+    boolean mInitializedListeners = false;
+    private boolean mSecondarySafe = false; // safe to skip primary sensor check and use secondary
+    protected @DevicePostureController.DevicePostureInt int mDevicePosture;
+
+    final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
+
+    final ThresholdSensor.Listener mSecondaryEventListener =
+            new ThresholdSensor.Listener() {
+        @Override
+        public void onThresholdCrossed(ThresholdSensorEvent event) {
+            // If we no longer have a "below" signal and the secondary sensor is not
+            // considered "safe", then we need to turn it off.
+            if (!mSecondarySafe
+                    && (mLastPrimaryEvent == null
+                    || !mLastPrimaryEvent.getBelow()
+                    || !event.getBelow())) {
+                chooseSensor();
+                if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
+                    // Only check the secondary as long as the primary thinks we're near.
+                    if (mCancelSecondaryRunnable != null) {
+                        mCancelSecondaryRunnable.run();
+                        mCancelSecondaryRunnable = null;
+                    }
+                    return;
+                } else {
+                    // Check this sensor again in a moment.
+                    mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+                        // This is safe because we know that mSecondaryThresholdSensor
+                        // is loaded, otherwise we wouldn't be here.
+                        mPrimaryThresholdSensor.pause();
+                        mSecondaryThresholdSensor.resume();
+                    },
+                        SECONDARY_PING_INTERVAL_MS);
+                }
+            }
+            logDebug("Secondary sensor event: " + event.getBelow() + ".");
+
+            if (!mPaused) {
+                onSensorEvent(event);
+            }
+        }
+    };
+
+    @Inject
+    ProximitySensorImpl(
+            @PrimaryProxSensor ThresholdSensor primary,
+            @SecondaryProxSensor ThresholdSensor  secondary,
+            @Main DelayableExecutor delayableExecutor,
+            Execution execution) {
+        mPrimaryThresholdSensor = primary;
+        mSecondaryThresholdSensor = secondary;
+        mDelayableExecutor = delayableExecutor;
+        mExecution = execution;
+    }
+
+    @Override
+    public void setTag(String tag) {
+        mTag = tag;
+        mPrimaryThresholdSensor.setTag(tag + ":primary");
+        mSecondaryThresholdSensor.setTag(tag + ":secondary");
+    }
+
+    @Override
+    public void setDelay(int delay) {
+        mExecution.assertIsMainThread();
+        mPrimaryThresholdSensor.setDelay(delay);
+        mSecondaryThresholdSensor.setDelay(delay);
+    }
+
+    /**
+     * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+     */
+    @Override
+    public void pause() {
+        mExecution.assertIsMainThread();
+        mPaused = true;
+        unregisterInternal();
+    }
+
+    /**
+     * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+     */
+    @Override
+    public void resume() {
+        mExecution.assertIsMainThread();
+        mPaused = false;
+        registerInternal();
+    }
+
+    @Override
+    public void setSecondarySafe(boolean safe) {
+        mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+        chooseSensor();
+    }
+
+    /**
+     * Returns true if we are registered with the SensorManager.
+     */
+    @Override
+    public boolean isRegistered() {
+        return mRegistered;
+    }
+
+    /**
+     * Returns {@code false} if a Proximity sensor is not available.
+     */
+    @Override
+    public boolean isLoaded() {
+        return mPrimaryThresholdSensor.isLoaded();
+    }
+
+    /**
+     * Add a listener.
+     *
+     * Registers itself with the {@link SensorManager} if this is the first listener
+     * added. If the ProximitySensor is paused, it will be registered when resumed.
+     */
+    @Override
+    public void register(ThresholdSensor.Listener listener) {
+        mExecution.assertIsMainThread();
+        if (!isLoaded()) {
+            return;
+        }
+
+        if (mListeners.contains(listener)) {
+            logDebug("ProxListener registered multiple times: " + listener);
+        } else {
+            mListeners.add(listener);
+        }
+        registerInternal();
+    }
+
+    protected void registerInternal() {
+        mExecution.assertIsMainThread();
+        if (mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        if (!mInitializedListeners) {
+            mPrimaryThresholdSensor.pause();
+            mSecondaryThresholdSensor.pause();
+            mPrimaryThresholdSensor.register(mPrimaryEventListener);
+            mSecondaryThresholdSensor.register(mSecondaryEventListener);
+            mInitializedListeners = true;
+        }
+
+        mRegistered = true;
+        chooseSensor();
+    }
+
+    private void chooseSensor() {
+        mExecution.assertIsMainThread();
+        if (!mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        if (mSecondarySafe) {
+            mSecondaryThresholdSensor.resume();
+            mPrimaryThresholdSensor.pause();
+        } else {
+            mPrimaryThresholdSensor.resume();
+            mSecondaryThresholdSensor.pause();
+        }
+    }
+
+    /**
+     * Remove a listener.
+     *
+     * If all listeners are removed from an instance of this class,
+     * it will unregister itself with the SensorManager.
+     */
+    @Override
+    public void unregister(ThresholdSensor.Listener listener) {
+        mExecution.assertIsMainThread();
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            unregisterInternal();
+        }
+    }
+
+    @Override
+    public String getName() {
+        return mPrimaryThresholdSensor.getName();
+    }
+
+    @Override
+    public String getType() {
+        return mPrimaryThresholdSensor.getType();
+    }
+
+    protected void unregisterInternal() {
+        mExecution.assertIsMainThread();
+        if (!mRegistered) {
+            return;
+        }
+        logDebug("unregistering sensor listener");
+        mPrimaryThresholdSensor.pause();
+        mSecondaryThresholdSensor.pause();
+        if (mCancelSecondaryRunnable != null) {
+            mCancelSecondaryRunnable.run();
+            mCancelSecondaryRunnable = null;
+        }
+        mLastPrimaryEvent = null;  // Forget what we know.
+        mLastEvent = null;
+        mRegistered = false;
+    }
+
+    @Override
+    public Boolean isNear() {
+        return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
+    }
+
+    @Override
+    public void alertListeners() {
+        mExecution.assertIsMainThread();
+        if (mAlerting.getAndSet(true)) {
+            return;
+        }
+        if (mLastEvent != null) {
+            ThresholdSensorEvent lastEvent = mLastEvent;  // Listeners can null out mLastEvent.
+            List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
+            listeners.forEach(proximitySensorListener ->
+                    proximitySensorListener.onThresholdCrossed(lastEvent));
+        }
+
+        mAlerting.set(false);
+    }
+
+    private void onPrimarySensorEvent(ThresholdSensorEvent event) {
+        mExecution.assertIsMainThread();
+        if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
+            return;
+        }
+
+        mLastPrimaryEvent = event;
+
+        if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
+            logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
+                    + ". Checking secondary.");
+            if (mCancelSecondaryRunnable == null) {
+                mSecondaryThresholdSensor.resume();
+            }
+            return;
+        }
+
+
+        if (!mSecondaryThresholdSensor.isLoaded()) {  // No secondary
+            logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
+            onSensorEvent(event);
+        } else if (event.getBelow()) {  // Covered? Check secondary.
+            logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
+            if (mCancelSecondaryRunnable != null) {
+                mCancelSecondaryRunnable.run();
+            }
+            mSecondaryThresholdSensor.resume();
+        } else {  // Uncovered. Report immediately.
+            onSensorEvent(event);
+        }
+    }
+
+    private void onSensorEvent(ThresholdSensorEvent event) {
+        mExecution.assertIsMainThread();
+        if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
+            return;
+        }
+
+        if (!mSecondarySafe && !event.getBelow()) {
+            chooseSensor();
+        }
+
+        mLastEvent = event;
+        alertListeners();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{registered=%s, paused=%s, near=%s, posture=%s, primarySensor=%s, "
+                + "secondarySensor=%s secondarySafe=%s}",
+                isRegistered(), mPaused, isNear(), mDevicePosture, mPrimaryThresholdSensor,
+                mSecondaryThresholdSensor, mSecondarySafe);
+    }
+
+    void logDebug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
index 11e7df8..0be6068c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
@@ -16,11 +16,23 @@
 
 package com.android.systemui.util.sensors;
 
+import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -31,8 +43,10 @@
 public class SensorModule {
     @Provides
     @PrimaryProxSensor
-    static ThresholdSensor providePrimaryProxSensor(SensorManager sensorManager,
-            ThresholdSensorImpl.Builder thresholdSensorBuilder) {
+    static ThresholdSensor providePrimaryProximitySensor(
+            SensorManager sensorManager,
+            ThresholdSensorImpl.Builder thresholdSensorBuilder
+    ) {
         try {
             return thresholdSensorBuilder
                     .setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL)
@@ -52,8 +66,9 @@
 
     @Provides
     @SecondaryProxSensor
-    static ThresholdSensor provideSecondaryProxSensor(
-            ThresholdSensorImpl.Builder thresholdSensorBuilder) {
+    static ThresholdSensor provideSecondaryProximitySensor(
+            ThresholdSensorImpl.Builder thresholdSensorBuilder
+    ) {
         try {
             return thresholdSensorBuilder
                     .setSensorResourceId(R.string.proximity_sensor_secondary_type, true)
@@ -64,4 +79,153 @@
             return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build();
         }
     }
+
+    /**
+     * If postures are supported on the device, returns a posture dependent proximity sensor
+     * which switches proximity sensors based on the current posture.
+     *
+     * If postures are not supported the regular {@link ProximitySensorImpl} will be returned.
+     */
+    @Provides
+    static ProximitySensor provideProximitySensor(
+            @Main Resources resources,
+            Lazy<PostureDependentProximitySensor> postureDependentProximitySensorProvider,
+            Lazy<ProximitySensorImpl> proximitySensorProvider
+    ) {
+        if (hasPostureSupport(
+                resources.getStringArray(R.array.proximity_sensor_posture_mapping))) {
+            return postureDependentProximitySensorProvider.get();
+        } else  {
+            return proximitySensorProvider.get();
+        }
+    }
+
+    @Provides
+    static ProximityCheck provideProximityCheck(
+            ProximitySensor proximitySensor,
+            @Main DelayableExecutor delayableExecutor
+    ) {
+        return new ProximityCheck(
+                proximitySensor,
+                delayableExecutor
+        );
+    }
+
+    @Provides
+    @PrimaryProxSensor
+    @NonNull
+    static ThresholdSensor[] providePostureToProximitySensorMapping(
+            ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+            @Main Resources resources
+    ) {
+        return createPostureToSensorMapping(
+                thresholdSensorImplBuilderFactory,
+                resources.getStringArray(R.array.proximity_sensor_posture_mapping),
+                R.dimen.proximity_sensor_threshold,
+                R.dimen.proximity_sensor_threshold_latch
+        );
+    }
+
+    @Provides
+    @SecondaryProxSensor
+    @NonNull
+    static ThresholdSensor[] providePostureToSecondaryProximitySensorMapping(
+            ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+            @Main Resources resources
+    ) {
+        return createPostureToSensorMapping(
+                thresholdSensorImplBuilderFactory,
+                resources.getStringArray(R.array.proximity_sensor_secondary_posture_mapping),
+                R.dimen.proximity_sensor_secondary_threshold,
+                R.dimen.proximity_sensor_secondary_threshold_latch
+        );
+    }
+
+    /**
+     * Builds sensors to use per posture.
+     *
+     * @param sensorTypes an array where the index represents
+     *                    {@link DevicePostureController.DevicePostureInt} and the value
+     *                    at the given index is the sensorType. Empty values represent
+     *                    no sensor desired.
+     * @param proximitySensorThresholdResourceId resource id for the threshold for all sensor
+     *                                           postures. This currently only supports one value.
+     *                                           This needs to be updated in the future if postures
+     *                                           use different sensors with differing thresholds.
+     * @param proximitySensorThresholdLatchResourceId resource id for the latch for all sensor
+     *                                                postures. This currently only supports one
+     *                                                value. This needs to be updated in the future
+     *                                                if postures use different sensors with
+     *                                                differing latches.
+     * @return an array where the index represents the device posture
+     * {@link DevicePostureController.DevicePostureInt} and the value at the index is the sensor to
+     * use when the device is in that posture.
+     */
+    @NonNull
+    private static ThresholdSensor[] createPostureToSensorMapping(
+            ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+            String[] sensorTypes,
+            int proximitySensorThresholdResourceId,
+            int proximitySensorThresholdLatchResourceId
+
+    ) {
+        ThresholdSensor noProxSensor = thresholdSensorImplBuilderFactory
+                .createBuilder()
+                .setSensor(null).setThresholdValue(0).build();
+
+
+        // length and index of sensorMap correspond to DevicePostureController.DevicePostureInt:
+        final ThresholdSensor[] sensorMap =
+                new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+        for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) {
+            sensorMap[i] = noProxSensor;
+        }
+
+        if (!hasPostureSupport(sensorTypes)) {
+            Log.e("SensorModule", "config doesn't support postures,"
+                    + " but attempting to retrieve proxSensorMapping");
+            return sensorMap;
+        }
+
+        // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
+        // postures
+        Map<String, ThresholdSensor> typeToSensorMap = new HashMap<>();
+        for (int i = 0; i < sensorTypes.length; i++) {
+            try {
+                final String sensorType = sensorTypes[i];
+                if (typeToSensorMap.containsKey(sensorType)) {
+                    sensorMap[i] = typeToSensorMap.get(sensorType);
+                } else {
+                    sensorMap[i] = thresholdSensorImplBuilderFactory
+                            .createBuilder()
+                            .setSensorType(sensorTypes[i], true)
+                            .setThresholdResourceId(proximitySensorThresholdResourceId)
+                            .setThresholdLatchResourceId(proximitySensorThresholdLatchResourceId)
+                            .build();
+                    typeToSensorMap.put(sensorType, sensorMap[i]);
+                }
+            } catch (IllegalStateException e) {
+                // do nothing, sensor at this posture is already set to noProxSensor
+            }
+        }
+
+        return sensorMap;
+    }
+
+    /**
+     * Returns true if there's at least one non-empty sensor type in the given array.
+     */
+    private static boolean hasPostureSupport(String[] postureToSensorTypeMapping) {
+        if (postureToSensorTypeMapping == null || postureToSensorTypeMapping.length == 0) {
+            return false;
+        }
+
+        for (String sensorType : postureToSensorTypeMapping) {
+            if (!TextUtils.isEmpty(sensorType)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
index 363a734..d81a8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.util.sensors;
 
-import java.util.Locale;
-
 /**
  * A wrapper class for sensors that have a boolean state - above/below.
  */
@@ -77,6 +75,16 @@
     void unregister(Listener listener);
 
     /**
+     * Name of the sensor.
+     */
+    String getName();
+
+    /**
+     * Type of the sensor.
+     */
+    String getType();
+
+    /**
      * Interface for listening to events on {@link ThresholdSensor}
      */
     interface Listener {
@@ -85,34 +93,4 @@
          */
         void onThresholdCrossed(ThresholdSensorEvent event);
     }
-
-    /**
-     * Returned when the below/above state of a {@link ThresholdSensor} changes.
-     */
-    class ThresholdSensorEvent {
-        private final boolean mBelow;
-        private final long mTimestampNs;
-
-        public ThresholdSensorEvent(boolean below, long timestampNs) {
-            mBelow = below;
-            mTimestampNs = timestampNs;
-        }
-
-        public boolean getBelow() {
-            return mBelow;
-        }
-
-        public long getTimestampNs() {
-            return mTimestampNs;
-        }
-
-        public long getTimestampMs() {
-            return mTimestampNs / 1000000;
-        }
-
-        @Override
-        public String toString() {
-            return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java
new file mode 100644
index 0000000..afce09f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import java.util.Locale;
+
+/**
+ * Returned when the below/above state of a {@link ThresholdSensor} changes.
+ */
+public class ThresholdSensorEvent {
+    private final boolean mBelow;
+    private final long mTimestampNs;
+
+    public ThresholdSensorEvent(boolean below, long timestampNs) {
+        mBelow = below;
+        mTimestampNs = timestampNs;
+    }
+
+    public boolean getBelow() {
+        return mBelow;
+    }
+
+    public long getTimestampNs() {
+        return mTimestampNs;
+    }
+
+    public long getTimestampMs() {
+        return mTimestampNs / 1000000;
+    }
+
+    @Override
+    public String toString() {
+        return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index d10cf9b..a9086b1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -32,7 +33,10 @@
 
 import javax.inject.Inject;
 
-class ThresholdSensorImpl implements ThresholdSensor {
+/**
+ * Sensor that will only trigger beyond some lower and upper threshold.
+ */
+public class ThresholdSensorImpl implements ThresholdSensor {
     private static final String TAG = "ThresholdSensor";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -198,6 +202,15 @@
         alertListenersInternal(belowThreshold, timestampNs);
     }
 
+    @Override
+    public String getName() {
+        return mSensor != null ? mSensor.getName() : null;
+    }
+
+    @Override
+    public String getType() {
+        return mSensor != null ? mSensor.getStringType() : null;
+    }
 
     @Override
     public String toString() {
@@ -211,7 +224,12 @@
         }
     }
 
-    static class Builder {
+    /**
+     * Use to build a ThresholdSensor. Should only be used once per sensor built, since
+     * parameters are not reset after calls to build(). For ease of retrievingnew Builders, use
+     * {@link BuilderFactory}.
+     */
+    public static class Builder {
         private final Resources mResources;
         private final AsyncSensorManager mSensorManager;
         private final Execution mExecution;
@@ -318,7 +336,7 @@
 
         @VisibleForTesting
         Sensor findSensorByType(String sensorType, boolean requireWakeUp) {
-            if (sensorType.isEmpty()) {
+            if (TextUtils.isEmpty(sensorType)) {
                 return null;
             }
 
@@ -336,4 +354,29 @@
             return sensor;
         }
     }
+
+    /**
+     * Factory that creates a new ThresholdSensorImpl.Builder. In general, Builders should not be
+     * reused after creating a ThresholdSensor or else there may be default threshold and sensor
+     * values set from the previous built sensor.
+     */
+    public static class BuilderFactory {
+        private final Resources mResources;
+        private final AsyncSensorManager mSensorManager;
+        private final Execution mExecution;
+
+        @Inject
+        BuilderFactory(
+                @Main Resources resources,
+                AsyncSensorManager sensorManager,
+                Execution execution) {
+            mResources = resources;
+            mSensorManager = sensorManager;
+            mExecution = execution;
+        }
+
+        ThresholdSensorImpl.Builder createBuilder() {
+            return new Builder(mResources, mSensorManager, mExecution);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index f420a85..ff26310 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -28,7 +28,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
-import com.android.wm.shell.FullscreenTaskListener;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.ShellCommandHandlerImpl;
@@ -57,6 +56,8 @@
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -79,6 +80,7 @@
 import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
 
 import java.util.Optional;
 
@@ -218,8 +220,28 @@
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        return new FullscreenTaskListener(syncQueue);
+    static FullscreenTaskListener provideFullscreenTaskListener(
+            SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> controller) {
+        return new FullscreenTaskListener(syncQueue, controller);
+    }
+
+    //
+    // Unfold transition
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
+            Context context,
+            Optional<ShellUnfoldProgressProvider> progressProvider,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            DisplayInsetsController displayInsetsController,
+            @ShellMainThread ShellExecutor mainExecutor
+    ) {
+        return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+                new FullscreenUnfoldController(context, mainExecutor,
+                        shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer,
+                        displayInsetsController));
     }
 
     //
@@ -474,6 +496,7 @@
             Optional<AppPairsController> appPairsOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
+            Optional<FullscreenUnfoldController> appUnfoldTransitionController,
             Optional<Optional<FreeformTaskListener>> freeformTaskListener,
             Transitions transitions,
             StartingWindowController startingWindow,
@@ -489,6 +512,7 @@
                 appPairsOptional,
                 pipTouchHandlerOptional,
                 fullscreenTaskListener,
+                appUnfoldTransitionController,
                 freeformTaskListener,
                 transitions,
                 startingWindow,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index b688fcc..31fa3f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -53,7 +53,8 @@
 import com.android.systemui.util.sensors.FakeProximitySensor;
 import com.android.systemui.util.sensors.FakeSensorManager;
 import com.android.systemui.util.sensors.FakeThresholdSensor;
-import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.sensors.ProximityCheck;
+import com.android.systemui.util.sensors.ThresholdSensorEvent;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -80,7 +81,7 @@
     @Mock
     private DockManager mDockManager;
     @Mock
-    private ProximitySensor.ProximityCheck mProximityCheck;
+    private ProximityCheck mProximityCheck;
     @Mock
     private AuthController mAuthController;
     @Mock
@@ -136,14 +137,14 @@
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
-        mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 1));
+        mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
         captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
         mProximitySensor.alertListeners();
 
         verify(mMachine, never()).requestState(any());
         verify(mMachine, never()).requestPulse(anyInt());
 
-        mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(false, 2));
+        mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
         mProximitySensor.alertListeners();
         waitForSensorManager();
         captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 07ebaea..baed694 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 
@@ -99,6 +100,7 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private TunerService mTunerService;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@@ -152,6 +154,7 @@
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
                 mTunerService,
+                mDeviceProvisionedController,
                 mDynamicPrivacyController,
                 mConfigurationController,
                 mSysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 4e76b16..9f42fa4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -280,6 +280,8 @@
     @Test
     public void testUpdateFooter_noNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
+
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
@@ -289,6 +291,7 @@
     @Test
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         when(row.canViewBeDismissed()).thenReturn(true);
@@ -308,6 +311,7 @@
     @Test
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
         when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
@@ -321,8 +325,25 @@
     }
 
     @Test
+    public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
+        setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(false);
+
+        when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+                .thenReturn(true);
+        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
+
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(false, true, true);
+    }
+
+    @Test
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         when(row.canViewBeDismissed()).thenReturn(false);
@@ -341,6 +362,8 @@
 
     @Test
     public void testUpdateFooter_atEnd() {
+        mStackScroller.setCurrentUserSetup(true);
+
         // add footer
         mStackScroller.inflateFooterView();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 50947ab..22cf744 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -19,14 +19,21 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.FakeExecution;
 
-public class FakeProximitySensor extends ProximitySensor {
+public class FakeProximitySensor extends ProximitySensorImpl {
     private boolean mAvailable;
     private boolean mRegistered;
 
-    public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary,
-            DelayableExecutor delayableExecutor) {
-        super(primary, secondary == null ? new FakeThresholdSensor() : secondary,
-                delayableExecutor, new FakeExecution());
+    public FakeProximitySensor(
+            ThresholdSensor primary,
+            ThresholdSensor secondary,
+            DelayableExecutor delayableExecutor
+    ) {
+        super(
+                primary,
+                secondary == null ? new FakeThresholdSensor() : secondary,
+                delayableExecutor,
+                new FakeExecution()
+        );
         mAvailable = true;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
index d9f9789..0d4a6c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
@@ -59,6 +59,16 @@
         mListeners.remove(listener);
     }
 
+    @Override
+    public String getName() {
+        return "FakeThresholdSensorName";
+    }
+
+    @Override
+    public String getType() {
+        return "FakeThresholdSensorType";
+    }
+
     public void setLoaded(boolean loaded) {
         mIsLoaded = loaded;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
new file mode 100644
index 0000000..075f393
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.FakeExecution;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PostureDependentProximitySensorTest extends SysuiTestCase {
+    @Mock private Resources mResources;
+    @Mock private DevicePostureController mDevicePostureController;
+    @Mock private AsyncSensorManager mSensorManager;
+
+    @Captor private ArgumentCaptor<DevicePostureController.Callback> mPostureListenerCaptor =
+            ArgumentCaptor.forClass(DevicePostureController.Callback.class);
+    private DevicePostureController.Callback mPostureListener;
+
+    private PostureDependentProximitySensor mProximitySensor;
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        allowTestableLooperAsMainThread();
+
+        mProximitySensor = new PostureDependentProximitySensor(
+                new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
+                new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
+                mFakeExecutor,
+                new FakeExecution(),
+                mDevicePostureController
+        );
+    }
+
+    @Test
+    public void testPostureChangeListenerAdded() {
+        capturePostureListener();
+    }
+
+    @Test
+    public void testPostureChangeListenerUpdatesPosture() {
+        // GIVEN posture listener is registered
+        capturePostureListener();
+
+        // WHEN the posture changes to DEVICE_POSTURE_OPENED
+        mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+        // THEN device posture is updated to DEVICE_POSTURE_OPENED
+        assertEquals(DevicePostureController.DEVICE_POSTURE_OPENED,
+                mProximitySensor.mDevicePosture);
+
+        // WHEN the posture changes to DEVICE_POSTURE_CLOSED
+        mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        // THEN device posture is updated to DEVICE_POSTURE_CLOSED
+        assertEquals(DevicePostureController.DEVICE_POSTURE_CLOSED,
+                mProximitySensor.mDevicePosture);
+
+        // WHEN the posture changes to DEVICE_POSTURE_FLIPPED
+        mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED);
+
+        // THEN device posture is updated to DEVICE_POSTURE_FLIPPED
+        assertEquals(DevicePostureController.DEVICE_POSTURE_FLIPPED,
+                mProximitySensor.mDevicePosture);
+
+        // WHEN the posture changes to DEVICE_POSTURE_HALF_OPENED
+        mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+
+        // THEN device posture is updated to DEVICE_POSTURE_HALF_OPENED
+        assertEquals(DevicePostureController.DEVICE_POSTURE_HALF_OPENED,
+                mProximitySensor.mDevicePosture);
+    }
+
+    private void capturePostureListener() {
+        verify(mDevicePostureController).addCallback(mPostureListenerCaptor.capture());
+        mPostureListener = mPostureListenerCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 242fe9f..19dbf9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -49,7 +49,7 @@
 
     private TestableCallback mTestableCallback = new TestableCallback();
 
-    private ProximitySensor.ProximityCheck mProximityCheck;
+    private ProximityCheck mProximityCheck;
 
     @Before
     public void setUp() throws Exception {
@@ -58,7 +58,7 @@
         thresholdSensor.setLoaded(true);
         mFakeProximitySensor = new FakeProximitySensor(thresholdSensor, null, mFakeExecutor);
 
-        mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor);
+        mProximityCheck = new ProximityCheck(mFakeProximitySensor, mFakeExecutor);
     }
 
     @Test
@@ -67,7 +67,7 @@
 
         assertNull(mTestableCallback.mLastResult);
 
-        mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+        mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
         mFakeProximitySensor.alertListeners();
 
         assertTrue(mTestableCallback.mLastResult);
@@ -103,7 +103,7 @@
 
         mProximityCheck.check(100, mTestableCallback);
 
-        mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+        mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
         mFakeProximitySensor.alertListeners();
 
         assertThat(mTestableCallback.mLastResult).isNotNull();
@@ -123,7 +123,7 @@
 
         assertNull(mTestableCallback.mLastResult);
 
-        mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+        mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
         mFakeProximitySensor.alertListeners();
 
         assertTrue(mTestableCallback.mLastResult);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
index 0e9d96c..5e75578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
@@ -42,7 +42,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class ProximitySensorDualTest extends SysuiTestCase {
+public class ProximitySensorImplDualTest extends SysuiTestCase {
     private ProximitySensor mProximitySensor;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThresholdSensor mThresholdSensorPrimary;
@@ -57,7 +57,7 @@
         mThresholdSensorSecondary = new FakeThresholdSensor();
         mThresholdSensorSecondary.setLoaded(true);
 
-        mProximitySensor = new ProximitySensor(
+        mProximitySensor = new ProximitySensorImpl(
                 mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor,
                 new FakeExecution());
     }
@@ -430,11 +430,11 @@
     }
 
     private static class TestableListener implements ThresholdSensor.Listener {
-        ThresholdSensor.ThresholdSensorEvent mLastEvent;
+        ThresholdSensorEvent mLastEvent;
         int mCallCount = 0;
 
         @Override
-        public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+        public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
             mLastEvent = proximityEvent;
             mCallCount++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
index 6c6d355..752cd32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
@@ -42,7 +42,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class ProximitySensorSingleTest extends SysuiTestCase {
+public class ProximitySensorImplSingleTest extends SysuiTestCase {
     private ProximitySensor mProximitySensor;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThresholdSensor mThresholdSensor;
@@ -54,7 +54,7 @@
         mThresholdSensor = new FakeThresholdSensor();
         mThresholdSensor.setLoaded(true);
 
-        mProximitySensor = new ProximitySensor(
+        mProximitySensor = new ProximitySensorImpl(
                 mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution());
     }
 
@@ -215,7 +215,7 @@
     public void testPreventRecursiveAlert() {
         TestableListener listenerA = new TestableListener() {
             @Override
-            public void onThresholdCrossed(ProximitySensor.ThresholdSensorEvent proximityEvent) {
+            public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
                 super.onThresholdCrossed(proximityEvent);
                 if (mCallCount < 2) {
                     mProximitySensor.alertListeners();
@@ -231,11 +231,11 @@
     }
 
     private static class TestableListener implements ThresholdSensor.Listener {
-        ThresholdSensor.ThresholdSensorEvent mLastEvent;
+        ThresholdSensorEvent mLastEvent;
         int mCallCount = 0;
 
         @Override
-        public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+        public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
             mLastEvent = proximityEvent;
             mCallCount++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index 125063a..b10f16c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -380,7 +380,7 @@
         int mCallCount;
 
         @Override
-        public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
+        public void onThresholdCrossed(ThresholdSensorEvent event) {
             mBelow = event.getBelow();
             mTimestampNs = event.getTimestampNs();
             mCallCount++;
diff --git a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java
index 9801407..5080ca2 100644
--- a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java
+++ b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java
@@ -95,7 +95,7 @@
         filter.addAction(ACTION_A11Y_SETTINGS);
         filter.addAction(ACTION_DISMISS_NOTIFICATION);
         mContext.registerReceiver(mNotificationController, filter,
-                Manifest.permission.MANAGE_ACCESSIBILITY, mMainHandler);
+                Manifest.permission.MANAGE_ACCESSIBILITY, mMainHandler, Context.RECEIVER_EXPORTED);
 
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 012c5b1..5e2449d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -209,7 +209,8 @@
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
+        context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler(),
+                Context.RECEIVER_NOT_EXPORTED);
 
         mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
                 com.android.internal.R.string.config_defaultAugmentedAutofillService);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e3b7fdd..7a38a02 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -145,6 +145,7 @@
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
@@ -156,6 +157,15 @@
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
 
+    private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
+    static {
+        final Map<String, String> map = new ArrayMap<>();
+        map.put(AssociationRequest.DEVICE_PROFILE_WATCH,
+                Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+
+        DEVICE_PROFILE_TO_PERMISSION = Collections.unmodifiableMap(map);
+    }
+
     private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
             CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
             ".CompanionDeviceDiscoveryService");
@@ -429,7 +439,8 @@
             checkCallerIsSystemOr(callingPackage);
             int userId = getCallingUserId();
             checkUsesFeature(callingPackage, userId);
-            checkProfilePermissions(request);
+            final String deviceProfile = request.getDeviceProfile();
+            validateDeviceProfileAndCheckPermission(deviceProfile);
 
             mFindDeviceCallback = callback;
             mRequest = request;
@@ -442,13 +453,8 @@
             }
             callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0);
 
-            AndroidFuture<String> fetchProfileDescription =
-                    request.getDeviceProfile() == null
-                            ? AndroidFuture.completedFuture(null)
-                            : getDeviceProfilePermissionDescription(
-                                    request.getDeviceProfile());
-
-            mOngoingDeviceDiscovery = fetchProfileDescription.thenComposeAsync(description -> {
+            mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
+                    .thenComposeAsync(description -> {
                 Slog.d(LOG_TAG, "fetchProfileDescription done: " + description);
 
                 request.setDeviceProfilePrivilegesDescription(description);
@@ -464,8 +470,7 @@
             }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
                 if (err == null) {
                     Association association = new Association(userId, deviceAddress, callingPackage,
-                            mRequest.getDeviceProfile(), false,
-                            System.currentTimeMillis());
+                            deviceProfile, false, System.currentTimeMillis());
                     addAssociation(association, userId);
                 } else {
                     Slog.e(LOG_TAG, "Failed to discover device(s)", err);
@@ -542,18 +547,18 @@
             }
         }
 
-        private void checkProfilePermissions(AssociationRequest request) {
-            checkProfilePermission(request,
-                    AssociationRequest.DEVICE_PROFILE_WATCH,
-                    Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
-        }
+        private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
+            // Device profile can be null.
+            if (deviceProfile == null) return;
 
-        private void checkProfilePermission(
-                AssociationRequest request, String profile, String permission) {
-            if (profile.equals(request.getDeviceProfile())
-                    && getContext().checkCallingOrSelfPermission(permission)
-                            != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Using " + profile + " requires " + permission);
+            if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
+                throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
+            }
+
+            final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
+            if (getContext().checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+                throw new SecurityException("Application must hold " + permission + " to associate "
+                        + "with a device with " + deviceProfile + " profile.");
             }
         }
 
@@ -1517,8 +1522,14 @@
         return result;
     }
 
-    private AndroidFuture<String> getDeviceProfilePermissionDescription(String deviceProfile) {
-        AndroidFuture<String> result = new AndroidFuture<>();
+    @NonNull
+    private AndroidFuture<String> getDeviceProfilePermissionDescription(
+            @Nullable String deviceProfile) {
+        if (deviceProfile == null) {
+            return AndroidFuture.completedFuture(null);
+        }
+
+        final AndroidFuture<String> result = new AndroidFuture<>();
         mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
                 deviceProfile, FgThread.getExecutor(), desc -> {
                         try {
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 57ce342..19f8f38 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -347,7 +347,7 @@
                             intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
                 }
             }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
-                    MANAGE_SENSOR_PRIVACY, null);
+                    MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED);
             mUserManagerInternal.addUserRestrictionsListener(this);
         }
 
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index a03425c..b48e21e 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -681,7 +681,7 @@
         intentFilter = new IntentFilter();
         intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
         mUserAllContext.registerReceiver(
-                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler, Context.RECEIVER_EXPORTED);
     }
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 46efa3c..e7b078a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -143,6 +143,8 @@
     );
 
     public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
+            "android.hardware.biometrics.face.IFace/",
+            "android.hardware.biometrics.fingerprint.IFingerprint/",
             "android.hardware.light.ILights/",
             "android.hardware.power.stats.IPowerStats/",
     };
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6a63254..5330252 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5091,14 +5091,19 @@
     @Override
     public PendingIntentInfo getInfoForIntentSender(IIntentSender sender) {
         if (sender instanceof PendingIntentRecord) {
-            PendingIntentRecord res = (PendingIntentRecord) sender;
+            final PendingIntentRecord res = (PendingIntentRecord) sender;
+            final String packageName = res.key.packageName;
+            final int uid = res.uid;
+            final boolean shouldFilter = getPackageManagerInternal().filterAppAccess(
+                    packageName, Binder.getCallingUid(), UserHandle.getUserId(uid));
             return new PendingIntentInfo(
-                    res.key.packageName,
-                    res.uid,
+                    shouldFilter ? null : packageName,
+                    shouldFilter ? INVALID_UID : uid,
                     (res.key.flags & PendingIntent.FLAG_IMMUTABLE) != 0,
                     res.key.type);
         } else {
-            return new PendingIntentInfo(null, -1, false, ActivityManager.INTENT_SENDER_UNKNOWN);
+            return new PendingIntentInfo(null, INVALID_UID, false,
+                    ActivityManager.INTENT_SENDER_UNKNOWN);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 289396e..3aa7ab9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1122,7 +1122,8 @@
         intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
         intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
 
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
+        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
+                Context.RECEIVER_EXPORTED);
 
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index dc5dace9..a0befea 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -79,7 +79,7 @@
         mLockoutReceiver = new LockoutReceiver();
 
         context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
-                RESET_FINGERPRINT_LOCKOUT, null /* handler */);
+                RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
     }
 
     // Attempt counter should only be cleared when Keyguard goes away or when
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index d6c28e2..87b9e6a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
+import static android.os.PowerManager.BRIGHTNESS_INVALID;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +41,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -61,6 +63,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.utils.AmbientFilter;
@@ -153,7 +156,7 @@
         mAppRequestObserver = new AppRequestObserver();
         mSettingsObserver = new SettingsObserver(context, handler);
         mDisplayObserver = new DisplayObserver(context, handler);
-        mBrightnessObserver = new BrightnessObserver(context, handler);
+        mBrightnessObserver = new BrightnessObserver(context, handler, injector);
         mUdfpsObserver = new UdfpsObserver();
         final BallotBox ballotBox = (displayId, priority, vote) -> {
             synchronized (mLock) {
@@ -1425,8 +1428,6 @@
         @Override
         public void onDisplayChanged(int displayId) {
             updateDisplayModes(displayId);
-            // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
-            mBrightnessObserver.onDisplayChanged(displayId);
         }
 
         private void updateDisplayModes(int displayId) {
@@ -1463,7 +1464,7 @@
      * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
      */
     @VisibleForTesting
-    public class BrightnessObserver extends ContentObserver {
+    public class BrightnessObserver implements DisplayManager.DisplayListener {
         private final static int LIGHT_SENSOR_RATE_MS = 250;
         private int[] mLowDisplayBrightnessThresholds;
         private int[] mLowAmbientBrightnessThresholds;
@@ -1486,6 +1487,8 @@
         private int mBrightness = -1;
 
         private final Context mContext;
+        private final Injector mInjector;
+        private final Handler mHandler;
 
         // Enable light sensor only when mShouldObserveAmbientLowChange is true or
         // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
@@ -1498,9 +1501,11 @@
         private int mRefreshRateInLowZone;
         private int mRefreshRateInHighZone;
 
-        BrightnessObserver(Context context, Handler handler) {
-            super(handler);
+        BrightnessObserver(Context context, Handler handler, Injector injector) {
             mContext = context;
+            mHandler = handler;
+            mInjector = injector;
+
             mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
                     R.array.config_brightnessThresholdsOfPeakRefreshRate);
             mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
@@ -1567,8 +1572,7 @@
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
             final ContentResolver cr = mContext.getContentResolver();
-            mBrightness = Settings.System.getIntForUser(cr,
-                    Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+            mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
             // DeviceConfig is accessible after system ready.
             int[] lowDisplayBrightnessThresholds =
@@ -1601,6 +1605,10 @@
 
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
+
+            mInjector.registerDisplayListener(this, mHandler,
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
+                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         public void setLoggingEnabled(boolean loggingEnabled) {
@@ -1716,28 +1724,30 @@
             }
         }
 
+        @Override
+        public void onDisplayAdded(int displayId) {}
+
+        @Override
+        public void onDisplayRemoved(int displayId) {}
+
+        @Override
         public void onDisplayChanged(int displayId) {
             if (displayId == Display.DEFAULT_DISPLAY) {
                 updateDefaultDisplayState();
-            }
-        }
 
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            synchronized (mLock) {
-                final ContentResolver cr = mContext.getContentResolver();
-                int brightness = Settings.System.getIntForUser(cr,
-                        Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
-                if (brightness != mBrightness) {
-                    mBrightness = brightness;
-                    onBrightnessChangedLocked();
+                // We don't support multiple display blocking zones yet, so only handle
+                // brightness changes for the default display for now.
+                int brightness = getBrightness(displayId);
+                synchronized (mLock) {
+                    if (brightness != mBrightness) {
+                        mBrightness = brightness;
+                        onBrightnessChangedLocked();
+                    }
                 }
             }
         }
 
         private void restartObserver() {
-            final ContentResolver cr = mContext.getContentResolver();
-
             if (mRefreshRateInLowZone > 0) {
                 mShouldObserveDisplayLowChange = hasValidThreshold(
                         mLowDisplayBrightnessThresholds);
@@ -1758,15 +1768,6 @@
                 mShouldObserveAmbientHighChange = false;
             }
 
-            if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
-                // Content Service does not check if an listener has already been registered.
-                // To ensure only one listener is registered, force an unregistration first.
-                mInjector.unregisterBrightnessObserver(cr, this);
-                mInjector.registerBrightnessObserver(cr, this);
-            } else {
-                mInjector.unregisterBrightnessObserver(cr, this);
-            }
-
             if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
                 Resources resources = mContext.getResources();
                 String lightSensorType = resources.getString(
@@ -1966,6 +1967,15 @@
             return mDefaultDisplayState == Display.STATE_ON;
         }
 
+        private int getBrightness(int displayId) {
+            final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
+            if (info != null) {
+                return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+            }
+
+            return BRIGHTNESS_INVALID;
+        }
+
         private final class LightSensorEventListener implements SensorEventListener {
             final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
             private float mLastSensorData;
@@ -2283,6 +2293,7 @@
         private final BallotBox mBallotBox;
         private final Handler mHandler;
         private final SparseIntArray mHbmMode = new SparseIntArray();
+        private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
         private final Injector mInjector;
         private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
         private int mRefreshRateInHbmSunlight;
@@ -2351,6 +2362,7 @@
         public void onDisplayRemoved(int displayId) {
             mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
             mHbmMode.delete(displayId);
+            mHbmActive.delete(displayId);
         }
 
         @Override
@@ -2360,12 +2372,17 @@
                 // Display no longer there. Assume we'll get an onDisplayRemoved very soon.
                 return;
             }
+
             final int hbmMode = info.highBrightnessMode;
-            if (hbmMode == mHbmMode.get(displayId)) {
+            final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
+                info.adjustedBrightness > info.highBrightnessTransitionPoint;
+            if (hbmMode == mHbmMode.get(displayId) &&
+                isHbmActive == mHbmActive.get(displayId)) {
                 // no change, ignore.
                 return;
             }
             mHbmMode.put(displayId, hbmMode);
+            mHbmActive.put(displayId, isHbmActive);
             recalculateVotesForDisplay(displayId);
         }
 
@@ -2379,28 +2396,36 @@
         }
 
         private void recalculateVotesForDisplay(int displayId) {
-            final int hbmMode = mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
             Vote vote = null;
-            if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
-                // Device resource properties take priority over DisplayDeviceConfig
-                if (mRefreshRateInHbmSunlight > 0) {
-                    vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
-                            mRefreshRateInHbmSunlight);
-                } else {
-                    final List<RefreshRateLimitation> limits =
-                        mDisplayManagerInternal.getRefreshRateLimitations(displayId);
-                    for (int i = 0; limits != null && i < limits.size(); i++) {
-                        final RefreshRateLimitation limitation = limits.get(i);
-                        if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
-                            vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
-                            break;
+            if (mHbmActive.get(displayId, false)) {
+                final int hbmMode =
+                    mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+                if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+                    // Device resource properties take priority over DisplayDeviceConfig
+                    if (mRefreshRateInHbmSunlight > 0) {
+                        vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
+                                mRefreshRateInHbmSunlight);
+                    } else {
+                        final List<RefreshRateLimitation> limits =
+                            mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+                        for (int i = 0; limits != null && i < limits.size(); i++) {
+                            final RefreshRateLimitation limitation = limits.get(i);
+                            if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
+                                vote = Vote.forRefreshRates(limitation.range.min,
+                                        limitation.range.max);
+                                break;
+                            }
                         }
                     }
+                } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
+                        mRefreshRateInHbmHdr > 0) {
+                    // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
+                    // a vote from Device properties
+                    vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+                } else {
+                    Slog.w(TAG, "Unexpected HBM mode " + hbmMode + " for display ID " + displayId);
                 }
-            }
-            if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
-                    && mRefreshRateInHbmHdr > 0) {
-                vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+
             }
             mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
         }
@@ -2408,6 +2433,7 @@
         void dumpLocked(PrintWriter pw) {
             pw.println("   HbmObserver");
             pw.println("     mHbmMode: " + mHbmMode);
+            pw.println("     mHbmActive: " + mHbmActive);
             pw.println("     mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight);
             pw.println("     mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr);
         }
@@ -2630,19 +2656,11 @@
     }
 
     interface Injector {
-        // TODO: brightnessfloat: change this to the float setting
-        Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
         Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
 
         @NonNull
         DeviceConfigInterface getDeviceConfig();
 
-        void registerBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer);
-
-        void unregisterBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer);
-
         void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
 
@@ -2672,19 +2690,6 @@
         }
 
         @Override
-        public void registerBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
-                    observer, UserHandle.USER_SYSTEM);
-        }
-
-        @Override
-        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            cr.unregisterContentObserver(observer);
-        }
-
-        @Override
         public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
             cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1063481..bf5208a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1259,10 +1259,6 @@
             putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
         }
 
-        // We save the brightness info *after* the brightness setting has been changed so that
-        // the brightness info reflects the latest value.
-        saveBrightnessInfo(getScreenBrightnessSetting());
-
         // Apply dimming by at least some minimum amount when user activity
         // timeout is about to expire.
         if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1393,6 +1389,11 @@
                         hadUserBrightnessPoint);
             }
 
+            // We save the brightness info *after* the brightness setting has been changed and
+            // adjustments made so that the brightness info reflects the latest value.
+            saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+        } else {
+            saveBrightnessInfo(getScreenBrightnessSetting());
         }
 
         // Log any changes to what is currently driving the brightness setting.
@@ -1509,18 +1510,27 @@
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
                     mCachedBrightnessInfo.brightness,
+                    mCachedBrightnessInfo.adjustedBrightness,
                     mCachedBrightnessInfo.brightnessMin,
                     mCachedBrightnessInfo.brightnessMax,
-                    mCachedBrightnessInfo.hbmMode);
+                    mCachedBrightnessInfo.hbmMode,
+                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
         }
     }
 
     private void saveBrightnessInfo(float brightness) {
+        saveBrightnessInfo(brightness, brightness);
+    }
+
+    private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
             mCachedBrightnessInfo.brightness = brightness;
+            mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
             mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
             mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
             mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+            mCachedBrightnessInfo.highBrightnessTransitionPoint =
+                mHbmController.getTransitionPoint();
         }
     }
 
@@ -2195,6 +2205,18 @@
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
+        synchronized (mCachedBrightnessInfo) {
+            pw.println("  mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
+            pw.println("  mCachedBrightnessInfo.adjustedBrightness=" +
+                    mCachedBrightnessInfo.adjustedBrightness);
+            pw.println("  mCachedBrightnessInfo.brightnessMin=" +
+                    mCachedBrightnessInfo.brightnessMin);
+            pw.println("  mCachedBrightnessInfo.brightnessMax=" +
+                    mCachedBrightnessInfo.brightnessMax);
+            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
+            pw.println("  mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
+                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
+        }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
 
@@ -2606,8 +2628,10 @@
 
     static class CachedBrightnessInfo {
         public float brightness;
+        public float adjustedBrightness;
         public float brightnessMin;
         public float brightnessMax;
         public int hbmMode;
+        public float highBrightnessTransitionPoint;
     }
 }
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2791f6a..1e1cfeb 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -59,6 +59,9 @@
 
     private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
 
+    @VisibleForTesting
+    static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
     private final float mBrightnessMin;
     private final float mBrightnessMax;
     private final Handler mHandler;
@@ -214,6 +217,14 @@
         return mHbmMode;
     }
 
+    float getTransitionPoint() {
+        if (deviceSupportsHbm()) {
+            return mHbmData.transitionPoint;
+        } else {
+            return HBM_TRANSITION_POINT_INVALID;
+        }
+    }
+
     void stop() {
         registerHdrListener(null /*displayToken*/);
         mSkinThermalStatusObserver.stopObserving();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index aba7d39..c2ed24a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -568,10 +568,8 @@
             }
         }
 
-        if (!mService.isPowerStandbyOrTransient()) {
-            addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
-                    activeSource.physicalAddress, deviceType));
-        }
+        addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
+                activeSource.physicalAddress, deviceType));
     }
 
     private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
@@ -723,14 +721,12 @@
     @ServiceThreadOnly
     void onNewAvrAdded(HdmiDeviceInfo avr) {
         assertRunOnServiceThread();
-        if (!mService.isPowerStandbyOrTransient()) {
-            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
-            if (!isDirectConnectAddress(avr.getPhysicalAddress())) {
-                startArcAction(false);
-            } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
-                    && !hasAction(SetArcTransmissionStateAction.class)) {
-                startArcAction(true);
-            }
+        addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
+        if (!isDirectConnectAddress(avr.getPhysicalAddress())) {
+            startArcAction(false);
+        } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
+                && !hasAction(SetArcTransmissionStateAction.class)) {
+            startArcAction(true);
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 075b74d..cfc32ce 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1798,7 +1798,8 @@
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
                 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
                 mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(),
-                        UserHandle.ALL, broadcastFilterForAllUsers, null, null);
+                        UserHandle.ALL, broadcastFilterForAllUsers, null, null,
+                        Context.RECEIVER_NOT_EXPORTED);
 
                 final String defaultImiId = mSettings.getSelectedInputMethod();
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index f2ccf9f..6e2fe52 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -41,7 +41,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
@@ -49,6 +48,7 @@
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.pkg.PackageUserState;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -598,7 +598,7 @@
                     0,
                     0,
                     null,
-                    new PackageUserState(),
+                    PackageUserState.DEFAULT,
                     UserHandle.getCallingUserId(),
                     null);
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 84be7f5..d37c1c8 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -976,7 +976,8 @@
 
             // listen for restrict background changes from notifications
             final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
-            mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
+            mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler,
+                    Context.RECEIVER_EXPORTED);
 
             // Listen for snooze from notifications
             mContext.registerReceiver(mSnoozeReceiver,
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index ed1f5f5..3fc4169 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -356,7 +356,7 @@
                 return false;
             }
 
-            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
+            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 75dbf04..d6b9c34 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -33,7 +33,6 @@
 import android.content.pm.InstantAppResolveInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageUserState;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -43,6 +42,7 @@
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.pkg.PackageUserState;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1549,7 +1549,7 @@
                     && (!matchExplicitlyVisibleOnly || info.isExplicitlyVisibleToInstantApp());
             final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
             // throw out filters that aren't visible to ephemeral apps
-            if (matchVisibleToInstantApp && !(componentVisible || userState.instantApp)) {
+            if (matchVisibleToInstantApp && !(componentVisible || userState.isInstantApp())) {
                 if (DEBUG) {
                     log("Filter(s) not visible to ephemeral apps"
                             + "; matchVisibleToInstantApp=" + matchVisibleToInstantApp
@@ -1563,7 +1563,7 @@
                 return null;
             }
             // throw out instant app filters if we're not explicitly requesting them
-            if (!matchInstantApp && userState.instantApp) {
+            if (!matchInstantApp && userState.isInstantApp()) {
                 if (DEBUG) {
                     log("Instant app filter is not explicitly requested", info, match, userId);
                 }
@@ -1571,7 +1571,7 @@
             }
             // throw out instant app filters if updates are available; will trigger
             // instant app resolution
-            if (userState.instantApp && ps.isUpdateAvailable()) {
+            if (userState.isInstantApp() && ps.isUpdateAvailable()) {
                 if (DEBUG) {
                     log("Instant app update is available", info, match, userId);
                 }
@@ -1599,7 +1599,7 @@
             }
             res.iconResourceId = info.getIcon();
             res.system = res.activityInfo.applicationInfo.isSystemApp();
-            res.isInstantAppAvailable = userState.instantApp;
+            res.isInstantAppAvailable = userState.isInstantApp();
             return res;
         }
 
@@ -1846,16 +1846,16 @@
             final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
             // throw out filters that aren't visible to instant applications
             if (matchVisibleToInstantApp
-                    && !(filter.isVisibleToInstantApp() || userState.instantApp)) {
+                    && !(filter.isVisibleToInstantApp() || userState.isInstantApp())) {
                 return null;
             }
             // throw out instant application filters if we're not explicitly requesting them
-            if (!isInstantApp && userState.instantApp) {
+            if (!isInstantApp && userState.isInstantApp()) {
                 return null;
             }
             // throw out instant application filters if updates are available; will trigger
             // instant application resolution
-            if (userState.instantApp && ps.isUpdateAvailable()) {
+            if (userState.isInstantApp() && ps.isUpdateAvailable()) {
                 return null;
             }
             final ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
@@ -2093,16 +2093,16 @@
             final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
             // throw out filters that aren't visible to ephemeral apps
             if (matchVisibleToInstantApp
-                    && !(filter.isVisibleToInstantApp() || userState.instantApp)) {
+                    && !(filter.isVisibleToInstantApp() || userState.isInstantApp())) {
                 return null;
             }
             // throw out ephemeral filters if we're not explicitly requesting them
-            if (!isInstantApp && userState.instantApp) {
+            if (!isInstantApp && userState.isInstantApp()) {
                 return null;
             }
             // throw out instant app filters if updates are available; will trigger
             // instant app resolution
-            if (userState.instantApp && ps.isUpdateAvailable()) {
+            if (userState.isInstantApp() && ps.isUpdateAvailable()) {
                 return null;
             }
             final ResolveInfo res = new ResolveInfo();
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9addf0e..34e9428 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -68,7 +68,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -81,6 +80,8 @@
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.os.Binder;
 import android.os.PatternMatcher;
 import android.os.Process;
@@ -647,7 +648,7 @@
         }
         if (resolveComponentName().equals(component)) {
             return PackageInfoWithoutStateUtils.generateDelegateActivityInfo(mResolveActivity,
-                    flags, new PackageUserState(), userId);
+                    flags, PackageUserState.DEFAULT, userId);
         }
         return null;
     }
@@ -1312,8 +1313,8 @@
         }
         final PackageSetting ps =
                 mSettings.getPackageLPr(instantAppInstallerActivity().packageName);
-        if (ps == null
-                || !ps.readUserState(userId).isEnabled(instantAppInstallerActivity(), 0)) {
+        if (ps == null || !PackageUserStateUtils.isEnabled(ps.readUserState(userId),
+                instantAppInstallerActivity(), 0)) {
             return result;
         }
         final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
@@ -1388,7 +1389,8 @@
                     resolveExternalPackageNameLPr(p);
 
             return packageInfo;
-        } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 && state.isAvailable(flags)) {
+        } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0
+                && PackageUserStateUtils.isAvailable(state, flags)) {
             PackageInfo pi = new PackageInfo();
             pi.packageName = ps.getPackageName();
             pi.setLongVersionCode(ps.getVersionCode());
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 4e2b978..5b692b0 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -41,9 +41,9 @@
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.VersionedPackage;
+import android.content.pm.pkg.PackageUserState;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Process;
@@ -201,8 +201,8 @@
                 for (int i = 0; i < allUsers.length; i++) {
                     PackageUserState userState = uninstalledPs.readUserState(allUsers[i]);
                     priorUserStates.put(allUsers[i],
-                            new TempUserState(userState.enabled, userState.lastDisableAppCaller,
-                                    userState.installed));
+                            new TempUserState(userState.getEnabledState(),
+                                    userState.getLastDisableAppCaller(), userState.isInstalled()));
                 }
             } else {
                 freezeUser = removeUser;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a224b81..df54030 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -62,7 +62,6 @@
 import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
-import static com.android.server.pm.parsing.PackageInfoUtils.checkUseInstalledOrHidden;
 
 import android.Manifest;
 import android.annotation.AppIdInt;
@@ -138,7 +137,6 @@
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackagePartitions;
-import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -166,6 +164,9 @@
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateInternal;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -2842,7 +2843,7 @@
                 if (ps != null) {
                     final PackageUserState state = ps.readUserState(userId);
                     if (state != null) {
-                        return checkUseInstalledOrHidden(p, ps, state, 0 /*flags*/);
+                        return PackageUserStateUtils.isAvailable(state, 0);
                     }
                 }
             }
@@ -3577,7 +3578,7 @@
                 continue;
             }
 
-            if (!ps.readUserState(userId).isAvailable(flags)) {
+            if (!PackageUserStateUtils.isAvailable(ps.readUserState(userId), flags)) {
                 continue;
             }
 
@@ -7164,7 +7165,7 @@
                 // The instance stored in PackageManagerService is special cased to be non-user
                 // specific, so initialize all the needed fields here.
                 mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                        new PackageUserState(), UserHandle.USER_SYSTEM, pkgSetting);
+                        PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
                 if (!mResolverReplaced) {
                     mResolveActivity.applicationInfo = mAndroidApplication;
@@ -7314,7 +7315,7 @@
             // The instance created in PackageManagerService is special cased to be non-user
             // specific, so initialize all the needed fields here.
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    new PackageUserState(), UserHandle.USER_SYSTEM, pkgSetting);
+                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
             // Set up information for custom user intent resolution activity.
             mResolveActivity.applicationInfo = appInfo;
@@ -8102,11 +8103,11 @@
             if (ps == null) {
                 return null;
             }
-            final PackageUserState pus = ps.readUserState(userId);
+            final PackageUserStateInternal pus = ps.readUserState(userId);
             final Bundle allExtras = new Bundle();
-            if (pus.suspended) {
-                for (int i = 0; i < pus.suspendParams.size(); i++) {
-                    final PackageUserState.SuspendParams params = pus.suspendParams.valueAt(i);
+            if (pus.isSuspended()) {
+                for (int i = 0; i < pus.getSuspendParams().size(); i++) {
+                    final PackageUserState.SuspendParams params = pus.getSuspendParams().valueAt(i);
                     if (params != null && params.appExtras != null) {
                         allExtras.putAll(params.appExtras);
                     }
@@ -13692,11 +13693,11 @@
                 final PackageSetting ps = mSettings.getPackageLPr(packageName);
                 final Bundle allExtras = new Bundle();
                 if (ps != null) {
-                    final PackageUserState pus = ps.readUserState(userId);
-                    if (pus.suspended) {
-                        for (int i = 0; i < pus.suspendParams.size(); i++) {
+                    final PackageUserStateInternal pus = ps.readUserState(userId);
+                    if (pus.isSuspended()) {
+                        for (int i = 0; i < pus.getSuspendParams().size(); i++) {
                             final PackageUserState.SuspendParams params =
-                                    pus.suspendParams.valueAt(i);
+                                    pus.getSuspendParams().valueAt(i);
                             if (params != null && params.launcherExtras != null) {
                                 allExtras.putAll(params.launcherExtras);
                             }
@@ -13758,11 +13759,11 @@
             synchronized (mLock) {
                 final PackageSetting ps = mSettings.getPackageLPr(suspendedPackage);
                 if (ps != null) {
-                    final PackageUserState pus = ps.readUserState(userId);
-                    if (pus.suspended) {
+                    final PackageUserStateInternal pus = ps.readUserState(userId);
+                    if (pus.isSuspended()) {
                         String suspendingPackage = null;
-                        for (int i = 0; i < pus.suspendParams.size(); i++) {
-                            suspendingPackage = pus.suspendParams.keyAt(i);
+                        for (int i = 0; i < pus.getSuspendParams().size(); i++) {
+                            suspendingPackage = pus.getSuspendParams().keyAt(i);
                             if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
                                 return suspendingPackage;
                             }
@@ -13780,10 +13781,10 @@
             synchronized (mLock) {
                 final PackageSetting ps = mSettings.getPackageLPr(suspendedPackage);
                 if (ps != null) {
-                    final PackageUserState pus = ps.readUserState(userId);
-                    if (pus.suspended) {
+                    final PackageUserStateInternal pus = ps.readUserState(userId);
+                    if (pus.isSuspended()) {
                         final PackageUserState.SuspendParams suspendParams =
-                                pus.suspendParams.get(suspendingPackage);
+                                pus.getSuspendParams().get(suspendingPackage);
                         return (suspendParams != null) ? suspendParams.dialogInfo : null;
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7d99ba1..1a5415c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -27,13 +27,14 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateInternal;
 import android.os.PersistableBundle;
 import android.os.incremental.IncrementalManager;
 import android.service.pm.PackageProto;
@@ -51,6 +52,7 @@
 import com.android.server.pm.pkg.AndroidPackageApi;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateUnserialized;
+import com.android.server.pm.pkg.PackageUserStateInternalImpl;
 import com.android.server.utils.SnapshotCache;
 
 import libcore.util.EmptyArray;
@@ -75,8 +77,6 @@
 @DataClass.Suppress({"getSnapshot", })
 public class PackageSetting extends SettingBase implements PackageState {
 
-    static final PackageUserState DEFAULT_USER_STATE = new PackageUserState();
-
     /**
      * Temporary holding space for the shared user ID. While parsing package settings, the
      * shared users tag may come after the packages. In this case, we must delay linking the
@@ -183,8 +183,7 @@
     // TODO: Access is not locked.
     // Whether this package is currently stopped, thus can not be
     // started until explicitly launched by the user.
-    @NonNull
-    private final SparseArray<PackageUserState> mUserState = new SparseArray<>();
+    private final SparseArray<PackageUserStateInternalImpl> mUserState = new SparseArray<>();
 
     @NonNull
     private InstallSource installSource;
@@ -668,10 +667,10 @@
     }
 
     @VisibleForTesting
-    PackageUserState modifyUserState(int userId) {
-        PackageUserState state = mUserState.get(userId);
+    PackageUserStateInternalImpl modifyUserState(int userId) {
+        PackageUserStateInternalImpl state = mUserState.get(userId);
         if (state == null) {
-            state = new PackageUserState();
+            state = new PackageUserStateInternalImpl();
             mUserState.put(userId, state);
             onChanged();
         }
@@ -679,10 +678,10 @@
     }
 
     @NonNull
-    public PackageUserState readUserState(int userId) {
-        PackageUserState state = mUserState.get(userId);
+    public PackageUserStateInternal readUserState(int userId) {
+        PackageUserStateInternal state = mUserState.get(userId);
         if (state == null) {
-            return DEFAULT_USER_STATE;
+            return PackageUserStateInternal.DEFAULT;
         }
         return state;
     }
@@ -693,42 +692,42 @@
     }
 
     void setEnabled(int state, int userId, String callingPackage) {
-        PackageUserState st = modifyUserState(userId);
-        st.enabled = state;
-        st.lastDisableAppCaller = callingPackage;
+        modifyUserState(userId)
+                .setEnabledState(state)
+                .setLastDisableAppCaller(callingPackage);
         onChanged();
     }
 
     int getEnabled(int userId) {
-        return readUserState(userId).enabled;
+        return readUserState(userId).getEnabledState();
     }
 
     String getLastDisabledAppCaller(int userId) {
-        return readUserState(userId).lastDisableAppCaller;
+        return readUserState(userId).getLastDisableAppCaller();
     }
 
     void setInstalled(boolean inst, int userId) {
-        modifyUserState(userId).installed = inst;
+        modifyUserState(userId).setInstalled(inst);
     }
 
     boolean getInstalled(int userId) {
-        return readUserState(userId).installed;
+        return readUserState(userId).isInstalled();
     }
 
     int getInstallReason(int userId) {
-        return readUserState(userId).installReason;
+        return readUserState(userId).getInstallReason();
     }
 
     void setInstallReason(int installReason, int userId) {
-        modifyUserState(userId).installReason = installReason;
+        modifyUserState(userId).setInstallReason(installReason);
     }
 
     int getUninstallReason(int userId) {
-        return readUserState(userId).uninstallReason;
+        return readUserState(userId).getUninstallReason();
     }
 
     void setUninstallReason(@PackageManager.UninstallReason int uninstallReason, int userId) {
-        modifyUserState(userId).uninstallReason = uninstallReason;
+        modifyUserState(userId).setUninstallReason(uninstallReason);
     }
 
     boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
@@ -751,13 +750,13 @@
 
     /** Only use for testing. Do NOT use in production code. */
     @VisibleForTesting
-    SparseArray<PackageUserState> getUserState() {
+    SparseArray<PackageUserStateInternalImpl> getUserState() {
         return mUserState;
     }
 
     boolean isAnyInstalled(int[] users) {
         for (int user: users) {
-            if (readUserState(user).installed) {
+            if (readUserState(user).isInstalled()) {
                 return true;
             }
         }
@@ -783,117 +782,118 @@
     }
 
     long getCeDataInode(int userId) {
-        return readUserState(userId).ceDataInode;
+        return readUserState(userId).getCeDataInode();
     }
 
     void setCeDataInode(long ceDataInode, int userId) {
-        modifyUserState(userId).ceDataInode = ceDataInode;
+        modifyUserState(userId).setCeDataInode(ceDataInode);
     }
 
     boolean getStopped(int userId) {
-        return readUserState(userId).stopped;
+        return readUserState(userId).isStopped();
     }
 
     void setStopped(boolean stop, int userId) {
-        modifyUserState(userId).stopped = stop;
+        modifyUserState(userId).setStopped(stop);
     }
 
     boolean getNotLaunched(int userId) {
-        return readUserState(userId).notLaunched;
+        return readUserState(userId).isNotLaunched();
     }
 
     void setNotLaunched(boolean stop, int userId) {
-        modifyUserState(userId).notLaunched = stop;
+        modifyUserState(userId).setNotLaunched(stop);
     }
 
     boolean getHidden(int userId) {
-        return readUserState(userId).hidden;
+        return readUserState(userId).isHidden();
     }
 
     void setHidden(boolean hidden, int userId) {
-        modifyUserState(userId).hidden = hidden;
+        modifyUserState(userId).setHidden(hidden);
     }
 
     int getDistractionFlags(int userId) {
-        return readUserState(userId).distractionFlags;
+        return readUserState(userId).getDistractionFlags();
     }
 
     void setDistractionFlags(int distractionFlags, int userId) {
-        modifyUserState(userId).distractionFlags = distractionFlags;
+        modifyUserState(userId).setDistractionFlags(distractionFlags);
     }
 
     boolean getSuspended(int userId) {
-        return readUserState(userId).suspended;
+        return readUserState(userId).isSuspended();
     }
 
     boolean isSuspendedBy(String suspendingPackage, int userId) {
-        final PackageUserState state = readUserState(userId);
-        return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
+        final PackageUserStateInternal state = readUserState(userId);
+        return state.getSuspendParams() != null
+                && state.getSuspendParams().containsKey(suspendingPackage);
     }
 
     boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
             PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
-        final PackageUserState existingUserState = modifyUserState(userId);
+        final PackageUserStateInternalImpl existingUserState = modifyUserState(userId);
         final PackageUserState.SuspendParams newSuspendParams =
                 PackageUserState.SuspendParams.getInstanceOrNull(dialogInfo, appExtras,
                         launcherExtras);
-        if (existingUserState.suspendParams == null) {
-            existingUserState.suspendParams = new ArrayMap<>();
+        if (existingUserState.getSuspendParams() == null) {
+            existingUserState.setSuspendParams(new ArrayMap<>());
         }
         final PackageUserState.SuspendParams oldSuspendParams =
-                existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
-        existingUserState.suspended = true;
+                existingUserState.getSuspendParams().put(suspendingPackage, newSuspendParams);
+        existingUserState.setSuspended(true);
         onChanged();
         return !Objects.equals(oldSuspendParams, newSuspendParams);
     }
 
     boolean removeSuspension(String suspendingPackage, int userId) {
         boolean wasModified = false;
-        final PackageUserState existingUserState = modifyUserState(userId);
-        if (existingUserState.suspendParams != null) {
-            if (existingUserState.suspendParams.remove(suspendingPackage) != null) {
+        final PackageUserStateInternalImpl existingUserState = modifyUserState(userId);
+        if (existingUserState.getSuspendParams() != null) {
+            if (existingUserState.getSuspendParams().remove(suspendingPackage) != null) {
                 wasModified = true;
             }
-            if (existingUserState.suspendParams.size() == 0) {
-                existingUserState.suspendParams = null;
+            if (existingUserState.getSuspendParams().size() == 0) {
+                existingUserState.setSuspendParams(null);
             }
         }
-        existingUserState.suspended = (existingUserState.suspendParams != null);
+        existingUserState.setSuspended((existingUserState.getSuspendParams() != null));
         onChanged();
         return wasModified;
     }
 
     void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
-        final PackageUserState existingUserState = modifyUserState(userId);
-        if (existingUserState.suspendParams != null) {
-            for (int i = existingUserState.suspendParams.size() - 1; i >= 0; i--) {
-                final String suspendingPackage = existingUserState.suspendParams.keyAt(i);
+        final PackageUserStateInternalImpl existingUserState = modifyUserState(userId);
+        if (existingUserState.getSuspendParams() != null) {
+            for (int i = existingUserState.getSuspendParams().size() - 1; i >= 0; i--) {
+                final String suspendingPackage = existingUserState.getSuspendParams().keyAt(i);
                 if (suspendingPackagePredicate.test(suspendingPackage)) {
-                    existingUserState.suspendParams.removeAt(i);
+                    existingUserState.getSuspendParams().removeAt(i);
                 }
             }
-            if (existingUserState.suspendParams.size() == 0) {
-                existingUserState.suspendParams = null;
+            if (existingUserState.getSuspendParams().size() == 0) {
+                existingUserState.setSuspendParams(null);
             }
         }
-        existingUserState.suspended = (existingUserState.suspendParams != null);
+        existingUserState.setSuspended((existingUserState.getSuspendParams() != null));
         onChanged();
     }
 
     public boolean getInstantApp(int userId) {
-        return readUserState(userId).instantApp;
+        return readUserState(userId).isInstantApp();
     }
 
     void setInstantApp(boolean instantApp, int userId) {
-        modifyUserState(userId).instantApp = instantApp;
+        modifyUserState(userId).setInstantApp(instantApp);
     }
 
     boolean getVirtualPreload(int userId) {
-        return readUserState(userId).virtualPreload;
+        return readUserState(userId).isVirtualPreload();
     }
 
     void setVirtualPreload(boolean virtualPreload, int userId) {
-        modifyUserState(userId).virtualPreload = virtualPreload;
+        modifyUserState(userId).setVirtualPreload(virtualPreload);
     }
 
     void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
@@ -903,74 +903,77 @@
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
             int installReason, int uninstallReason,
             String harmfulAppWarning, String splashScreenTheme) {
-        PackageUserState state = modifyUserState(userId);
-        state.ceDataInode = ceDataInode;
-        state.enabled = enabled;
-        state.installed = installed;
-        state.stopped = stopped;
-        state.notLaunched = notLaunched;
-        state.hidden = hidden;
-        state.distractionFlags = distractionFlags;
-        state.suspended = suspended;
-        state.suspendParams = suspendParams;
-        state.lastDisableAppCaller = lastDisableAppCaller;
-        state.enabledComponents = enabledComponents;
-        state.disabledComponents = disabledComponents;
-        state.installReason = installReason;
-        state.uninstallReason = uninstallReason;
-        state.instantApp = instantApp;
-        state.virtualPreload = virtualPreload;
-        state.harmfulAppWarning = harmfulAppWarning;
-        state.splashScreenTheme = splashScreenTheme;
+        modifyUserState(userId)
+                .setSuspendParams(suspendParams)
+                .setCeDataInode(ceDataInode)
+                .setEnabledState(enabled)
+                .setInstalled(installed)
+                .setStopped(stopped)
+                .setNotLaunched(notLaunched)
+                .setHidden(hidden)
+                .setDistractionFlags(distractionFlags)
+                .setSuspended(suspended)
+                .setLastDisableAppCaller(lastDisableAppCaller)
+                .setEnabledComponents(enabledComponents)
+                .setDisabledComponents(disabledComponents)
+                .setInstallReason(installReason)
+                .setUninstallReason(uninstallReason)
+                .setInstantApp(instantApp)
+                .setVirtualPreload(virtualPreload)
+                .setHarmfulAppWarning(harmfulAppWarning)
+                .setSplashScreenTheme(splashScreenTheme);
         onChanged();
     }
 
-    void setUserState(int userId, PackageUserState otherState) {
-        setUserState(userId, otherState.ceDataInode, otherState.enabled, otherState.installed,
-                otherState.stopped, otherState.notLaunched, otherState.hidden,
-                otherState.distractionFlags, otherState.suspended, otherState.suspendParams,
-                otherState.instantApp,
-                otherState.virtualPreload, otherState.lastDisableAppCaller,
-                otherState.enabledComponents, otherState.disabledComponents,
-                otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning,
-                otherState.splashScreenTheme);
+    void setUserState(int userId, PackageUserStateInternal otherState) {
+        setUserState(userId, otherState.getCeDataInode(), otherState.getEnabledState(),
+                otherState.isInstalled(),
+                otherState.isStopped(), otherState.isNotLaunched(), otherState.isHidden(),
+                otherState.getDistractionFlags(), otherState.isSuspended(),
+                otherState.getSuspendParams(),
+                otherState.isInstantApp(),
+                otherState.isVirtualPreload(), otherState.getLastDisableAppCaller(),
+                otherState.getEnabledComponentsNoCopy(), otherState.getDisabledComponentsNoCopy(),
+                otherState.getInstallReason(), otherState.getUninstallReason(),
+                otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme());
     }
 
     ArraySet<String> getEnabledComponents(int userId) {
-        return readUserState(userId).enabledComponents;
+        return readUserState(userId).getEnabledComponentsNoCopy();
     }
 
     ArraySet<String> getDisabledComponents(int userId) {
-        return readUserState(userId).disabledComponents;
+        return readUserState(userId).getDisabledComponentsNoCopy();
     }
 
     void setEnabledComponents(ArraySet<String> components, int userId) {
-        modifyUserState(userId).enabledComponents = components;
+        modifyUserState(userId).setEnabledComponents(components);
     }
 
     void setDisabledComponents(ArraySet<String> components, int userId) {
-        modifyUserState(userId).disabledComponents = components;
+        modifyUserState(userId).setDisabledComponents(components);
     }
 
     void setEnabledComponentsCopy(ArraySet<String> components, int userId) {
-        modifyUserState(userId).enabledComponents = components != null
-                ? new ArraySet<String>(components) : null;
+        modifyUserState(userId).setEnabledComponents(components != null
+                ? new ArraySet<String>(components) : null);
     }
 
     void setDisabledComponentsCopy(ArraySet<String> components, int userId) {
-        modifyUserState(userId).disabledComponents = components != null
-                ? new ArraySet<String>(components) : null;
+        modifyUserState(userId).setDisabledComponents(components != null
+                ? new ArraySet<String>(components) : null);
     }
 
-    PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) {
-        PackageUserState state = modifyUserState(userId);
+    PackageUserStateInternalImpl modifyUserStateComponents(int userId, boolean disabled,
+            boolean enabled) {
+        PackageUserStateInternalImpl state = modifyUserState(userId);
         boolean changed = false;
-        if (disabled && state.disabledComponents == null) {
-            state.disabledComponents = new ArraySet<String>(1);
+        if (disabled && state.getDisabledComponentsNoCopy() == null) {
+            state.setDisabledComponents(new ArraySet<String>(1));
             changed = true;
         }
-        if (enabled && state.enabledComponents == null) {
-            state.enabledComponents = new ArraySet<String>(1);
+        if (enabled && state.getEnabledComponentsNoCopy() == null) {
+            state.setEnabledComponents(new ArraySet<String>(1));
             changed = true;
         }
         if (changed) {
@@ -980,44 +983,47 @@
     }
 
     void addDisabledComponent(String componentClassName, int userId) {
-        modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName);
+        modifyUserStateComponents(userId, true, false)
+                .getDisabledComponentsNoCopy().add(componentClassName);
     }
 
     void addEnabledComponent(String componentClassName, int userId) {
-        modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName);
+        modifyUserStateComponents(userId, false, true)
+                .getEnabledComponentsNoCopy().add(componentClassName);
     }
 
     boolean enableComponentLPw(String componentClassName, int userId) {
-        PackageUserState state = modifyUserStateComponents(userId, false, true);
-        boolean changed = state.disabledComponents != null
-                ? state.disabledComponents.remove(componentClassName) : false;
-        changed |= state.enabledComponents.add(componentClassName);
+        PackageUserStateInternalImpl state = modifyUserStateComponents(userId, false, true);
+        boolean changed = state.getDisabledComponentsNoCopy() != null
+                ? state.getDisabledComponentsNoCopy().remove(componentClassName) : false;
+        changed |= state.getEnabledComponentsNoCopy().add(componentClassName);
         return changed;
     }
 
     boolean disableComponentLPw(String componentClassName, int userId) {
-        PackageUserState state = modifyUserStateComponents(userId, true, false);
-        boolean changed = state.enabledComponents != null
-                ? state.enabledComponents.remove(componentClassName) : false;
-        changed |= state.disabledComponents.add(componentClassName);
+        PackageUserStateInternalImpl state = modifyUserStateComponents(userId, true, false);
+        boolean changed = state.getEnabledComponentsNoCopy() != null
+                ? state.getEnabledComponentsNoCopy().remove(componentClassName) : false;
+        changed |= state.getDisabledComponentsNoCopy().add(componentClassName);
         return changed;
     }
 
     boolean restoreComponentLPw(String componentClassName, int userId) {
-        PackageUserState state = modifyUserStateComponents(userId, true, true);
-        boolean changed = state.disabledComponents != null
-                ? state.disabledComponents.remove(componentClassName) : false;
-        changed |= state.enabledComponents != null
-                ? state.enabledComponents.remove(componentClassName) : false;
+        PackageUserStateInternalImpl state = modifyUserStateComponents(userId, true, true);
+        boolean changed = state.getDisabledComponentsNoCopy() != null
+                ? state.getDisabledComponentsNoCopy().remove(componentClassName) : false;
+        changed |= state.getEnabledComponentsNoCopy() != null
+                ? state.getEnabledComponentsNoCopy().remove(componentClassName) : false;
         return changed;
     }
 
     int getCurrentEnabledStateLPr(String componentName, int userId) {
-        PackageUserState state = readUserState(userId);
-        if (state.enabledComponents != null && state.enabledComponents.contains(componentName)) {
+        PackageUserStateInternal state = readUserState(userId);
+        if (state.getEnabledComponentsNoCopy() != null
+                && state.getEnabledComponentsNoCopy().contains(componentName)) {
             return COMPONENT_ENABLED_STATE_ENABLED;
-        } else if (state.disabledComponents != null
-                && state.disabledComponents.contains(componentName)) {
+        } else if (state.getDisabledComponentsNoCopy() != null
+                && state.getDisabledComponentsNoCopy().contains(componentName)) {
             return COMPONENT_ENABLED_STATE_DISABLED;
         } else {
             return COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1033,7 +1039,7 @@
         int count = 0;
         int userStateCount = mUserState.size();
         for (int i = 0; i < userStateCount; i++) {
-            if (!mUserState.valueAt(i).installed) {
+            if (!mUserState.valueAt(i).isInstalled()) {
                 count++;
             }
         }
@@ -1044,7 +1050,7 @@
         int[] excludedUserIds = new int[count];
         int idx = 0;
         for (int i = 0; i < userStateCount; i++) {
-            if (!mUserState.valueAt(i).installed) {
+            if (!mUserState.valueAt(i).isInstalled()) {
                 excludedUserIds[idx++] = mUserState.keyAt(i);
             }
         }
@@ -1079,45 +1085,44 @@
         for (int i = 0; i < count; i++) {
             final long userToken = proto.start(fieldId);
             final int userId = mUserState.keyAt(i);
-            final PackageUserState state = mUserState.valueAt(i);
+            final PackageUserStateInternal state = mUserState.valueAt(i);
             proto.write(PackageProto.UserInfoProto.ID, userId);
             final int installType;
-            if (state.instantApp) {
+            if (state.isInstantApp()) {
                 installType = PackageProto.UserInfoProto.INSTANT_APP_INSTALL;
-            } else if (state.installed) {
+            } else if (state.isInstalled()) {
                 installType = PackageProto.UserInfoProto.FULL_APP_INSTALL;
             } else {
                 installType = PackageProto.UserInfoProto.NOT_INSTALLED_FOR_USER;
             }
             proto.write(PackageProto.UserInfoProto.INSTALL_TYPE, installType);
-            proto.write(PackageProto.UserInfoProto.IS_HIDDEN, state.hidden);
-            proto.write(PackageProto.UserInfoProto.DISTRACTION_FLAGS, state.distractionFlags);
-            proto.write(PackageProto.UserInfoProto.IS_SUSPENDED, state.suspended);
-            if (state.suspended) {
-                for (int j = 0; j < state.suspendParams.size(); j++) {
+            proto.write(PackageProto.UserInfoProto.IS_HIDDEN, state.isHidden());
+            proto.write(PackageProto.UserInfoProto.DISTRACTION_FLAGS, state.getDistractionFlags());
+            proto.write(PackageProto.UserInfoProto.IS_SUSPENDED, state.isSuspended());
+            if (state.isSuspended()) {
+                for (int j = 0; j < state.getSuspendParams().size(); j++) {
                     proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE,
-                            state.suspendParams.keyAt(j));
+                            state.getSuspendParams().keyAt(j));
                 }
             }
-            proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.stopped);
-            proto.write(PackageProto.UserInfoProto.IS_LAUNCHED, !state.notLaunched);
-            proto.write(PackageProto.UserInfoProto.ENABLED_STATE, state.enabled);
+            proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped());
+            proto.write(PackageProto.UserInfoProto.IS_LAUNCHED, !state.isNotLaunched());
+            proto.write(PackageProto.UserInfoProto.ENABLED_STATE, state.getEnabledState());
             proto.write(
                     PackageProto.UserInfoProto.LAST_DISABLED_APP_CALLER,
-                    state.lastDisableAppCaller);
+                    state.getLastDisableAppCaller());
             proto.end(userToken);
         }
     }
 
     void setHarmfulAppWarning(int userId, String harmfulAppWarning) {
-        PackageUserState userState = modifyUserState(userId);
-        userState.harmfulAppWarning = harmfulAppWarning;
+        modifyUserState(userId).setHarmfulAppWarning(harmfulAppWarning);
         onChanged();
     }
 
     String getHarmfulAppWarning(int userId) {
         PackageUserState userState = readUserState(userId);
-        return userState.harmfulAppWarning;
+        return userState.getHarmfulAppWarning();
     }
 
     /**
@@ -1160,7 +1165,7 @@
      * @see android.window.SplashScreen#setSplashScreenTheme(int)
      */
     public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
-        modifyUserState(userId).splashScreenTheme = themeName;
+        modifyUserState(userId).setSplashScreenTheme(themeName);
         onChanged();
     }
 
@@ -1172,7 +1177,7 @@
      */
     @Nullable
     public String getSplashScreenTheme(@UserIdInt int userId) {
-        return readUserState(userId).splashScreenTheme;
+        return readUserState(userId).getSplashScreenTheme();
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8ee2588..85adaa0 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -42,7 +42,6 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageUserState;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
@@ -56,6 +55,9 @@
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedPermission;
 import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateInternal;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -288,17 +290,17 @@
     private static final String TAG_DEFAULT_DIALER = "default-dialer";
     private static final String TAG_VERSION = "version";
     /**
-     * @deprecated Moved to {@link android.content.pm.PackageUserState.SuspendParams}
+     * @deprecated Moved to {@link PackageUserState.SuspendParams}
      */
     @Deprecated
     private static final String TAG_SUSPENDED_DIALOG_INFO = "suspended-dialog-info";
     /**
-     * @deprecated Moved to {@link android.content.pm.PackageUserState.SuspendParams}
+     * @deprecated Moved to {@link PackageUserState.SuspendParams}
      */
     @Deprecated
     private static final String TAG_SUSPENDED_APP_EXTRAS = "suspended-app-extras";
     /**
-     * @deprecated Moved to {@link android.content.pm.PackageUserState.SuspendParams}
+     * @deprecated Moved to {@link PackageUserState.SuspendParams}
      */
     @Deprecated
     private static final String TAG_SUSPENDED_LAUNCHER_EXTRAS = "suspended-launcher-extras";
@@ -1163,7 +1165,7 @@
         }
         for (UserInfo user : allUsers) {
             final PackageUserState oldUserState = oldPackage == null
-                    ? PackageSetting.DEFAULT_USER_STATE
+                    ? PackageUserState.DEFAULT
                     : oldPackage.readUserState(user.id);
             if (!oldUserState.equals(newPackage.readUserState(user.id))) {
                 writePackageRestrictionsLPr(user.id);
@@ -1994,87 +1996,91 @@
 
             if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile);
             for (final PackageSetting pkg : mPackages.values()) {
-                final PackageUserState ustate = pkg.readUserState(userId);
+                final PackageUserStateInternal ustate = pkg.readUserState(userId);
                 if (DEBUG_MU) {
-                    Log.i(TAG, "  pkg=" + pkg.getPackageName() + ", installed=" + ustate.installed
-                            + ", state=" + ustate.enabled);
+                    Log.i(TAG, "  pkg=" + pkg.getPackageName()
+                            + ", installed=" + ustate.isInstalled()
+                            + ", state=" + ustate.getEnabledState());
                 }
 
                 serializer.startTag(null, TAG_PACKAGE);
                 serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
-                if (ustate.ceDataInode != 0) {
-                    serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.ceDataInode);
+                if (ustate.getCeDataInode() != 0) {
+                    serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode());
                 }
-                if (!ustate.installed) {
+                if (!ustate.isInstalled()) {
                     serializer.attributeBoolean(null, ATTR_INSTALLED, false);
                 }
-                if (ustate.stopped) {
+                if (ustate.isStopped()) {
                     serializer.attributeBoolean(null, ATTR_STOPPED, true);
                 }
-                if (ustate.notLaunched) {
+                if (ustate.isNotLaunched()) {
                     serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true);
                 }
-                if (ustate.hidden) {
+                if (ustate.isHidden()) {
                     serializer.attributeBoolean(null, ATTR_HIDDEN, true);
                 }
-                if (ustate.distractionFlags != 0) {
-                    serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS, ustate.distractionFlags);
+                if (ustate.getDistractionFlags() != 0) {
+                    serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS,
+                            ustate.getDistractionFlags());
                 }
-                if (ustate.suspended) {
+                if (ustate.isSuspended()) {
                     serializer.attributeBoolean(null, ATTR_SUSPENDED, true);
                 }
-                if (ustate.instantApp) {
+                if (ustate.isInstantApp()) {
                     serializer.attributeBoolean(null, ATTR_INSTANT_APP, true);
                 }
-                if (ustate.virtualPreload) {
+                if (ustate.isVirtualPreload()) {
                     serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true);
                 }
-                if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
-                    serializer.attributeInt(null, ATTR_ENABLED, ustate.enabled);
-                    if (ustate.lastDisableAppCaller != null) {
+                if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
+                    serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
+                    if (ustate.getLastDisableAppCaller() != null) {
                         serializer.attribute(null, ATTR_ENABLED_CALLER,
-                                ustate.lastDisableAppCaller);
+                                ustate.getLastDisableAppCaller());
                     }
                 }
-                if (ustate.installReason != PackageManager.INSTALL_REASON_UNKNOWN) {
-                    serializer.attributeInt(null, ATTR_INSTALL_REASON, ustate.installReason);
+                if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
+                    serializer.attributeInt(null, ATTR_INSTALL_REASON,
+                            ustate.getInstallReason());
                 }
-                if (ustate.uninstallReason != PackageManager.UNINSTALL_REASON_UNKNOWN) {
-                    serializer.attributeInt(null, ATTR_UNINSTALL_REASON, ustate.uninstallReason);
+                if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) {
+                    serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
+                            ustate.getUninstallReason());
                 }
-                if (ustate.harmfulAppWarning != null) {
+                if (ustate.getHarmfulAppWarning() != null) {
                     serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
-                            ustate.harmfulAppWarning);
+                            ustate.getHarmfulAppWarning());
                 }
-                if (ustate.splashScreenTheme != null) {
+                if (ustate.getSplashScreenTheme() != null) {
                     serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
-                            ustate.splashScreenTheme);
+                            ustate.getSplashScreenTheme());
                 }
-                if (ustate.suspended) {
-                    for (int i = 0; i < ustate.suspendParams.size(); i++) {
-                        final String suspendingPackage = ustate.suspendParams.keyAt(i);
+                if (ustate.isSuspended()) {
+                    for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
+                        final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
                         serializer.startTag(null, TAG_SUSPEND_PARAMS);
                         serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, suspendingPackage);
                         final PackageUserState.SuspendParams params =
-                                ustate.suspendParams.valueAt(i);
+                                ustate.getSuspendParams().valueAt(i);
                         if (params != null) {
                             params.saveToXml(serializer);
                         }
                         serializer.endTag(null, TAG_SUSPEND_PARAMS);
                     }
                 }
-                if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
+                if (!ArrayUtils.isEmpty(ustate.getEnabledComponentsNoCopy())) {
                     serializer.startTag(null, TAG_ENABLED_COMPONENTS);
-                    for (final String name : ustate.enabledComponents) {
+                    for (final String name : ustate.getEnabledComponentsNoCopy()) {
                         serializer.startTag(null, TAG_ITEM);
                         serializer.attribute(null, ATTR_NAME, name);
                         serializer.endTag(null, TAG_ITEM);
                     }
                     serializer.endTag(null, TAG_ENABLED_COMPONENTS);
                 }
-                if (!ArrayUtils.isEmpty(ustate.disabledComponents)) {
+                if (!ArrayUtils.isEmpty(ustate.getDisabledComponentsNoCopy())) {
                     serializer.startTag(null, TAG_DISABLED_COMPONENTS);
-                    for (final String name : ustate.disabledComponents) {
+                    for (final String name : ustate.getDisabledComponentsNoCopy()) {
                         serializer.startTag(null, TAG_ITEM);
                         serializer.attribute(null, ATTR_NAME, name);
                         serializer.endTag(null, TAG_ITEM);
@@ -4158,7 +4164,7 @@
         if (ps == null) return false;
 
         final PackageUserState userState = ps.readUserState(userId);
-        return userState.isMatch(componentInfo, flags);
+        return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -4168,7 +4174,8 @@
         if (ps == null) return false;
 
         final PackageUserState userState = ps.readUserState(userId);
-        return userState.isMatch(pkg.isSystem(), pkg.isEnabled(), component, flags);
+        return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component,
+                flags);
     }
 
     boolean isOrphaned(String packageName) {
@@ -4771,12 +4778,12 @@
             if (ps.getSuspended(user.id)) {
                 pw.print(prefix);
                 pw.println("  Suspend params:");
-                final PackageUserState pus = ps.readUserState(user.id);
-                for (int i = 0; i < pus.suspendParams.size(); i++) {
+                final PackageUserStateInternal pus = ps.readUserState(user.id);
+                for (int i = 0; i < pus.getSuspendParams().size(); i++) {
                     pw.print(prefix);
                     pw.print("    suspendingPackage=");
-                    pw.print(pus.suspendParams.keyAt(i));
-                    final PackageUserState.SuspendParams params = pus.suspendParams.valueAt(i);
+                    pw.print(pus.getSuspendParams().keyAt(i));
+                    final PackageUserState.SuspendParams params = pus.getSuspendParams().valueAt(i);
                     if (params != null) {
                         pw.print(" dialogInfo=");
                         pw.print(params.dialogInfo);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 876c534..4c88b47 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProcessInfo;
@@ -47,6 +46,8 @@
 import android.content.pm.parsing.component.ParsedProcess;
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -70,8 +71,8 @@
 
 /**
  * Methods that use a {@link PackageSetting} use it to override information provided from the raw
- * package, or to provide information that would otherwise be missing. Null can be passed if none
- * of the state values should be applied.
+ * package, or to provide information that would otherwise be missing. Null can be passed if none of
+ * the state values should be applied.
  *
  * @hide
  **/
@@ -97,7 +98,7 @@
     public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
             @Nullable PackageSetting pkgSetting) {
         return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                new PackageUserState(), UserHandle.getCallingUserId(), apexInfo, pkgSetting);
+                PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
     }
 
     /**
@@ -425,7 +426,7 @@
             @PackageManager.PackageInfoFlags int flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
-                && !state.installed
+                && !state.isInstalled()
                 && pkgSetting != null
                 && pkgSetting.getPkgState().isHiddenUntilInstalled()) {
             return false;
@@ -433,7 +434,7 @@
 
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
-        return state.isAvailable(flags)
+        return PackageUserStateUtils.isAvailable(state, flags)
                 || (pkg.isSystem()
                 && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
                 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 61f7daf..7598423 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -48,7 +48,7 @@
  * and not exposed to the core SDK.
  *
  * Many of the fields contained here will eventually be moved inside
- * {@link com.android.server.pm.PackageSetting} or {@link android.content.pm.PackageUserState}.
+ * {@link com.android.server.pm.PackageSetting} or {@link android.content.pm.pkg.PackageUserState}.
  *
  * @hide
  */
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 4c593d9..0ba6d6e 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -338,6 +338,8 @@
         private final Map<String, OverlayPaths> mSharedLibraryOverlayPaths;
         @PackageManager.UninstallReason
         private final int mUninstallReason;
+        @Nullable
+        private final String mSplashScreenTheme;
 
         private UserStateImpl(@NonNull PackageUserState userState) {
             mCeDataInode = userState.getCeDataInode();
@@ -351,6 +353,7 @@
             mOverlayPaths = userState.getOverlayPaths();
             mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
             mUninstallReason = userState.getUninstallReason();
+            mSplashScreenTheme = userState.getSplashScreenTheme();
             setBoolean(Booleans.HIDDEN, userState.isHidden());
             setBoolean(Booleans.INSTALLED, userState.isInstalled());
             setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
@@ -395,6 +398,31 @@
             return getBoolean(Booleans.VIRTUAL_PRELOAD);
         }
 
+        @Override
+        public boolean isComponentEnabled(String componentName) {
+            return mEnabledComponents.contains(componentName);
+        }
+
+        @Override
+        public boolean isComponentDisabled(String componentName) {
+            return mDisabledComponents.contains(componentName);
+        }
+
+        @Override
+        public OverlayPaths getAllOverlayPaths() {
+            if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) {
+                return null;
+            }
+            final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
+            newPaths.addAll(mOverlayPaths);
+            if (mSharedLibraryOverlayPaths != null) {
+                for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) {
+                    newPaths.addAll(libOverlayPaths);
+                }
+            }
+            return newPaths.build();
+        }
+
 
 
         // Code below generated by codegen v1.0.23.
@@ -471,16 +499,21 @@
         }
 
         @DataClass.Generated.Member
+        public @Nullable String getSplashScreenTheme() {
+            return mSplashScreenTheme;
+        }
+
+        @DataClass.Generated.Member
         public @NonNull UserStateImpl setBooleans( int value) {
             mBooleans = value;
             return this;
         }
 
         @DataClass.Generated(
-                time = 1630604430207L,
+                time = 1630604891308L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\npublic static  android.content.pm.pkg.PackageUserState copy(android.content.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\nclass UserStateImpl extends java.lang.Object implements [android.content.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\npublic static  android.content.pm.pkg.PackageUserState copy(android.content.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic  android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [android.content.pm.pkg.PackageUserState, android.content.pm.pkg.PackageUserStateHidden]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
         @Deprecated
         private void __metadata() {}
 
@@ -637,7 +670,7 @@
     }
 
     @DataClass.Generated(
-            time = 1630604430248L,
+            time = 1630604891337L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
             inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mFirstInstallTime\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.Nullable java.lang.String mSeInfoOverride\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull int[] mUserIds\nprivate final @android.annotation.NonNull android.util.SparseArray<android.content.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.PackageSetting)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.Nullable @java.lang.Override android.content.pm.pkg.PackageUserState getUserState(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternalImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternalImpl.java
new file mode 100644
index 0000000..d5f8dbf
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternalImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateImpl;
+import android.content.pm.pkg.PackageUserStateInternal;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+@DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
+@DataClass.Suppress({"mCachedOverlayPathsLock", "mCachedOverlayPaths", "setCachedOverlayPaths"})
+public class PackageUserStateInternalImpl extends PackageUserStateImpl implements
+        PackageUserStateInternal {
+
+    /** Suspending package to suspend params */
+    @Nullable
+    private ArrayMap<String, PackageUserState.SuspendParams> mSuspendParams;
+
+    @Nullable
+    private OverlayPaths mCachedOverlayPaths;
+
+    @Nullable
+    private ArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
+
+    public PackageUserStateInternalImpl() {
+        super();
+    }
+
+    public PackageUserStateInternalImpl(PackageUserStateInternalImpl other) {
+        super(other);
+        mSuspendParams = other.mSuspendParams == null ? null : new ArrayMap<>(other.mSuspendParams);
+        mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null ? null
+                : new ArrayMap<>(other.mComponentLabelIconOverrideMap);
+    }
+
+    /**
+     * Sets the path of overlays currently enabled for this package and user combination.
+     * @return true if the path contents differ than what they were previously
+     */
+    @Nullable
+    public boolean setOverlayPaths(@Nullable OverlayPaths paths) {
+        if (Objects.equals(paths, mOverlayPaths)) {
+            return false;
+        }
+        if ((mOverlayPaths == null && paths.isEmpty())
+                || (paths == null && mOverlayPaths.isEmpty())) {
+            return false;
+        }
+        mOverlayPaths = paths;
+        mCachedOverlayPaths = null;
+        return true;
+    }
+
+    /**
+     * Sets the path of overlays currently enabled for a library that this package uses.
+     *
+     * @return true if the path contents for the library differ than what they were previously
+     */
+    public boolean setSharedLibraryOverlayPaths(@NonNull String library,
+            @Nullable OverlayPaths paths) {
+        if (mSharedLibraryOverlayPaths == null) {
+            mSharedLibraryOverlayPaths = new ArrayMap<>();
+        }
+        final OverlayPaths currentPaths = mSharedLibraryOverlayPaths.get(library);
+        if (Objects.equals(paths, currentPaths)) {
+            return false;
+        }
+        mCachedOverlayPaths = null;
+        if (paths == null || paths.isEmpty()) {
+            return mSharedLibraryOverlayPaths.remove(library) != null;
+        } else {
+            mSharedLibraryOverlayPaths.put(library, paths);
+            return true;
+        }
+    }
+
+    @Nullable
+    @Override
+    public ArraySet<String> getDisabledComponentsNoCopy() {
+        return mDisabledComponents;
+    }
+
+    @Nullable
+    @Override
+    public ArraySet<String> getEnabledComponentsNoCopy() {
+        return mEnabledComponents;
+    }
+
+    /**
+     * Overrides the non-localized label and icon of a component.
+     *
+     * @return true if the label or icon was changed.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean overrideLabelAndIcon(@NonNull ComponentName component,
+            @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+        String existingLabel = null;
+        Integer existingIcon = null;
+
+        if (mComponentLabelIconOverrideMap != null) {
+            Pair<String, Integer> pair = mComponentLabelIconOverrideMap.get(component);
+            if (pair != null) {
+                existingLabel = pair.first;
+                existingIcon = pair.second;
+            }
+        }
+
+        boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel)
+                || !Objects.equals(existingIcon, icon);
+
+        if (changed) {
+            if (nonLocalizedLabel == null && icon == null) {
+                mComponentLabelIconOverrideMap.remove(component);
+                if (mComponentLabelIconOverrideMap.isEmpty()) {
+                    mComponentLabelIconOverrideMap = null;
+                }
+            } else {
+                if (mComponentLabelIconOverrideMap == null) {
+                    mComponentLabelIconOverrideMap = new ArrayMap<>(1);
+                }
+
+                mComponentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon));
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName,
+     * String, Integer)}.
+     *
+     * This is done when the package is updated as the components and resource IDs may have changed.
+     */
+    public void resetOverrideComponentLabelIcon() {
+        mComponentLabelIconOverrideMap = null;
+    }
+
+    @Nullable
+    public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
+        if (ArrayUtils.isEmpty(mComponentLabelIconOverrideMap)) {
+            return null;
+        }
+
+        return mComponentLabelIconOverrideMap.get(componentName);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof PackageUserStateInternalImpl)) return false;
+        if (!super.equals(o)) return false;
+        PackageUserStateInternalImpl that = (PackageUserStateInternalImpl) o;
+        return Objects.equals(mSuspendParams, that.mSuspendParams)
+                && Objects.equals(mCachedOverlayPaths, that.mCachedOverlayPaths)
+                && Objects.equals(mComponentLabelIconOverrideMap,
+                that.mComponentLabelIconOverrideMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSuspendParams, mCachedOverlayPaths,
+                mComponentLabelIconOverrideMap);
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateInternalImpl.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Suspending package to suspend params
+     */
+    @DataClass.Generated.Member
+    public @Nullable ArrayMap<String,PackageUserState.SuspendParams> getSuspendParams() {
+        return mSuspendParams;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable OverlayPaths getCachedOverlayPaths() {
+        return mCachedOverlayPaths;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable ArrayMap<ComponentName,Pair<String,Integer>> getComponentLabelIconOverrideMap() {
+        return mComponentLabelIconOverrideMap;
+    }
+
+    /**
+     * Suspending package to suspend params
+     */
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateInternalImpl setSuspendParams(@NonNull ArrayMap<String,PackageUserState.SuspendParams> value) {
+        mSuspendParams = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull PackageUserStateInternalImpl setComponentLabelIconOverrideMap(@NonNull ArrayMap<ComponentName,Pair<String,Integer>> value) {
+        mComponentLabelIconOverrideMap = value;
+        return this;
+    }
+
+    @DataClass.Generated(
+            time = 1626458385872L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateInternalImpl.java",
+            inputSignatures = "private @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.PackageUserState.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mCachedOverlayPaths\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic  android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\nclass PackageUserStateInternalImpl extends android.content.pm.pkg.PackageUserStateImpl implements [android.content.pm.pkg.PackageUserStateInternal, android.content.pm.pkg.PackageUserStateHidden]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
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 f0f825a..3174e91 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
@@ -29,9 +29,10 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageUserState;
 import android.content.pm.ResolveInfo;
 import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateUtils;
 import android.content.pm.verify.domain.DomainOwner;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
@@ -1743,13 +1744,13 @@
                 userId, debugObject);
         if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
             PackageUserState pkgUserState = pkgSetting.readUserState(userId);
-            if (!pkgUserState.installed) {
+            if (!pkgUserState.isInstalled()) {
                 return APPROVAL_LEVEL_NOT_INSTALLED;
             }
 
             AndroidPackage pkg = pkgSetting.getPkg();
             if (pkg != null) {
-                if (!pkgUserState.isPackageEnabled(pkg)) {
+                if (!PackageUserStateUtils.isPackageEnabled(pkgUserState, pkg)) {
                     return APPROVAL_LEVEL_DISABLED;
                 } else if (mCollector.containsAutoVerifyDomain(pkgSetting.getPkg(), host)) {
                     return APPROVAL_LEVEL_UNVERIFIED;
@@ -1783,7 +1784,7 @@
             return APPROVAL_LEVEL_NONE;
         }
 
-        if (!pkgUserState.installed) {
+        if (!pkgUserState.isInstalled()) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, debugObject, userId, false,
                         "package not installed for user");
@@ -1791,7 +1792,7 @@
             return APPROVAL_LEVEL_NONE;
         }
 
-        if (!pkgUserState.isPackageEnabled(pkg)) {
+        if (!PackageUserStateUtils.isPackageEnabled(pkgUserState, pkg)) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, debugObject, userId, false,
                         "package not enabled for user");
@@ -1799,7 +1800,7 @@
             return APPROVAL_LEVEL_NONE;
         }
 
-        if (pkgUserState.suspended) {
+        if (pkgUserState.isSuspended()) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, debugObject, userId, false,
                         "package suspended for user");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 811a434..12e6086d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1816,7 +1816,11 @@
             @Override
             public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
                     long statusBarAnimationStartTime, long statusBarAnimationDuration) {
-                return handleStartTransitionForKeyguardLw(keyguardGoingAway, duration);
+                // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+                // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+                // need to call IKeyguardService#keyguardGoingAway here.
+                return handleStartTransitionForKeyguardLw(keyguardGoingAway
+                        && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, duration);
             }
 
             @Override
@@ -3066,7 +3070,7 @@
     private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
         final int res = applyKeyguardOcclusionChange();
         if (res != 0) return res;
-        if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation && keyguardGoingAway) {
+        if (keyguardGoingAway) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
             startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index d190678..86ff33e 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -410,8 +410,7 @@
     }
 
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
-        if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation
-                && mKeyguardService != null) {
+        if (mKeyguardService != null) {
             mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 5f6fff1..59f8e54 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -425,7 +425,8 @@
         mBound = context.bindServiceAsUser(intent, mConnection,
                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, user);
         if (mBound) {
-            mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null);
+            mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null,
+                    Context.RECEIVER_EXPORTED);
         } else {
             Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString());
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 161a2bc..b807863 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4938,7 +4938,8 @@
         } else {
             // If we are being set visible, and the starting window is not yet displayed,
             // then make sure it doesn't get displayed.
-            if (mStartingWindow != null && !mStartingWindow.isDrawn()) {
+            if (mStartingWindow != null && !mStartingWindow.isDrawn()
+                    && (firstWindowDrawn || allDrawn)) {
                 mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
                 mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
             }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 1a2bf9a..ffaf710 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1108,6 +1108,11 @@
         //    the same transition.
         for (int i = rootTasks.size() - 1; i >= 0; i--) {
             final Task rootTask = rootTasks.valueAt(i);
+            if (rootTask == null) {
+                // It is possible that one activity may have been removed from the hierarchy. No
+                // need to check for this case.
+                continue;
+            }
             final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> {
                 if (!taskFragment.isReadyToTransit()) {
                     ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s",
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1909875..e50e8ef 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -758,8 +758,13 @@
                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
-            mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
-                    SystemClock.uptimeMillis(), 0 /* duration */);
+            if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+                // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+                // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+                // need to call IKeyguardService#keyguardGoingAway here.
+                mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
+                        SystemClock.uptimeMillis(), 0 /* duration */);
+            }
         }
         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
             mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 00e58ef..59f831e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10630,19 +10630,21 @@
             }
         }
         final String adminPkg = admin.getPackageName();
-        try {
-            // Install the profile owner if not present.
-            if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) {
-                mIPackageManager.installExistingPackageAsUser(adminPkg, userId,
-                        PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
-                        PackageManager.INSTALL_REASON_POLICY,
-                        /* allowlistedRestrictedPermissions= */ null);
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            try {
+                // Install the profile owner if not present.
+                if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) {
+                    mIPackageManager.installExistingPackageAsUser(adminPkg, userId,
+                            PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+                            PackageManager.INSTALL_REASON_POLICY,
+                            /* allowlistedRestrictedPermissions= */ null);
+                }
+            } catch (RemoteException e) {
+                // Does not happen, same process
+                Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d",
+                        adminPkg, userId);
             }
-        } catch (RemoteException e) {
-            // Does not happen, same process
-            Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d",
-                    adminPkg, userId);
-        }
+        });
 
         // Set admin.
         setActiveAdmin(profileOwner, /* refreshing= */ true, userId);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index ef1201e..e6fd916 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -161,7 +161,8 @@
 
         mStatusExpReceiver = new ConversationStatusExpirationBroadcastReceiver();
         mContext.registerReceiver(mStatusExpReceiver,
-                ConversationStatusExpirationBroadcastReceiver.getFilter());
+                ConversationStatusExpirationBroadcastReceiver.getFilter(),
+                Context.RECEIVER_NOT_EXPORTED);
 
         IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
         BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
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 d6d5e62..1ad1879 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
@@ -19,9 +19,10 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.content.pm.PackageUserState
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.pkg.PackageUserState
+import android.content.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.verify.domain.DomainVerificationState
 import android.os.Build
@@ -331,13 +332,13 @@
                 domainSetId
             )
         ) {
-            whenever(getPackageName()) { packageName }
-            whenever(getPkg()) { mockPkg(packageName) }
+            whenever(this.packageName) { packageName }
+            whenever(pkg) { mockPkg(packageName) }
             whenever(this.domainSetId) { domainSetId }
-            whenever(readUserState(0)) { PackageUserState() }
-            whenever(readUserState(1)) { PackageUserState() }
+            whenever(readUserState(0)) { PackageUserStateInternal.DEFAULT }
+            whenever(readUserState(1)) { PackageUserStateInternal.DEFAULT }
             whenever(getInstantApp(anyInt())) { false }
-            whenever(isSystem()) { false }
+            whenever(isSystem) { false }
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index d67b26a..b65995f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.content.pm.PackageUserState
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainOwner
 import android.content.pm.verify.domain.DomainVerificationInfo
 import android.content.pm.verify.domain.DomainVerificationManager
@@ -77,7 +77,7 @@
         }
 
         assertThat(service.queryValidVerificationPackageNames())
-                .containsExactly(pkgWithDomains.getPackageName())
+            .containsExactly(pkgWithDomains.packageName)
     }
 
     @Test
@@ -104,16 +104,18 @@
             addPackages(pkgWithDomains, pkgWithoutDomains)
         }
 
-        val infoOne = service.getDomainVerificationInfo(pkgWithDomains.getPackageName())
+        val infoOne = service.getDomainVerificationInfo(pkgWithDomains.packageName)
         assertThat(infoOne).isNotNull()
         assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
-        assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getPackageName())
-        assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
+        assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.packageName)
+        assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(
+            mapOf(
                 DOMAIN_1 to DomainVerificationInfo.STATE_NO_RESPONSE,
                 DOMAIN_2 to DomainVerificationInfo.STATE_NO_RESPONSE
-        ))
+            )
+        )
 
-        assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.getPackageName())).isNull()
+        assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.packageName)).isNull()
 
         assertFailsWith(PackageManager.NameNotFoundException::class) {
             service.getDomainVerificationInfo("invalid.pkg.name")
@@ -125,14 +127,14 @@
         val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
         val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
 
-        val map = mutableMapOf(pkg1.getPackageName() to pkg1, pkg2.getPackageName() to pkg2)
+        val map = mutableMapOf(pkg1.packageName to pkg1, pkg2.packageName to pkg2)
         val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
 
         assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_2), 1100))
-                .isEqualTo(DomainVerificationManager.STATUS_OK)
+            .isEqualTo(DomainVerificationManager.STATUS_OK)
 
         assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
+            .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
 
         assertFailsWith(IllegalArgumentException::class) {
             DomainVerificationJavaUtil.setStatusForceNullable(service, null, setOf(DOMAIN_1), 1100)
@@ -147,10 +149,10 @@
         }
 
         assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100))
-                .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+            .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
 
         assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100))
-                .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+            .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
 
         assertFailsWith(IllegalArgumentException::class) {
             service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)
@@ -167,22 +169,28 @@
         val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
         val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
 
-        val map = mutableMapOf(pkg1.getPackageName() to pkg1, pkg2.getPackageName() to pkg2)
+        val map = mutableMapOf(pkg1.packageName to pkg1, pkg2.packageName to pkg2)
         val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
 
         service.setDomainVerificationLinkHandlingAllowed(PKG_ONE, false, 0)
 
         // Should edit same package, same user
-        assertThat(service.getDomainVerificationUserState(PKG_ONE, 0)
-                ?.isLinkHandlingAllowed).isEqualTo(false)
+        assertThat(
+            service.getDomainVerificationUserState(PKG_ONE, 0)
+                ?.isLinkHandlingAllowed
+        ).isEqualTo(false)
 
         // Shouldn't edit different user
-        assertThat(service.getDomainVerificationUserState(PKG_ONE, 1)
-                ?.isLinkHandlingAllowed).isEqualTo(true)
+        assertThat(
+            service.getDomainVerificationUserState(PKG_ONE, 1)
+                ?.isLinkHandlingAllowed
+        ).isEqualTo(true)
 
         // Shouldn't edit different package
-        assertThat(service.getDomainVerificationUserState(PKG_TWO, 0)
-                ?.isLinkHandlingAllowed).isEqualTo(true)
+        assertThat(
+            service.getDomainVerificationUserState(PKG_TWO, 0)
+                ?.isLinkHandlingAllowed
+        ).isEqualTo(true)
 
         assertFailsWith(PackageManager.NameNotFoundException::class) {
             service.setDomainVerificationLinkHandlingAllowed("invalid.pkg.name", false, 0)
@@ -196,26 +204,30 @@
         val pkg3 = mockPkgSetting(PKG_THREE, UUID_THREE, listOf(DOMAIN_1, DOMAIN_2))
 
         val map = mutableMapOf(
-                pkg1.getPackageName() to pkg1,
-                pkg2.getPackageName() to pkg2,
-                pkg3.getPackageName() to pkg3
+            pkg1.packageName to pkg1,
+            pkg2.packageName to pkg2,
+            pkg3.packageName to pkg3
         )
         val service = makeService(map::get).apply { addPackages(pkg1, pkg2, pkg3) }
 
         assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, 0))
-                .isEqualTo(DomainVerificationManager.STATUS_OK)
+            .isEqualTo(DomainVerificationManager.STATUS_OK)
 
         assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
+            .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
 
         assertFailsWith(IllegalArgumentException::class) {
-            DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
-                setOf(DOMAIN_1), true, 0)
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(
+                service, null,
+                setOf(DOMAIN_1), true, 0
+            )
         }
 
         assertFailsWith(IllegalArgumentException::class) {
-            DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
-                true, 0)
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(
+                service, UUID_ONE, null,
+                true, 0
+            )
         }
 
         assertFailsWith(IllegalArgumentException::class) {
@@ -223,10 +235,10 @@
         }
 
         assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0))
-                .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+            .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
 
         assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), true, 0))
-                .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+            .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
 
         service.setStatus(UUID_ONE, setOf(DOMAIN_2), DomainVerificationInfo.STATE_SUCCESS)
 
@@ -248,20 +260,22 @@
             addPackages(pkgWithDomains, pkgWithoutDomains)
         }
 
-        val infoOne = service.getDomainVerificationUserState(pkgWithDomains.getPackageName(), 0)
+        val infoOne = service.getDomainVerificationUserState(pkgWithDomains.packageName, 0)
         assertThat(infoOne).isNotNull()
         assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
-        assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getPackageName())
+        assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.packageName)
         assertThat(infoOne.isLinkHandlingAllowed).isTrue()
-        assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
+        assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(
+            mapOf(
                 DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE,
                 DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE
-        ))
+            )
+        )
 
-        val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.getPackageName(), 0)
+        val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.packageName, 0)
         assertThat(infoTwo).isNotNull()
         assertThat(infoTwo!!.identifier).isEqualTo(pkgWithoutDomains.domainSetId)
-        assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.getPackageName())
+        assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.packageName)
         assertThat(infoOne.isLinkHandlingAllowed).isTrue()
         assertThat(infoTwo.hostToStateMap).isEmpty()
 
@@ -276,10 +290,15 @@
 
         val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2), pkgUserState0 = {
             mockThrowOnUnmocked {
-                whenever(isPackageEnabled(any())) {
-                    pkg1User0Enabled.get()
+                whenever(enabledState) {
+                    if (pkg1User0Enabled.get()) {
+                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                    } else {
+                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                    }
                 }
-                installed = true
+                whenever(isInstalled) { true }
+                whenever(isSuspended) { false }
             }
         })
         val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
@@ -323,43 +342,43 @@
 
         service.getOwnersForDomain(DOMAIN_1, 0).let {
             assertThat(it).containsExactly(
-                DomainOwner(pkg1.getPackageName(), false),
-                DomainOwner(pkg2.getPackageName(), false)
+                DomainOwner(pkg1.packageName, false),
+                DomainOwner(pkg2.packageName, false)
             ).inOrder()
         }
         manager0.getOwnersForDomain(DOMAIN_1).let {
             assertThat(it).containsExactly(
-                DomainOwner(pkg1.getPackageName(), false),
-                DomainOwner(pkg2.getPackageName(), false)
+                DomainOwner(pkg1.packageName, false),
+                DomainOwner(pkg2.packageName, false)
             ).inOrder()
         }
 
         service.getOwnersForDomain(DOMAIN_2, 0).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
         manager0.getOwnersForDomain(DOMAIN_2).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
 
         assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
         assertThat(manager1.getOwnersForDomain(DOMAIN_2)).isEmpty()
         service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 1)
         service.getOwnersForDomain(DOMAIN_2, 1).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
         manager1.getOwnersForDomain(DOMAIN_2).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
 
         // "Uninstall" the package from user 0 and ensure it's stripped from the results
         pkg1User0Enabled.set(false)
-        service.clearPackageForUser(pkg1.getPackageName(), 0)
+        service.clearPackageForUser(pkg1.packageName, 0)
 
         service.getOwnersForDomain(DOMAIN_1, 0).let {
-            assertThat(it).containsExactly(DomainOwner(pkg2.getPackageName(), false))
+            assertThat(it).containsExactly(DomainOwner(pkg2.packageName, false))
         }
         manager0.getOwnersForDomain(DOMAIN_1).let {
-            assertThat(it).containsExactly(DomainOwner(pkg2.getPackageName(), false))
+            assertThat(it).containsExactly(DomainOwner(pkg2.packageName, false))
         }
 
         // Domain 2 user selection gone for user 0
@@ -367,33 +386,33 @@
 
         // Domain 2 user selection still around for user 1
         service.getOwnersForDomain(DOMAIN_2, 1).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
         manager1.getOwnersForDomain(DOMAIN_2).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
 
         // Now assert for user 1 that it was unaffected by the change to user 0
         service.getOwnersForDomain(DOMAIN_1, 1).let {
             assertThat(it).containsExactly(
-                DomainOwner(pkg1.getPackageName(), false),
-                DomainOwner(pkg2.getPackageName(), false)
+                DomainOwner(pkg1.packageName, false),
+                DomainOwner(pkg2.packageName, false)
             ).inOrder()
         }
         manager1.getOwnersForDomain(DOMAIN_1).let {
             assertThat(it).containsExactly(
-                DomainOwner(pkg1.getPackageName(), false),
-                DomainOwner(pkg2.getPackageName(), false)
+                DomainOwner(pkg1.packageName, false),
+                DomainOwner(pkg2.packageName, false)
             ).inOrder()
         }
 
         service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
 
         service.getOwnersForDomain(DOMAIN_2, 1).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
         manager1.getOwnersForDomain(DOMAIN_2).let {
-            assertThat(it).containsExactly(DomainOwner(pkg1.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg1.packageName, true))
         }
 
         // "Reinstall" the package to user 0
@@ -406,10 +425,10 @@
         // Other package unaffected
         service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
         service.getOwnersForDomain(DOMAIN_2, 0).let {
-            assertThat(it).containsExactly(DomainOwner(pkg2.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg2.packageName, true))
         }
         manager0.getOwnersForDomain(DOMAIN_2).let {
-            assertThat(it).containsExactly(DomainOwner(pkg2.getPackageName(), true))
+            assertThat(it).containsExactly(DomainOwner(pkg2.packageName, true))
         }
     }
 
@@ -456,42 +475,44 @@
     }
 
     private fun makeService(vararg pkgSettings: PackageSetting) =
-            makeService { pkgName -> pkgSettings.find { pkgName == it.getPackageName() } }
+        makeService { pkgName -> pkgSettings.find { pkgName == it.packageName } }
 
     private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
-            DomainVerificationService(mockThrowOnUnmocked {
-                // Assume the test has every permission necessary
-                whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
-                whenever(checkPermission(anyString(), anyInt(), anyInt())) {
-                    PackageManager.PERMISSION_GRANTED
-                }
-            }, mockThrowOnUnmocked {
-                whenever(linkedApps) { ArraySet<String>() }
-            }, mockThrowOnUnmocked {
-                whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
-            }).apply {
-                setConnection(mockThrowOnUnmocked {
-                    whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
-                    whenever(doesUserExist(0)) { true }
-                    whenever(doesUserExist(1)) { true }
-                    whenever(scheduleWriteSettings())
-
-                    // Need to provide an internal UID so some permission checks are ignored
-                    whenever(callingUid) { Process.ROOT_UID }
-                    whenever(callingUserId) { 0 }
-
-                    mockPackageSettings {
-                        pkgSettingFunction(it)
-                    }
-                })
+        DomainVerificationService(mockThrowOnUnmocked {
+            // Assume the test has every permission necessary
+            whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
+            whenever(checkPermission(anyString(), anyInt(), anyInt())) {
+                PackageManager.PERMISSION_GRANTED
             }
+        }, mockThrowOnUnmocked {
+            whenever(linkedApps) { ArraySet<String>() }
+        }, mockThrowOnUnmocked {
+            whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
+        }).apply {
+            setConnection(mockThrowOnUnmocked {
+                whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+                whenever(doesUserExist(0)) { true }
+                whenever(doesUserExist(1)) { true }
+                whenever(scheduleWriteSettings())
+
+                // Need to provide an internal UID so some permission checks are ignored
+                whenever(callingUid) { Process.ROOT_UID }
+                whenever(callingUserId) { 0 }
+
+                mockPackageSettings {
+                    pkgSettingFunction(it)
+                }
+            })
+        }
 
     private fun mockPkgSetting(
         pkgName: String,
         domainSetId: UUID,
         domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
-        pkgUserState0: PackageSetting.() -> PackageUserState = { PackageUserState() },
-        pkgUserState1: PackageSetting.() -> PackageUserState = { PackageUserState() }
+        pkgUserState0: PackageSetting.() -> PackageUserStateInternal = {
+            PackageUserStateInternal.DEFAULT },
+        pkgUserState1: PackageSetting.() -> PackageUserStateInternal = {
+            PackageUserStateInternal.DEFAULT }
     ) = mockThrowOnUnmocked<PackageSetting> {
         val pkg = mockThrowOnUnmocked<AndroidPackage> {
             whenever(packageName) { pkgName }
@@ -499,41 +520,43 @@
             whenever(isEnabled) { true }
 
             val activityList = listOf(
-                    ParsedActivity().apply {
-                        domains.forEach {
-                            addIntent(
-                                    ParsedIntentInfo().apply {
-                                        autoVerify = true
-                                        addAction(Intent.ACTION_VIEW)
-                                        addCategory(Intent.CATEGORY_BROWSABLE)
-                                        addCategory(Intent.CATEGORY_DEFAULT)
-                                        addDataScheme("http")
-                                        addDataScheme("https")
-                                        addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
-                                        addDataAuthority(it, null)
-                                    }
-                            )
-                        }
+                ParsedActivity().apply {
+                    domains.forEach {
+                        addIntent(
+                            ParsedIntentInfo().apply {
+                                autoVerify = true
+                                addAction(Intent.ACTION_VIEW)
+                                addCategory(Intent.CATEGORY_BROWSABLE)
+                                addCategory(Intent.CATEGORY_DEFAULT)
+                                addDataScheme("http")
+                                addDataScheme("https")
+                                addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                                addDataAuthority(it, null)
+                            }
+                        )
                     }
+                }
             )
 
             whenever(activities) { activityList }
         }
 
         whenever(getPkg()) { pkg }
-        whenever(getPackageName()) { pkgName }
+        whenever(packageName) { pkgName }
         whenever(this.domainSetId) { domainSetId }
         whenever(getInstantApp(anyInt())) { false }
         whenever(firstInstallTime) { 0L }
         whenever(readUserState(0)) { pkgUserState0() }
         whenever(readUserState(1)) { pkgUserState1() }
-        whenever(isSystem()) { false }
+        whenever(isSystem) { false }
     }
 
     private fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =
-            pkgSettings.forEach(::addPackage)
+        pkgSettings.forEach(::addPackage)
 
     private fun makeManager(service: DomainVerificationService, userId: Int) =
-        DomainVerificationManager(mockThrowOnUnmocked { whenever(this.userId) { userId } },
-            DomainVerificationManagerStub(service))
+        DomainVerificationManager(
+            mockThrowOnUnmocked { whenever(this.userId) { userId } },
+            DomainVerificationManagerStub(service)
+        )
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 74ae7a9..dbec56f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -18,11 +18,11 @@
 
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.content.pm.PackageUserState
 import android.content.pm.Signature
 import android.content.pm.SigningDetails
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainOwner
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
@@ -834,13 +834,13 @@
             whenever(activities) { activityList }
         }
 
-        whenever(getPkg()) { pkg }
+        whenever(this.pkg) { pkg }
         whenever(packageName) { pkgName }
         whenever(this.domainSetId) { domainSetId }
         whenever(getInstantApp(anyInt())) { false }
         whenever(firstInstallTime) { 0L }
-        whenever(readUserState(0)) { PackageUserState() }
-        whenever(readUserState(10)) { PackageUserState() }
+        whenever(readUserState(0)) { PackageUserStateInternal.DEFAULT }
+        whenever(readUserState(10)) { PackageUserStateInternal.DEFAULT }
         whenever(isSystem) { isSystemApp }
 
         val mockSigningDetails = SigningDetails(arrayOf(spy(Signature(signature)) {
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 32866ea..8125128 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
@@ -19,9 +19,10 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.content.pm.PackageUserState
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.pkg.PackageUserState
+import android.content.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainVerificationState
 import android.os.Build
 import android.os.Process
@@ -231,13 +232,13 @@
                 TEST_UUID
             )
         ) {
-            whenever(getPackageName()) { TEST_PKG }
-            whenever(getPkg()) { mockPkg() }
+            whenever(packageName) { TEST_PKG }
+            whenever(pkg) { mockPkg() }
             whenever(domainSetId) { TEST_UUID }
-            whenever(readUserState(0)) { PackageUserState() }
-            whenever(readUserState(10)) { PackageUserState() }
+            whenever(readUserState(0)) { PackageUserStateInternal.DEFAULT }
+            whenever(readUserState(10)) { PackageUserStateInternal.DEFAULT }
             whenever(getInstantApp(anyInt())) { false }
-            whenever(isSystem()) { false }
+            whenever(isSystem) { false }
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 1d5e280..1711355 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -18,9 +18,10 @@
 
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.content.pm.PackageUserState
 import android.content.pm.parsing.component.ParsedActivity
 import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.pkg.PackageUserState
+import android.content.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.verify.domain.DomainVerificationState
 import android.content.pm.verify.domain.DomainVerificationUserState
@@ -138,14 +139,14 @@
             whenever(activities) { activityList }
         }
 
-        whenever(getPkg()) { pkg }
-        whenever(getPackageName()) { pkgName }
+        whenever(this.pkg) { pkg }
+        whenever(packageName) { pkgName }
         whenever(this.domainSetId) { domainSetId }
         whenever(getInstantApp(anyInt())) { false }
         whenever(firstInstallTime) { 0L }
-        whenever(readUserState(0)) { PackageUserState() }
-        whenever(readUserState(1)) { PackageUserState() }
-        whenever(isSystem()) { false }
+        whenever(readUserState(0)) { PackageUserStateInternal.DEFAULT }
+        whenever(readUserState(1)) { PackageUserStateInternal.DEFAULT }
+        whenever(isSystem) { false }
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
new file mode 100644
index 0000000..98e089e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.eq;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.controllers.PrefetchController.PcConstants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class PrefetchControllerTest {
+    private PrefetchController mPrefetchController;
+    private PcConstants mPcConstants;
+    private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
+
+    private MockitoSession mMockingSession;
+    @Mock
+    private Context mContext;
+    @Mock
+    private JobSchedulerService mJobSchedulerService;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(DeviceConfig.class)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+
+        // Called in StateController constructor.
+        when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+        when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+        // Used in PrefetchController.PcConstants
+        doAnswer((Answer<Void>) invocationOnMock -> null)
+                .when(() -> DeviceConfig.addOnPropertiesChangedListener(
+                        anyString(), any(Executor.class),
+                        any(DeviceConfig.OnPropertiesChangedListener.class)));
+        mDeviceConfigPropertiesBuilder =
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+        doAnswer(
+                (Answer<DeviceConfig.Properties>) invocationOnMock
+                        -> mDeviceConfigPropertiesBuilder.build())
+                .when(() -> DeviceConfig.getProperties(
+                        eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
+
+        // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+        // in the past, and PrefetchController sometimes floors values at 0, so if the test time
+        // causes sessions with negative timestamps, they will fail.
+        JobSchedulerService.sSystemClock =
+                getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
+                        24 * HOUR_IN_MILLIS);
+        JobSchedulerService.sUptimeMillisClock = getShiftedClock(
+                Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
+                24 * HOUR_IN_MILLIS);
+        JobSchedulerService.sElapsedRealtimeClock = getShiftedClock(
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
+                24 * HOUR_IN_MILLIS);
+
+        // Initialize real objects.
+        // Capture the listeners.
+        mPrefetchController = new PrefetchController(mJobSchedulerService);
+        mPcConstants = mPrefetchController.getPcConstants();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private Clock getShiftedClock(Clock clock, long incrementMs) {
+        return Clock.offset(clock, Duration.ofMillis(incrementMs));
+    }
+
+    private void setDeviceConfigLong(String key, long val) {
+        mDeviceConfigPropertiesBuilder.setLong(key, val);
+        synchronized (mPrefetchController.mLock) {
+            mPrefetchController.prepareForUpdatedConstantsLocked();
+            mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+        }
+    }
+
+    @Test
+    public void testConstantsUpdating_ValidValues() {
+        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 5 * HOUR_IN_MILLIS);
+
+        assertEquals(5 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
+    }
+
+    @Test
+    public void testConstantsUpdating_InvalidValues() {
+        // Test negatives/too low.
+        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 4 * MINUTE_IN_MILLIS);
+
+        assertEquals(HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
+
+        // Test larger than a day. Controller should cap at one day.
+        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 25 * HOUR_IN_MILLIS);
+
+        assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 0dd5c61..418831f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -26,6 +26,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
 import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -74,6 +75,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -110,6 +112,7 @@
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int DISPLAY_ID = 0;
+    private static final float TRANSITION_POINT = 0.763f;
 
     private Context mContext;
     private FakesInjector mInjector;
@@ -751,19 +754,27 @@
 
         director.start(sensorManager);
 
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
         Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
                 .registerListener(
-                        listenerCaptor.capture(),
+                        sensorListenerCaptor.capture(),
                         eq(lightSensor),
                         anyInt(),
                         any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
+        SensorEventListener sensorListener = sensorListenerCaptor.getValue();
 
-        setBrightness(10);
+        setBrightness(10, 10, displayListener);
         // Sensor reads 20 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
 
         Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
         assertVoteForRefreshRate(vote, 90 /*fps*/);
@@ -771,9 +782,11 @@
         assertThat(vote).isNotNull();
         assertThat(vote.disableRefreshRateSwitching).isTrue();
 
-        setBrightness(125);
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(10, 125, displayListener);
         // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
 
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
         assertThat(vote).isNull();
@@ -799,6 +812,14 @@
 
         director.start(sensorManager);
 
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
         verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
@@ -807,20 +828,22 @@
                         eq(lightSensor),
                         anyInt(),
                         any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
+        SensorEventListener sensorListener = listenerCaptor.getValue();
 
-        setBrightness(100);
+        setBrightness(100, 100, displayListener);
         // Sensor reads 2000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
 
         Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
         assertThat(vote).isNull();
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNull();
 
-        setBrightness(255);
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(100, 255, displayListener);
         // Sensor reads 9000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
 
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
         assertVoteForRefreshRate(vote, 60 /*fps*/);
@@ -1435,16 +1458,58 @@
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
 
-        // Turn on HBM
+        // Turn on HBM, with brightness in the HBM range
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR));
+                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForRefreshRate(vote, hbmRefreshRate);
+
+        // Turn on HBM, with brightness below the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM, with brightness in the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertVoteForRefreshRate(vote, hbmRefreshRate);
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM, with brightness below the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
@@ -1514,7 +1579,8 @@
 
         // Turn on HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertVoteForRefreshRate(vote, initialRefreshRate);
@@ -1531,14 +1597,16 @@
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
 
         // Turn HBM on again and ensure the updated vote value stuck
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertVoteForRefreshRate(vote, updatedRefreshRate);
@@ -1553,7 +1621,8 @@
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
@@ -1584,14 +1653,82 @@
 
         // Turn on HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testHbmVoting_HbmUnsupported() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Limitation
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        60.0f, 60.0f)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForRefreshRate(vote, 60.0f);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
+        // no vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    HBM_TRANSITION_POINT_INVALID));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
+        // no vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
+                    HBM_TRANSITION_POINT_INVALID));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertNull(vote);
@@ -1600,7 +1737,7 @@
     private void setHbmAndAssertRefreshRate(
             DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
         when(mInjector.getBrightnessInfo(DISPLAY_ID))
-                .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode));
+                .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
 
         final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1679,7 +1816,8 @@
 
         // Turn on HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
         assertVoteForRefreshRate(vote, 60.f);
@@ -1834,11 +1972,14 @@
         }
     }
 
-    private void setBrightness(int brightness) {
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
-                brightness);
-        mInjector.notifyBrightnessChanged();
-        waitForIdleSync();
+    private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
+        float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
+        float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
+
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+        listener.onDisplayChanged(DISPLAY_ID);
     }
 
     private void setPeakRefreshRate(float fps) {
@@ -1902,27 +2043,6 @@
         }
 
         @Override
-        public void registerBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            if (mBrightnessObserver != null) {
-                throw new IllegalStateException("Tried to register a second brightness observer");
-            }
-            mBrightnessObserver = observer;
-        }
-
-        @Override
-        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            mBrightnessObserver = null;
-        }
-
-        void notifyBrightnessChanged() {
-            if (mBrightnessObserver != null) {
-                mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
-            }
-        }
-
-        @Override
         public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
             mPeakRefreshRateObserver = observer;
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index cc3591c8..aca8632 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,8 @@
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
 
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
@@ -124,6 +126,7 @@
                 mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
                 DEFAULT_MAX, null, () -> {}, mContextSpy);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
     }
 
     @Test
@@ -135,6 +138,7 @@
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index 0247590..f8c3bf3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -25,17 +25,16 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageUserState;
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Build;
 
 import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageUserStateInternalImpl;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,16 +44,14 @@
 
     private boolean mCompatibilityModeEnabled;;
     private PackageImpl mMockAndroidPackage;
-    private PackageUserState mMockUserState;
+    private PackageUserStateInternalImpl mMockUserState;
 
     @Before
     public void setUp() {
         mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
         mMockAndroidPackage = mock(PackageImpl.class);
-        mMockUserState = mock(PackageUserState.class);
-        mMockUserState.installed = true;
-        when(mMockUserState.isAvailable(anyInt())).thenReturn(true);
-        when(mMockUserState.getAllOverlayPaths()).thenReturn(null);
+        mMockUserState = new PackageUserStateInternalImpl();
+        mMockUserState.setInstalled(true);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 33099bb..24377a9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -40,9 +40,10 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.pkg.PackageUserState;
+import android.content.pm.pkg.PackageUserStateInternal;
 import android.os.BaseBundle;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -208,24 +209,24 @@
         settingsUnderTest.readPackageRestrictionsLPr(0);
 
         PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
-        PackageUserState packageUserState1 = ps1.readUserState(0);
-        assertThat(packageUserState1.suspended, is(true));
-        assertThat(packageUserState1.suspendParams.size(), is(1));
-        assertThat(packageUserState1.suspendParams.keyAt(0), is("android"));
-        assertThat(packageUserState1.suspendParams.valueAt(0), is(nullValue()));
+        PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0), is(nullValue()));
 
         // Verify that the snapshot returns the same answers
         ps1 = snapshot.mPackages.get(PACKAGE_NAME_1);
         packageUserState1 = ps1.readUserState(0);
-        assertThat(packageUserState1.suspended, is(true));
-        assertThat(packageUserState1.suspendParams.size(), is(1));
-        assertThat(packageUserState1.suspendParams.keyAt(0), is("android"));
-        assertThat(packageUserState1.suspendParams.valueAt(0), is(nullValue()));
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0), is(nullValue()));
 
         PackageSetting ps2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2);
-        PackageUserState packageUserState2 = ps2.readUserState(0);
-        assertThat(packageUserState2.suspended, is(false));
-        assertThat(packageUserState2.suspendParams, is(nullValue()));
+        PackageUserStateInternal packageUserState2 = ps2.readUserState(0);
+        assertThat(packageUserState2.isSuspended(), is(false));
+        assertThat(packageUserState2.getSuspendParams(), is(nullValue()));
 
         // Verify that the snapshot returns different answers
         ps2 = snapshot.mPackages.get(PACKAGE_NAME_2);
@@ -246,12 +247,13 @@
 
         final PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
         watcher.verifyNoChangeReported("get package 1");
-        final PackageUserState packageUserState1 = ps1.readUserState(0);
+        final PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
         watcher.verifyNoChangeReported("readUserState");
-        assertThat(packageUserState1.suspended, is(true));
-        assertThat(packageUserState1.suspendParams.size(), is(1));
-        assertThat(packageUserState1.suspendParams.keyAt(0), is(PACKAGE_NAME_3));
-        final PackageUserState.SuspendParams params = packageUserState1.suspendParams.valueAt(0);
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
+        final PackageUserState.SuspendParams params =
+                packageUserState1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("fetch user state");
         assertThat(params, is(notNullValue()));
         assertThat(params.appExtras.size(), is(1));
@@ -330,15 +332,15 @@
         // now read and verify
         settingsUnderTest.readPackageRestrictionsLPr(0);
         watcher.verifyChangeReported("readPackageRestrictions");
-        final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
+        final PackageUserStateInternal readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
                 .readUserState(0);
         watcher.verifyNoChangeReported("package get 1");
-        assertThat(readPus1.suspended, is(true));
-        assertThat(readPus1.suspendParams.size(), is(2));
+        assertThat(readPus1.isSuspended(), is(true));
+        assertThat(readPus1.getSuspendParams().size(), is(2));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.suspendParams.keyAt(0), is("suspendingPackage1"));
-        final PackageUserState.SuspendParams params11 = readPus1.suspendParams.valueAt(0);
+        assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
+        final PackageUserState.SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("read package param");
         assertThat(params11, is(notNullValue()));
         assertThat(params11.dialogInfo, is(dialogInfo1));
@@ -347,8 +349,8 @@
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.suspendParams.keyAt(1), is("suspendingPackage2"));
-        final PackageUserState.SuspendParams params12 = readPus1.suspendParams.valueAt(1);
+        assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
+        final PackageUserState.SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
         assertThat(params12, is(notNullValue()));
         assertThat(params12.dialogInfo, is(dialogInfo2));
         assertThat(BaseBundle.kindofEquals(params12.appExtras, appExtras2), is(true));
@@ -356,22 +358,22 @@
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
-        final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
+        final PackageUserStateInternal readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
                 .readUserState(0);
-        assertThat(readPus2.suspended, is(true));
-        assertThat(readPus2.suspendParams.size(), is(1));
-        assertThat(readPus2.suspendParams.keyAt(0), is("suspendingPackage3"));
-        final PackageUserState.SuspendParams params21 = readPus2.suspendParams.valueAt(0);
+        assertThat(readPus2.isSuspended(), is(true));
+        assertThat(readPus2.getSuspendParams().size(), is(1));
+        assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
+        final PackageUserState.SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
         assertThat(params21, is(notNullValue()));
         assertThat(params21.dialogInfo, is(nullValue()));
         assertThat(BaseBundle.kindofEquals(params21.appExtras, appExtras1), is(true));
         assertThat(params21.launcherExtras, is(nullValue()));
         watcher.verifyNoChangeReported("read package param");
 
-        final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
+        final PackageUserStateInternal readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
                 .readUserState(0);
-        assertThat(readPus3.suspended, is(false));
-        assertThat(readPus3.suspendParams, is(nullValue()));
+        assertThat(readPus3.isSuspended(), is(false));
+        assertThat(readPus3.getSuspendParams(), is(nullValue()));
         watcher.verifyNoChangeReported("package get 3");
     }
 
@@ -411,15 +413,15 @@
         settingsUnderTest.readPackageRestrictionsLPr(0);
         final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
                 .readUserState(0);
-        assertThat(readPus1.distractionFlags, is(distractionFlags1));
+        assertThat(readPus1.getDistractionFlags(), is(distractionFlags1));
 
         final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
                 .readUserState(0);
-        assertThat(readPus2.distractionFlags, is(distractionFlags2));
+        assertThat(readPus2.getDistractionFlags(), is(distractionFlags2));
 
         final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
                 .readUserState(0);
-        assertThat(readPus3.distractionFlags, is(distractionFlags3));
+        assertThat(readPus3.getDistractionFlags(), is(distractionFlags3));
     }
 
     @Test
@@ -895,13 +897,13 @@
 
     private void verifyUserState(PackageUserState userState, PackageUserState oldUserState,
             boolean userStateChanged, boolean notLaunched, boolean stopped, boolean installed) {
-        assertThat(userState.enabled, is(0));
-        assertThat(userState.hidden, is(false));
-        assertThat(userState.installed, is(installed));
-        assertThat(userState.notLaunched, is(notLaunched));
-        assertThat(userState.stopped, is(stopped));
-        assertThat(userState.suspended, is(false));
-        assertThat(userState.distractionFlags, is(0));
+        assertThat(userState.getEnabledState(), is(0));
+        assertThat(userState.isHidden(), is(false));
+        assertThat(userState.isInstalled(), is(installed));
+        assertThat(userState.isNotLaunched(), is(notLaunched));
+        assertThat(userState.isStopped(), is(stopped));
+        assertThat(userState.isSuspended(), is(false));
+        assertThat(userState.getDistractionFlags(), is(0));
         if (oldUserState != null) {
             assertThat(userState.equals(oldUserState), is(not(userStateChanged)));
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 4113332..c85b2e8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -40,7 +40,6 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.Property;
-import android.content.pm.PackageUserState;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
@@ -54,6 +53,7 @@
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
 import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.pkg.PackageUserState;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -645,9 +645,9 @@
         assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
 
         PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), new PackageUserState(), 0, mockPkgSetting(a));
+                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(a));
         PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), new PackageUserState(), 0, mockPkgSetting(b));
+                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(b));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
 
         assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
@@ -802,9 +802,9 @@
 
         // Validity check for ServiceInfo.
         ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
-                new PackageUserState(), 0, mockPkgSetting(aPkg));
+                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
         ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
-                new PackageUserState(), 0, mockPkgSetting(bPkg));
+                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
@@ -829,9 +829,9 @@
 
         // Validity check for ActivityInfo.
         ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
-                new PackageUserState(), 0, mockPkgSetting(aPkg));
+                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
         ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
-                new PackageUserState(), 0, mockPkgSetting(bPkg));
+                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 93aae28..f1c8dd6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -16,12 +16,12 @@
 
 package com.android.server.pm;
 
-import android.content.pm.PackageUserState;
 import android.content.pm.SigningDetails;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageUserStateInternalImpl;
 
 import java.io.File;
 import java.util.Map;
@@ -41,7 +41,7 @@
     private int mSharedUserId;
     private String mVolumeUuid;
     private int mAppId;
-    private SparseArray<PackageUserState> mUserStates = new SparseArray<>();
+    private SparseArray<PackageUserStateInternalImpl> mUserStates = new SparseArray<>();
     private AndroidPackage mPkg;
     private InstallSource mInstallSource;
     private String[] mUsesStaticLibraries;
@@ -139,17 +139,17 @@
 
     public PackageSettingBuilder setInstantAppUserState(int userId, boolean isInstant) {
         if (mUserStates.indexOfKey(userId) < 0) {
-            mUserStates.put(userId, new PackageUserState());
+            mUserStates.put(userId, new PackageUserStateInternalImpl());
         }
-        mUserStates.get(userId).instantApp = isInstant;
+        mUserStates.get(userId).setInstantApp(isInstant);
         return this;
     }
 
     public PackageSettingBuilder setInstallState(int userId, boolean installed) {
         if (mUserStates.indexOfKey(userId) < 0) {
-            mUserStates.put(userId, new PackageUserState());
+            mUserStates.put(userId, new PackageUserStateInternalImpl());
         }
-        mUserStates.get(userId).installed = installed;
+        mUserStates.get(userId).setInstalled(installed);
         return this;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 7297622..5376298 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -17,7 +17,6 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -26,9 +25,9 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.pm.PackageManager;
-import android.content.pm.PackageUserState;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.pkg.PackageUserState;
 import android.os.PersistableBundle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -37,6 +36,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.pm.pkg.PackageStateUnserialized;
+import com.android.server.pm.pkg.PackageUserStateInternalImpl;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,142 +47,141 @@
 
     @Test
     public void testPackageUserState01() {
-        final PackageUserState testUserState = new PackageUserState();
-        PackageUserState oldUserState;
+        final PackageUserStateInternalImpl testUserState = new PackageUserStateInternalImpl();
+        PackageUserStateInternalImpl oldUserState;
 
-        oldUserState = new PackageUserState();
+        oldUserState = new PackageUserStateInternalImpl();
         assertThat(testUserState.equals(null), is(false));
         assertThat(testUserState.equals(testUserState), is(true));
         assertThat(testUserState.equals(oldUserState), is(true));
 
-        oldUserState = new PackageUserState();
-        oldUserState.ceDataInode = 4000L;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setCeDataInode(4000L);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.enabled = COMPONENT_ENABLED_STATE_ENABLED;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setEnabledState(COMPONENT_ENABLED_STATE_ENABLED);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.hidden = true;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setHidden(true);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.installed = false;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setInstalled(false);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.notLaunched = true;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setNotLaunched(true);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.stopped = true;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setStopped(true);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.suspended = true;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setSuspended(true);
         assertThat(testUserState.equals(oldUserState), is(false));
 
-        oldUserState = new PackageUserState();
-        oldUserState.uninstallReason = PackageManager.UNINSTALL_REASON_USER_TYPE;
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setUninstallReason(PackageManager.UNINSTALL_REASON_USER_TYPE);
         assertThat(testUserState.equals(oldUserState), is(false));
     }
 
     @Test
     public void testPackageUserState02() {
-        final PackageUserState testUserState01 = new PackageUserState();
-        PackageUserState oldUserState;
+        final PackageUserStateInternalImpl testUserState01 = new PackageUserStateInternalImpl();
+        PackageUserStateInternalImpl oldUserState;
 
-        oldUserState = new PackageUserState();
-        oldUserState.lastDisableAppCaller = "unit_test";
+        oldUserState = new PackageUserStateInternalImpl();
+        oldUserState.setLastDisableAppCaller("unit_test");
         assertThat(testUserState01.equals(oldUserState), is(false));
 
-        final PackageUserState testUserState02 = new PackageUserState();
-        testUserState02.lastDisableAppCaller = "unit_test";
+        final PackageUserStateInternalImpl testUserState02 = new PackageUserStateInternalImpl();
+        testUserState02.setLastDisableAppCaller("unit_test");
         assertThat(testUserState02.equals(oldUserState), is(true));
 
-        final PackageUserState testUserState03 = new PackageUserState();
-        testUserState03.lastDisableAppCaller = "unit_test_00";
+        final PackageUserStateInternalImpl testUserState03 = new PackageUserStateInternalImpl();
+        testUserState03.setLastDisableAppCaller("unit_test_00");
         assertThat(testUserState03.equals(oldUserState), is(false));
     }
 
     @Test
     public void testPackageUserState03() {
-        final PackageUserState oldUserState = new PackageUserState();
+        final PackageUserStateInternalImpl oldUserState = new PackageUserStateInternalImpl();
 
         // only new user state has array defined; different
-        final PackageUserState testUserState01 = new PackageUserState();
-        testUserState01.disabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState01 = new PackageUserStateInternalImpl();
+        testUserState01.setDisabledComponents(new ArraySet<>());
         assertThat(testUserState01.equals(oldUserState), is(false));
 
         // only old user state has array defined; different
-        final PackageUserState testUserState02 = new PackageUserState();
-        oldUserState.disabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState02 = new PackageUserStateInternalImpl();
+        oldUserState.setDisabledComponents(new ArraySet<>());
         assertThat(testUserState02.equals(oldUserState), is(false));
 
         // both states have array defined; not different
-        final PackageUserState testUserState03 = new PackageUserState();
-        testUserState03.disabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState03 = new PackageUserStateInternalImpl();
+        testUserState03.setDisabledComponents(new ArraySet<>());
         assertThat(testUserState03.equals(oldUserState), is(true));
         // fewer elements in old user state; different
-        testUserState03.disabledComponents.add("com.android.unit_test01");
-        testUserState03.disabledComponents.add("com.android.unit_test02");
-        testUserState03.disabledComponents.add("com.android.unit_test03");
-        oldUserState.disabledComponents.add("com.android.unit_test03");
-        oldUserState.disabledComponents.add("com.android.unit_test02");
+        testUserState03.getDisabledComponentsNoCopy().add("com.android.unit_test01");
+        testUserState03.getDisabledComponentsNoCopy().add("com.android.unit_test02");
+        testUserState03.getDisabledComponentsNoCopy().add("com.android.unit_test03");
+        oldUserState.getDisabledComponentsNoCopy().add("com.android.unit_test03");
+        oldUserState.getDisabledComponentsNoCopy().add("com.android.unit_test02");
         assertThat(testUserState03.equals(oldUserState), is(false));
         // same elements in old user state; not different
-        oldUserState.disabledComponents.add("com.android.unit_test01");
+        oldUserState.getDisabledComponentsNoCopy().add("com.android.unit_test01");
         assertThat(testUserState03.equals(oldUserState), is(true));
         // more elements in old user state; different
-        oldUserState.disabledComponents.add("com.android.unit_test04");
+        oldUserState.getDisabledComponentsNoCopy().add("com.android.unit_test04");
         assertThat(testUserState03.equals(oldUserState), is(false));
         // different elements in old user state; different
-        testUserState03.disabledComponents.add("com.android.unit_test_04");
+        testUserState03.getDisabledComponentsNoCopy().add("com.android.unit_test_04");
         assertThat(testUserState03.equals(oldUserState), is(false));
     }
 
     @Test
     public void testPackageUserState04() {
-        final PackageUserState oldUserState = new PackageUserState();
+        final PackageUserStateInternalImpl oldUserState = new PackageUserStateInternalImpl();
 
         // only new user state has array defined; different
-        final PackageUserState testUserState01 = new PackageUserState();
-        testUserState01.enabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState01 = new PackageUserStateInternalImpl();
+        testUserState01.setEnabledComponents(new ArraySet<>());
         assertThat(testUserState01.equals(oldUserState), is(false));
 
         // only old user state has array defined; different
-        final PackageUserState testUserState02 = new PackageUserState();
-        oldUserState.enabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState02 = new PackageUserStateInternalImpl();
+        oldUserState.setEnabledComponents(new ArraySet<>());
         assertThat(testUserState02.equals(oldUserState), is(false));
 
         // both states have array defined; not different
-        final PackageUserState testUserState03 = new PackageUserState();
-        testUserState03.enabledComponents = new ArraySet<>();
+        final PackageUserStateInternalImpl testUserState03 = new PackageUserStateInternalImpl();
+        testUserState03.setEnabledComponents(new ArraySet<>());
         assertThat(testUserState03.equals(oldUserState), is(true));
         // fewer elements in old user state; different
-        testUserState03.enabledComponents.add("com.android.unit_test01");
-        testUserState03.enabledComponents.add("com.android.unit_test02");
-        testUserState03.enabledComponents.add("com.android.unit_test03");
-        oldUserState.enabledComponents.add("com.android.unit_test03");
-        oldUserState.enabledComponents.add("com.android.unit_test02");
+        testUserState03.getEnabledComponentsNoCopy().add("com.android.unit_test01");
+        testUserState03.getEnabledComponentsNoCopy().add("com.android.unit_test02");
+        testUserState03.getEnabledComponentsNoCopy().add("com.android.unit_test03");
+        oldUserState.getEnabledComponentsNoCopy().add("com.android.unit_test03");
+        oldUserState.getEnabledComponentsNoCopy().add("com.android.unit_test02");
         assertThat(testUserState03.equals(oldUserState), is(false));
         // same elements in old user state; not different
-        oldUserState.enabledComponents.add("com.android.unit_test01");
+        oldUserState.getEnabledComponentsNoCopy().add("com.android.unit_test01");
         assertThat(testUserState03.equals(oldUserState), is(true));
         // more elements in old user state; different
-        oldUserState.enabledComponents.add("com.android.unit_test04");
+        oldUserState.getEnabledComponentsNoCopy().add("com.android.unit_test04");
         assertThat(testUserState03.equals(oldUserState), is(false));
         // different elements in old user state; different
-        testUserState03.enabledComponents.add("com.android.unit_test_04");
+        testUserState03.getEnabledComponentsNoCopy().add("com.android.unit_test_04");
         assertThat(testUserState03.equals(oldUserState), is(false));
     }
 
     private static PackageUserState.SuspendParams createSuspendParams(SuspendDialogInfo dialogInfo,
             PersistableBundle appExtras, PersistableBundle launcherExtras) {
-        PackageUserState.SuspendParams obj = PackageUserState.SuspendParams.getInstanceOrNull(
+        return PackageUserState.SuspendParams.getInstanceOrNull(
                 dialogInfo, appExtras, launcherExtras);
-        return obj;
     }
 
     private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
@@ -230,29 +229,32 @@
                 appExtras2, launcherExtras2));
 
 
-        final PackageUserState testUserState1 = new PackageUserState();
-        testUserState1.suspended = true;
-        testUserState1.suspendParams = paramsMap1;
+        final PackageUserStateInternalImpl testUserState1 = new PackageUserStateInternalImpl();
+        testUserState1.setSuspended(true);
+        testUserState1.setSuspendParams(paramsMap1);
 
-        PackageUserState testUserState2 = new PackageUserState(testUserState1);
+        PackageUserStateInternalImpl testUserState2 =
+                new PackageUserStateInternalImpl(testUserState1);
         assertThat(testUserState1.equals(testUserState2), is(true));
-        testUserState2.suspendParams = paramsMap2;
+        testUserState2.setSuspendParams(paramsMap2);
         // Should not be equal since suspendParams maps are different
         assertThat(testUserState1.equals(testUserState2), is(false));
     }
 
     @Test
     public void testPackageUserState06() {
-        final PackageUserState userState1 = new PackageUserState();
-        assertThat(userState1.distractionFlags, is(PackageManager.RESTRICTION_NONE));
-        userState1.distractionFlags = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        final PackageUserStateInternalImpl userState1 = new PackageUserStateInternalImpl();
+        assertThat(userState1.getDistractionFlags(), is(PackageManager.RESTRICTION_NONE));
+        userState1.setDistractionFlags(PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS);
 
-        final PackageUserState copyOfUserState1 = new PackageUserState(userState1);
-        assertThat(userState1.distractionFlags, is(copyOfUserState1.distractionFlags));
+        final PackageUserStateInternalImpl copyOfUserState1 =
+                new PackageUserStateInternalImpl(userState1);
+        assertThat(userState1.getDistractionFlags(), is(copyOfUserState1.getDistractionFlags()));
         assertThat(userState1.equals(copyOfUserState1), is(true));
 
-        final PackageUserState userState2 = new PackageUserState(userState1);
-        userState2.distractionFlags = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
+        final PackageUserStateInternalImpl userState2 =
+                new PackageUserStateInternalImpl(userState1);
+        userState2.setDistractionFlags(PackageManager.RESTRICTION_HIDE_NOTIFICATIONS);
         assertThat(userState1.equals(userState2), is(false));
     }
 
@@ -352,7 +354,7 @@
 
     @Test
     public void testOverlayPaths() {
-        final PackageUserState testState = new PackageUserState();
+        final PackageUserStateInternalImpl testState = new PackageUserStateInternalImpl();
         assertFalse(testState.setOverlayPaths(null));
         assertFalse(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
 
@@ -366,7 +368,7 @@
     }
     @Test
     public void testSharedLibOverlayPaths() {
-        final PackageUserState testState = new PackageUserState();
+        final PackageUserStateInternalImpl testState = new PackageUserStateInternalImpl();
         final String LIB_ONE = "lib1";
         final String LIB_TW0 = "lib2";
         assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 370b1c9..82bf2f4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -158,7 +158,7 @@
         final ScanResult scanResult = executeScan(scanRequest);
 
         for (int uid : userIds) {
-            assertThat(scanResult.mPkgSetting.readUserState(uid).installed, is(true));
+            assertThat(scanResult.mPkgSetting.readUserState(uid).isInstalled(), is(true));
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 5d3905a..0602a55 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -19,13 +19,11 @@
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
-import android.content.pm.ComponentInfo
 import android.content.pm.ConfigurationInfo
 import android.content.pm.FeatureInfo
 import android.content.pm.InstrumentationInfo
 import android.content.pm.PackageInfo
 import android.content.pm.PackageParser
-import android.content.pm.PackageUserState
 import android.content.pm.PermissionInfo
 import android.content.pm.ProviderInfo
 import android.content.pm.ServiceInfo
@@ -40,14 +38,11 @@
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateUnserialized
+import com.android.server.pm.pkg.PackageUserStateInternalImpl
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import org.junit.BeforeClass
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.mock
 import java.io.File
 
 open class AndroidPackageParsingTestBase {
@@ -90,13 +85,7 @@
                 }
                 .distinct()
 
-        private val dummyUserState = mock(PackageUserState::class.java).apply {
-            installed = true
-            whenever(isAvailable(anyInt())) { true }
-            whenever(isMatch(any<ComponentInfo>(), anyInt())) { true }
-            whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
-                    anyString(), anyInt())) { true }
-        }
+        private val dummyUserState = PackageUserStateInternalImpl()
 
         val oldPackages = mutableListOf<PackageParser.Package>()
 
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index e932905..521ffe6 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -67,7 +67,7 @@
 fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
 
 @Suppress("UNCHECKED_CAST")
-fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
+fun <Type> whenever(mock: Type, block: InvocationOnMock.() -> Type) =
         Mockito.`when`(mock).thenAnswer { block(it) }
 
 fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 5fa76bb..82140f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -922,6 +922,40 @@
         verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
     }
 
+    @Test
+    public void testTransitionGoodToGoForTaskFragments_detachedApp() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment changeTaskFragment =
+                createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setOrganizer(organizer)
+                .build();
+        changeTaskFragment.getTopMostActivity().allDrawn = true;
+        // To make sure that having a detached activity won't cause any issue.
+        final ActivityRecord detachedActivity = createActivityRecord(task);
+        detachedActivity.removeImmediately();
+        assertNull(detachedActivity.getRootTask());
+        spyOn(mDisplayContent.mAppTransition);
+        spyOn(emptyTaskFragment);
+
+        prepareAndTriggerAppTransition(
+                null /* openingActivity */, detachedActivity, changeTaskFragment);
+
+        // Transition not ready because there is an empty non-finishing TaskFragment.
+        verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
+
+        doReturn(true).when(emptyTaskFragment).hasChild();
+        emptyTaskFragment.remove(false /* withTransition */, "test");
+
+        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+        // Transition ready because the empty (no running activity) TaskFragment is requested to be
+        // removed.
+        verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
+    }
+
     /** Registers remote animation for the organizer. */
     private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
             RemoteAnimationAdapter adapter) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 52c5b6bb..f4f06fd 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -190,7 +190,8 @@
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, handler,
+                Context.RECEIVER_NOT_EXPORTED);
     }
 
     public boolean showSessionLocked(Bundle args, int flags,
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java
index aa57394..00fe6f2 100644
--- a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java
@@ -24,10 +24,12 @@
 import android.net.Uri;
 import android.os.Build;
 import android.util.Log;
+
 import androidx.media.filterfw.FrameImage2D;
 import androidx.media.filterfw.FrameValue;
 import androidx.media.filterfw.RenderTarget;
 
+import java.io.IOException;
 import java.util.concurrent.LinkedBlockingQueue;
 
 @TargetApi(16)
@@ -276,12 +278,13 @@
     }
 
     @TargetApi(17)
-    private void retrieveDefaultRotation() {
+    private void retrieveDefaultRotation() throws IOException {
         MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
         metadataRetriever.setDataSource(mContext, mUri);
         String rotationString = metadataRetriever.extractMetadata(
                 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
         mDefaultRotation = rotationString == null ? 0 : Integer.parseInt(rotationString);
+        metadataRetriever.release();
     }
 
     private void onStop(boolean notifyListener) {