Merge "Update background color of main switch bar."
diff --git a/OWNERS b/OWNERS
index 4160122..710f13e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -9,6 +9,7 @@
 jjaggi@google.com
 jsharkey@android.com
 jsharkey@google.com
+lorenzo@google.com
 michaelwr@google.com
 nandana@google.com
 narayan@google.com
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 df0a0ee..35dadf0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1978,9 +1978,12 @@
                         JobStatus runNow = (JobStatus) message.obj;
                         // runNow can be null, which is a controller's way of indicating that its
                         // state is such that all ready jobs should be run immediately.
-                        if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
-                            mJobPackageTracker.notePending(runNow);
-                            addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                        if (runNow != null) {
+                            if (!isCurrentlyActiveLocked(runNow)
+                                    && isReadyToBeExecutedLocked(runNow)) {
+                                mJobPackageTracker.notePending(runNow);
+                                addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                            }
                         } else {
                             queueReadyJobsForExecutionLocked();
                         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 6ddafad..d5130dc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -217,20 +217,6 @@
         return jobs != null && jobs.size() > 0;
     }
 
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
-        final boolean networkAvailable = isNetworkAvailable(jobStatus);
-        if (DEBUG) {
-            Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
-                    + " networkAvailable=" + networkAvailable);
-        }
-        // If the network isn't available, then requesting an exception won't help.
-
-        return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
-                JobStatus.CONSTRAINT_CONNECTIVITY);
-    }
-
     /**
      * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
      * thing stopping a job from running.
@@ -243,7 +229,8 @@
         }
 
         // Always check the full job readiness stat in case the component has been disabled.
-        if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
+        if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)
+                && isNetworkAvailable(jobStatus)) {
             if (DEBUG) {
                 Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 2d55aa5..a02f8de 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
 import android.os.Handler;
@@ -64,6 +65,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.job.ConstantsProto;
@@ -507,6 +509,8 @@
             QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS
     };
 
+    private long mEjLimitSpecialAdditionMs = QcConstants.DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS;
+
     /**
      * The period of time used to calculate expedited job sessions. Apps can only have expedited job
      * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring
@@ -517,22 +521,26 @@
     /**
      * Length of time used to split an app's top time into chunks.
      */
-    public long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+    private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
 
     /**
      * How much EJ quota to give back to an app based on the number of top app time chunks it had.
      */
-    public long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS;
+    private long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS;
 
     /**
      * How much EJ quota to give back to an app based on each non-top user interaction.
      */
-    public long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS;
+    private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS;
 
     /**
      * How much EJ quota to give back to an app based on each notification seen event.
      */
-    public long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS;
+    private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS;
+
+    /** The package verifier app. */
+    @Nullable
+    private String mPackageVerifier;
 
     /** An app has reached its quota. The message should contain a {@link Package} object. */
     @VisibleForTesting
@@ -588,6 +596,16 @@
     }
 
     @Override
+    public void onSystemServicesReady() {
+        String[] pkgNames = LocalServices.getService(PackageManagerInternal.class)
+                .getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM);
+        synchronized (mLock) {
+            mPackageVerifier = ArrayUtils.firstOrNull(pkgNames);
+        }
+    }
+
+    @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
@@ -875,7 +893,7 @@
         if (quota.getStandbyBucketLocked() == NEVER_INDEX) {
             return 0;
         }
-        final long limitMs = mEJLimitsMs[quota.getStandbyBucketLocked()];
+        final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
         long remainingMs = limitMs - quota.getTallyLocked();
 
         // Stale sessions may still be factored into tally. Make sure they're removed.
@@ -912,6 +930,14 @@
         return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis());
     }
 
+    private long getEJLimitMsLocked(@NonNull final String packageName, final int standbyBucket) {
+        final long baseLimitMs = mEJLimitsMs[standbyBucket];
+        if (packageName.equals(mPackageVerifier)) {
+            return baseLimitMs + mEjLimitSpecialAdditionMs;
+        }
+        return baseLimitMs;
+    }
+
     /**
      * Returns the amount of time, in milliseconds, until the package would have reached its
      * duration quota, assuming it has a job counting towards its quota the entire time. This takes
@@ -1014,7 +1040,7 @@
 
         final long nowElapsed = sElapsedRealtimeClock.millis();
         ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
-        final long limitMs = mEJLimitsMs[quota.getStandbyBucketLocked()];
+        final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
         final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs);
         long remainingDeadSpaceMs = remainingExecutionTimeMs;
         // Total time looked at where a session wouldn't be phasing out.
@@ -1606,7 +1632,7 @@
             inRegularQuotaTimeElapsed = inQuotaTimeElapsed;
         }
         if (remainingEJQuota <= 0) {
-            final long limitMs = mEJLimitsMs[standbyBucket] - mQuotaBufferMs;
+            final long limitMs = getEJLimitMsLocked(packageName, standbyBucket) - mQuotaBufferMs;
             long sumMs = 0;
             final Timer ejTimer = mEJPkgTimers.get(userId, packageName);
             if (ejTimer != null && ejTimer.isActive()) {
@@ -2741,6 +2767,9 @@
         static final String KEY_EJ_LIMIT_RESTRICTED_MS =
                 QC_CONSTANT_PREFIX + "ej_limit_restricted_ms";
         @VisibleForTesting
+        static final String KEY_EJ_LIMIT_SPECIAL_ADDITION_MS =
+                QC_CONSTANT_PREFIX + "ej_limit_special_addition_ms";
+        @VisibleForTesting
         static final String KEY_EJ_WINDOW_SIZE_MS =
                 QC_CONSTANT_PREFIX + "ej_window_size_ms";
         @VisibleForTesting
@@ -2801,6 +2830,7 @@
         private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS;
         private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS;
+        private static final long DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS = 30 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS;
         private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS;
         private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
@@ -3001,6 +3031,11 @@
         public long EJ_LIMIT_RESTRICTED_MS = DEFAULT_EJ_LIMIT_RESTRICTED_MS;
 
         /**
+         * How much additional EJ quota special, critical apps should get.
+         */
+        public long EJ_LIMIT_SPECIAL_ADDITION_MS = DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS;
+
+        /**
          * The period of time used to calculate expedited job sessions. Apps can only have expedited
          * job sessions totalling EJ_LIMIT_<bucket>_MS within this period of time (without factoring
          * in any rewards or free EJs).
@@ -3053,6 +3088,7 @@
                 case KEY_EJ_LIMIT_FREQUENT_MS:
                 case KEY_EJ_LIMIT_RARE_MS:
                 case KEY_EJ_LIMIT_RESTRICTED_MS:
+                case KEY_EJ_LIMIT_SPECIAL_ADDITION_MS:
                 case KEY_EJ_WINDOW_SIZE_MS:
                     updateEJLimitConstantsLocked();
                     break;
@@ -3376,7 +3412,8 @@
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS,
                     KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS,
-                    KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_WINDOW_SIZE_MS);
+                    KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_SPECIAL_ADDITION_MS,
+                    KEY_EJ_WINDOW_SIZE_MS);
             EJ_LIMIT_ACTIVE_MS = properties.getLong(
                     KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
             EJ_LIMIT_WORKING_MS = properties.getLong(
@@ -3387,6 +3424,8 @@
                     KEY_EJ_LIMIT_RARE_MS, DEFAULT_EJ_LIMIT_RARE_MS);
             EJ_LIMIT_RESTRICTED_MS = properties.getLong(
                     KEY_EJ_LIMIT_RESTRICTED_MS, DEFAULT_EJ_LIMIT_RESTRICTED_MS);
+            EJ_LIMIT_SPECIAL_ADDITION_MS = properties.getLong(
+                    KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS);
             EJ_WINDOW_SIZE_MS = properties.getLong(
                     KEY_EJ_WINDOW_SIZE_MS, DEFAULT_EJ_WINDOW_SIZE_MS);
 
@@ -3432,6 +3471,13 @@
                 mEJLimitsMs[RESTRICTED_INDEX] = newRestrictedLimitMs;
                 mShouldReevaluateConstraints = true;
             }
+            // The addition must be in the range [0 minutes, window size - active limit].
+            long newSpecialAdditionMs = Math.max(0,
+                    Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_SPECIAL_ADDITION_MS));
+            if (mEjLimitSpecialAdditionMs != newSpecialAdditionMs) {
+                mEjLimitSpecialAdditionMs = newSpecialAdditionMs;
+                mShouldReevaluateConstraints = true;
+            }
         }
 
         private void dump(IndentingPrintWriter pw) {
@@ -3470,6 +3516,7 @@
             pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println();
             pw.print(KEY_EJ_LIMIT_RARE_MS, EJ_LIMIT_RARE_MS).println();
             pw.print(KEY_EJ_LIMIT_RESTRICTED_MS, EJ_LIMIT_RESTRICTED_MS).println();
+            pw.print(KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, EJ_LIMIT_SPECIAL_ADDITION_MS).println();
             pw.print(KEY_EJ_WINDOW_SIZE_MS, EJ_WINDOW_SIZE_MS).println();
             pw.print(KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, EJ_TOP_APP_TIME_CHUNK_SIZE_MS).println();
             pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println();
@@ -3593,6 +3640,11 @@
     }
 
     @VisibleForTesting
+    long getEjLimitSpecialAdditionMs() {
+        return mEjLimitSpecialAdditionMs;
+    }
+
+    @VisibleForTesting
     @NonNull
     long getEJLimitWindowSizeMs() {
         return mEJLimitWindowSizeMs;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index 2b5aab8..ede14ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -157,31 +157,27 @@
                 && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)
                 && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
             if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
-                checkExpiredDeadlinesAndResetAlarm();
-                checkExpiredDelaysAndResetAlarm();
-            } else {
-                final boolean isAlarmForJob =
-                        job.getLatestRunTimeElapsed() == mNextJobExpiredElapsedMillis;
-                final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
-                        job, JobStatus.CONSTRAINT_DEADLINE);
-                if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
-                    checkExpiredDeadlinesAndResetAlarm();
+                if (job.isReady()) {
+                    // If the job still isn't ready, there's no point trying to rush the
+                    // Scheduler.
+                    mStateChangedListener.onRunJobNow(job);
                 }
+            } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
+                // This job's deadline is earlier than the current set alarm. Update the alarm.
+                setDeadlineExpiredAlarmLocked(job.getLatestRunTimeElapsed(),
+                        deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
             }
         }
         if (job.hasTimingDelayConstraint()
                 && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)
                 && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
-            if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
-                checkExpiredDelaysAndResetAlarm();
-            } else {
-                final boolean isAlarmForJob =
-                        job.getEarliestRunTime() == mNextDelayExpiredElapsedMillis;
-                final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
-                        job, JobStatus.CONSTRAINT_TIMING_DELAY);
-                if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
-                    checkExpiredDelaysAndResetAlarm();
-                }
+            // Since this is just the delay, we don't need to rush the Scheduler to run the job
+            // immediately if the constraint is satisfied here.
+            if (!evaluateTimingDelayConstraint(job, nowElapsedMillis)
+                    && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+                // This job's delay is earlier than the current set alarm. Update the alarm.
+                setDelayExpiredAlarmLocked(job.getEarliestRunTime(),
+                        deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
             }
         }
     }
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index d449289..3d706e4 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -109,6 +109,9 @@
     /** Interval between trying to reconnect to the service. */
     private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40;
 
+    /** Default bpp(bits-per-pixel) to use for calculating default bitrate. */
+    private static final float BPP = 0.25f;
+
     /**
      * Default transcoding type.
      * @hide
@@ -1002,15 +1005,93 @@
                 if (!shouldTranscode()) {
                     return null;
                 }
-                // TODO(hkuang): Only modified the video codec type, and use fixed bitrate for now.
-                // May switch to transcoding profile when it's available.
+
                 MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint);
                 videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
-                videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+
+                int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH);
+                int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT);
+                if (width <= 0 || height <= 0) {
+                    throw new IllegalArgumentException(
+                            "Source Width and height must be larger than 0");
+                }
+
+                // TODO(hkuang): Remove the hardcoded frameRate after b/176940364 is fixed.
+                float frameRate = (float) 30.0;
+                /*mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE, frameRate);
+                if (frameRate <= 0) {
+                    throw new IllegalArgumentException(
+                            "frameRate must be larger than 0");
+                }*/
+
+                int bitrate = getAVCBitrate(width, height, frameRate);
+                videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
                 return videoTrackFormat;
             }
 
             /**
+             * Generate a default bitrate with the fixed bpp(bits-per-pixel) 0.25.
+             * This maps to:
+             * 1080P@30fps -> 16Mbps
+             * 1080P@60fps-> 32Mbps
+             * 4K@30fps -> 62Mbps
+             */
+            private static int getDefaultBitrate(int width, int height, float frameRate) {
+                return (int) (width * height * frameRate * BPP);
+            }
+
+            /**
+             * Query the bitrate from CamcorderProfile. If there are two profiles that match the
+             * width/height/framerate, we will use the higher one to get better quality.
+             * Return default bitrate if could not find any match profile.
+             */
+            private static int getAVCBitrate(int width, int height, float frameRate) {
+                int bitrate = -1;
+                int[] cameraIds = {0, 1};
+
+                // Profiles ordered in decreasing order of preference.
+                int[] preferQualities = {
+                        CamcorderProfile.QUALITY_2160P,
+                        CamcorderProfile.QUALITY_1080P,
+                        CamcorderProfile.QUALITY_720P,
+                        CamcorderProfile.QUALITY_480P,
+                        CamcorderProfile.QUALITY_LOW,
+                };
+
+                for (int cameraId : cameraIds) {
+                    for (int quality : preferQualities) {
+                        // Check if camera id has profile for the quality level.
+                        if (!CamcorderProfile.hasProfile(cameraId, quality)) {
+                            continue;
+                        }
+                        CamcorderProfile profile = CamcorderProfile.get(cameraId, quality);
+                        // Check the width/height/framerate/codec, also consider portrait case.
+                        if (((width == profile.videoFrameWidth
+                                && height == profile.videoFrameHeight)
+                                || (height == profile.videoFrameWidth
+                                && width == profile.videoFrameHeight))
+                                && (int) frameRate == profile.videoFrameRate
+                                && profile.videoCodec == MediaRecorder.VideoEncoder.H264) {
+                            if (bitrate < profile.videoBitRate) {
+                                bitrate = profile.videoBitRate;
+                            }
+                            break;
+                        }
+                    }
+                }
+
+                if (bitrate == -1) {
+                    Log.w(TAG, "Failed to find CamcorderProfile for w: " + width + "h: " + height
+                            + " fps: "
+                            + frameRate);
+                    bitrate = getDefaultBitrate(width, height, frameRate);
+                }
+                Log.d(TAG, "Using bitrate " + bitrate + " for " + width + " " + height + " "
+                        + frameRate);
+                return bitrate;
+            }
+
+            /**
              * Retrieves the audio track format to be used for transcoding.
              *
              * @return the audio track format to be used if transcoding should be performed, and
diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
index 8f5845b..09867f3 100644
--- a/cmds/idmap2/idmap2/CommandUtils.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -29,8 +29,8 @@
 using android::idmap2::Unit;
 
 Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
-                    const std::string& overlay_path, PolicyBitmask fulfilled_policies,
-                    bool enforce_overlayable) {
+                    const std::string& overlay_path, const std::string& overlay_name,
+                    PolicyBitmask fulfilled_policies, bool enforce_overlayable) {
   SYSTRACE << "Verify " << idmap_path;
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
@@ -39,7 +39,7 @@
     return Error("failed to parse idmap header");
   }
 
-  const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(),
+  const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name,
                                             fulfilled_policies, enforce_overlayable);
   if (!header_ok) {
     return Error(header_ok.GetError(), "idmap not up to date");
diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h
index e717e04..e068967 100644
--- a/cmds/idmap2/idmap2/CommandUtils.h
+++ b/cmds/idmap2/idmap2/CommandUtils.h
@@ -20,10 +20,8 @@
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/Result.h"
 
-android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path,
-                                                      const std::string& target_path,
-                                                      const std::string& overlay_path,
-                                                      PolicyBitmask fulfilled_policies,
-                                                      bool enforce_overlayable);
+android::idmap2::Result<android::idmap2::Unit> Verify(
+    const std::string& idmap_path, const std::string& target_path, const std::string& overlay_path,
+    const std::string& overlay_name, PolicyBitmask fulfilled_policies, bool enforce_overlayable);
 
 #endif  // IDMAP2_IDMAP2_COMMAND_UTILS_H_
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index 648b78e..c93c717 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -50,6 +50,7 @@
   std::string target_apk_path;
   std::string overlay_apk_path;
   std::string idmap_path;
+  std::string overlay_name;
   std::vector<std::string> policies;
   bool ignore_overlayable = false;
 
@@ -62,9 +63,11 @@
                            "input: path to apk which contains the new resource values",
                            &overlay_apk_path)
           .MandatoryOption("--idmap-path", "output: path to where to write idmap file", &idmap_path)
+          .OptionalOption("--overlay-name", "input: the value of android:name of the overlay",
+                          &overlay_name)
           .OptionalOption("--policy",
                           "input: an overlayable policy this overlay fulfills "
-                          "(if none or supplied, the overlay policy will default to \"public\")",
+                          "(if none are supplied, the overlay policy will default to \"public\")",
                           &policies)
           .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks",
                         &ignore_overlayable);
@@ -100,8 +103,8 @@
     return Error("failed to load apk %s", overlay_apk_path.c_str());
   }
 
-  const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
+  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name,
+                                          fulfilled_policies, !ignore_overlayable);
   if (!idmap) {
     return Error(idmap.GetError(), "failed to create idmap");
   }
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 19622c4..5db391c 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -105,7 +105,8 @@
       continue;
     }
 
-    if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies,
+    // TODO(b/175014391): Support multiple overlay tags in OverlayConfig
+    if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies,
                 !ignore_overlayable)) {
       const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
       if (!overlay_apk) {
@@ -113,8 +114,8 @@
         continue;
       }
 
-      const auto idmap =
-          Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
+      const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies,
+                                              !ignore_overlayable);
       if (!idmap) {
         LOG(WARNING) << "failed to create idmap";
         continue;
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index 437180d..43a1951 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -188,29 +188,27 @@
     }
 
     if (i == 0) {
-      target_path = idmap_header->GetTargetPath().to_string();
+      target_path = idmap_header->GetTargetPath();
       auto target_apk = ApkAssets::Load(target_path);
       if (!target_apk) {
         return Error("failed to read target apk from %s", target_path.c_str());
       }
       apk_assets.push_back(std::move(target_apk));
 
-      auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath().to_string(),
-                                                      true /* assert_overlay */);
+      auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(),
+                                                      idmap_header->GetOverlayName());
       if (!manifest_info) {
         return manifest_info.GetError();
       }
-      target_package_name = (*manifest_info).target_package;
+      target_package_name = manifest_info->target_package;
     } else if (target_path != idmap_header->GetTargetPath()) {
       return Error("different target APKs (expected target APK %s but %s has target APK %s)",
-                   target_path.c_str(), idmap_path.c_str(),
-                   idmap_header->GetTargetPath().to_string().c_str());
+                   target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str());
     }
 
     auto overlay_apk = ApkAssets::LoadOverlay(idmap_path);
     if (!overlay_apk) {
-      return Error("failed to read overlay apk from %s",
-                   idmap_header->GetOverlayPath().to_string().c_str());
+      return Error("failed to read overlay apk from %s", idmap_header->GetOverlayPath().c_str());
     }
     apk_assets.push_back(std::move(overlay_apk));
   }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 15e22a3..93537d3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -155,8 +155,9 @@
     return overlay_crc_status;
   }
 
+  // TODO(162841629): Support passing overlay name to idmap2d verify
   auto up_to_date =
-      header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc,
+      header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc,
                          ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
 
   *_aidl_return = static_cast<bool>(up_to_date);
@@ -190,8 +191,9 @@
     return error("failed to load apk " + overlay_apk_path);
   }
 
+  // TODO(162841629): Support passing overlay name to idmap2d create
   const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, policy_bitmask, enforce_overlayable);
+      Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable);
   if (!idmap) {
     return error(idmap.GetErrorMessage());
   }
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index bf31cbf..5e189f2 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -39,7 +39,6 @@
   void Write8(uint8_t value);
   void Write16(uint16_t value);
   void Write32(uint32_t value);
-  void WriteString256(const StringPiece& value);
   void WriteString(const StringPiece& value);
   std::ostream& stream_;
 };
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index a35fad9..1b815c1 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -19,7 +19,8 @@
  *
  * idmap                      := header data*
  * header                     := magic version target_crc overlay_crc fulfilled_policies
- *                               enforce_overlayable target_path overlay_path debug_info
+ *                               enforce_overlayable target_path overlay_path overlay_name
+ *                               debug_info
  * data                       := data_header target_entry* target_inline_entry* overlay_entry*
  *                               string_pool
  * data_header                := target_package_id overlay_package_id padding(2) target_entry_count
@@ -37,13 +38,13 @@
  * overlay_entry_count        := <uint32_t>
  * overlay_id                 := <uint32_t>
  * overlay_package_id         := <uint8_t>
- * overlay_path               := string256
+ * overlay_name               := string
+ * overlay_path               := string
  * padding(n)                 := <uint8_t>[n]
  * Res_value::size            := <uint16_t>
  * Res_value::type            := <uint8_t>
  * Res_value::value           := <uint32_t>
  * string                     := <uint32_t> <uint8_t>+ padding(n)
- * string256                  := <uint8_t>[256]
  * string_pool                := string
  * string_pool_index          := <uint32_t>
  * string_pool_length         := <uint32_t>
@@ -52,7 +53,7 @@
  * target_inline_entry_count  := <uint32_t>
  * target_id                  := <uint32_t>
  * target_package_id          := <uint8_t>
- * target_path                := string256
+ * target_path                := string
  * value_type                 := <uint8_t>
  * value_data                 := <uint32_t>
  * version                    := <uint32_t>
@@ -78,19 +79,12 @@
 class Idmap;
 class Visitor;
 
-static constexpr const ResourceId kPadding = 0xffffffffu;
-static constexpr const EntryId kNoEntry = 0xffffu;
-
 // magic number: all idmap files start with this
 static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic;
 
 // current version of the idmap binary format; must be incremented when the format is changed
 static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
 
-// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory
-// terminating null)
-static constexpr const size_t kIdmapStringLength = 256;
-
 // Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
 Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
 
@@ -122,32 +116,38 @@
     return enforce_overlayable_;
   }
 
-  inline StringPiece GetTargetPath() const {
-    return StringPiece(target_path_);
+  const std::string& GetTargetPath() const {
+    return target_path_;
   }
 
-  inline StringPiece GetOverlayPath() const {
-    return StringPiece(overlay_path_);
+  const std::string& GetOverlayPath() const {
+    return overlay_path_;
   }
 
-  inline const std::string& GetDebugInfo() const {
+  const std::string& GetOverlayName() const {
+    return overlay_name_;
+  }
+
+  const std::string& GetDebugInfo() const {
     return debug_info_;
   }
 
   // Invariant: anytime the idmap data encoding is changed, the idmap version
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
-  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path,
-                          PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
-  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc,
+  Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
+                          const std::string& overlay_name, PolicyBitmask fulfilled_policies,
+                          bool enforce_overlayable) const;
+
+  Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
+                          const std::string& overlay_name, uint32_t target_crc,
                           uint32_t overlay_crc, PolicyBitmask fulfilled_policies,
                           bool enforce_overlayable) const;
 
   void accept(Visitor* v) const;
 
  private:
-  IdmapHeader() {
-  }
+  IdmapHeader() = default;
 
   uint32_t magic_;
   uint32_t version_;
@@ -155,8 +155,9 @@
   uint32_t overlay_crc_;
   uint32_t fulfilled_policies_;
   bool enforce_overlayable_;
-  char target_path_[kIdmapStringLength];
-  char overlay_path_[kIdmapStringLength];
+  std::string target_path_;
+  std::string overlay_path_;
+  std::string overlay_name_;
   std::string debug_info_;
 
   friend Idmap;
@@ -251,8 +252,7 @@
   void accept(Visitor* v) const;
 
  private:
-  IdmapData() {
-  }
+  IdmapData() = default;
 
   std::unique_ptr<const Header> header_;
   std::vector<TargetEntry> target_entries_;
@@ -277,22 +277,22 @@
   // the target and overlay package names
   static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
                                                             const ApkAssets& overlay_apk_assets,
+                                                            const std::string& overlay_name,
                                                             const PolicyBitmask& fulfilled_policies,
                                                             bool enforce_overlayable);
 
-  inline const std::unique_ptr<const IdmapHeader>& GetHeader() const {
+  const std::unique_ptr<const IdmapHeader>& GetHeader() const {
     return header_;
   }
 
-  inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const {
+  const std::vector<std::unique_ptr<const IdmapData>>& GetData() const {
     return data_;
   }
 
   void accept(Visitor* v) const;
 
  private:
-  Idmap() {
-  }
+  Idmap() = default;
 
   std::unique_ptr<const IdmapHeader> header_;
   std::vector<std::unique_ptr<const IdmapData>> data_;
@@ -302,8 +302,7 @@
 
 class Visitor {
  public:
-  virtual ~Visitor() {
-  }
+  virtual ~Visitor() = default;
   virtual void visit(const Idmap& idmap) = 0;
   virtual void visit(const IdmapHeader& header) = 0;
   virtual void visit(const IdmapData& data) = 0;
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 58edc99..4583516 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -44,7 +44,9 @@
   void print(uint8_t value, const char* fmt, ...);
   void print(uint16_t value, const char* fmt, ...);
   void print(uint32_t value, const char* fmt, ...);
-  void print(const std::string& value, size_t encoded_size, const char* fmt, ...);
+  void print(const std::string& value, bool print_value, const char* fmt, ...);
+  void align();
+  void pad(size_t padding);
 
   std::ostream& stream_;
   std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index c643b0e..cd14d3e 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -44,17 +44,14 @@
 StringPiece DataTypeToString(uint8_t data_type);
 
 struct OverlayManifestInfo {
-  std::string target_package;               // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_name;                  // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string requiredSystemPropertyName;   // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string requiredSystemPropertyValue;  // NOLINT(misc-non-private-member-variables-in-classes)
-  uint32_t resource_mapping;                // NOLINT(misc-non-private-member-variables-in-classes)
-  bool is_static;                           // NOLINT(misc-non-private-member-variables-in-classes)
-  int priority = -1;                        // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string name;            // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_package;  // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_name;     // NOLINT(misc-non-private-member-variables-in-classes)
+  uint32_t resource_mapping;   // NOLINT(misc-non-private-member-variables-in-classes)
 };
 
 Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       bool assert_overlay = true);
+                                                       const std::string& name);
 
 Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
 
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
index 972a6d7..1c74ab3 100644
--- a/cmds/idmap2/include/idmap2/XmlParser.h
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -22,6 +22,7 @@
 #include <memory>
 #include <string>
 
+#include "ResourceUtils.h"
 #include "Result.h"
 #include "android-base/macros.h"
 #include "androidfw/ResourceTypes.h"
@@ -39,8 +40,11 @@
     Event event() const;
     std::string name() const;
 
-    Result<std::string> GetAttributeStringValue(const std::string& name) const;
     Result<Res_value> GetAttributeValue(const std::string& name) const;
+    Result<Res_value> GetAttributeValue(ResourceId attr, const std::string& label) const;
+
+    Result<std::string> GetAttributeStringValue(const std::string& name) const;
+    Result<std::string> GetAttributeStringValue(ResourceId attr, const std::string& label) const;
 
     bool operator==(const Node& rhs) const;
     bool operator!=(const Node& rhs) const;
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 726f6c5..c163107 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -38,13 +38,6 @@
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
 }
 
-void BinaryStreamVisitor::WriteString256(const StringPiece& value) {
-  char buf[kIdmapStringLength];
-  memset(buf, 0, sizeof(buf));
-  memcpy(buf, value.data(), std::min(value.size(), sizeof(buf)));
-  stream_.write(buf, sizeof(buf));
-}
-
 void BinaryStreamVisitor::WriteString(const StringPiece& value) {
   // pad with null to nearest word boundary;
   size_t padding_size = CalculatePadding(value.size());
@@ -64,8 +57,9 @@
   Write32(header.GetOverlayCrc());
   Write32(header.GetFulfilledPolicies());
   Write32(static_cast<uint8_t>(header.GetEnforceOverlayable()));
-  WriteString256(header.GetTargetPath());
-  WriteString256(header.GetOverlayPath());
+  WriteString(header.GetTargetPath());
+  WriteString(header.GetOverlayPath());
+  WriteString(header.GetOverlayName());
   WriteString(header.GetDebugInfo());
 }
 
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 1129413..5af84b0 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -69,38 +69,26 @@
   return false;
 }
 
-// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
-bool WARN_UNUSED ReadString256(std::istream& stream, char out[kIdmapStringLength]) {
-  char buf[kIdmapStringLength];
-  memset(buf, 0, sizeof(buf));
-  if (!stream.read(buf, sizeof(buf))) {
-    return false;
-  }
-  if (buf[sizeof(buf) - 1] != '\0') {
-    return false;
-  }
-  memcpy(out, buf, sizeof(buf));
-  return true;
-}
-
-Result<std::string> ReadString(std::istream& stream) {
+bool WARN_UNUSED ReadString(std::istream& stream, std::string* out) {
   uint32_t size;
   if (!Read32(stream, &size)) {
-    return Error("failed to read string size");
+    return false;
   }
   if (size == 0) {
-    return std::string("");
+    *out = "";
+    return true;
   }
   std::string buf(size, '\0');
   if (!stream.read(buf.data(), size)) {
-    return Error("failed to read string of size %u", size);
+    return false;
   }
   uint32_t padding_size = CalculatePadding(size);
   std::string padding(padding_size, '\0');
   if (!stream.read(padding.data(), padding_size)) {
-    return Error("failed to read string padding of size %u", padding_size);
+    return false;
   }
-  return buf;
+  *out = buf;
+  return true;
 }
 
 }  // namespace
@@ -119,28 +107,25 @@
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
       !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
       !Read32(stream, &idmap_header->fulfilled_policies_) ||
-      !Read32(stream, &enforce_overlayable) || !ReadString256(stream, idmap_header->target_path_) ||
-      !ReadString256(stream, idmap_header->overlay_path_)) {
+      !Read32(stream, &enforce_overlayable) || !ReadString(stream, &idmap_header->target_path_) ||
+      !ReadString(stream, &idmap_header->overlay_path_) ||
+      !ReadString(stream, &idmap_header->overlay_name_) ||
+      !ReadString(stream, &idmap_header->debug_info_)) {
     return nullptr;
   }
 
   idmap_header->enforce_overlayable_ = enforce_overlayable != 0U;
-
-  auto debug_str = ReadString(stream);
-  if (!debug_str) {
-    return nullptr;
-  }
-  idmap_header->debug_info_ = std::move(*debug_str);
-
   return std::move(idmap_header);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
+Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
+                                     const std::string& overlay_path,
+                                     const std::string& overlay_name,
                                      PolicyBitmask fulfilled_policies,
                                      bool enforce_overlayable) const {
   const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
   if (!target_zip) {
-    return Error("failed to open target %s", target_path);
+    return Error("failed to open target %s", target_path.c_str());
   }
 
   const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
@@ -150,7 +135,7 @@
 
   const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
   if (!overlay_zip) {
-    return Error("failed to overlay target %s", overlay_path);
+    return Error("failed to overlay target %s", overlay_path.c_str());
   }
 
   const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
@@ -158,13 +143,14 @@
     return Error("failed to get overlay crc");
   }
 
-  return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies,
-                    enforce_overlayable);
+  return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc,
+                    fulfilled_policies, enforce_overlayable);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
-                                     uint32_t target_crc, uint32_t overlay_crc,
-                                     PolicyBitmask fulfilled_policies,
+Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
+                                     const std::string& overlay_path,
+                                     const std::string& overlay_name, uint32_t target_crc,
+                                     uint32_t overlay_crc, PolicyBitmask fulfilled_policies,
                                      bool enforce_overlayable) const {
   if (magic_ != kIdmapMagic) {
     return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic);
@@ -194,14 +180,19 @@
                  enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false");
   }
 
-  if (strcmp(target_path, target_path_) != 0) {
-    return Error("bad target path: idmap version %s, file system version %s", target_path,
-                 target_path_);
+  if (target_path != target_path_) {
+    return Error("bad target path: idmap version %s, file system version %s", target_path.c_str(),
+                 target_path_.c_str());
   }
 
-  if (strcmp(overlay_path, overlay_path_) != 0) {
-    return Error("bad overlay path: idmap version %s, file system version %s", overlay_path,
-                 overlay_path_);
+  if (overlay_path != overlay_path_) {
+    return Error("bad overlay path: idmap version %s, file system version %s", overlay_path.c_str(),
+                 overlay_path_.c_str());
+  }
+
+  if (overlay_name != overlay_name_) {
+    return Error("bad overlay name: idmap version %s, file system version %s", overlay_name.c_str(),
+                 overlay_name_.c_str());
   }
 
   return Unit{};
@@ -262,12 +253,9 @@
   }
 
   // Read raw string pool bytes.
-  auto string_pool_data = ReadString(stream);
-  if (!string_pool_data) {
+  if (!ReadString(stream, &data->string_pool_data_)) {
     return nullptr;
   }
-  data->string_pool_data_ = std::move(*string_pool_data);
-
   return std::move(data);
 }
 
@@ -337,6 +325,7 @@
 
 Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
                                                           const ApkAssets& overlay_apk_assets,
+                                                          const std::string& overlay_name,
                                                           const PolicyBitmask& fulfilled_policies,
                                                           bool enforce_overlayable) {
   SYSTRACE << "Idmap::FromApkAssets";
@@ -368,32 +357,20 @@
     return Error(crc.GetError(), "failed to get zip CRC for overlay");
   }
   header->overlay_crc_ = *crc;
-
   header->fulfilled_policies_ = fulfilled_policies;
   header->enforce_overlayable_ = enforce_overlayable;
+  header->target_path_ = target_apk_path;
+  header->overlay_path_ = overlay_apk_path;
+  header->overlay_name_ = overlay_name;
 
-  if (target_apk_path.size() > sizeof(header->target_path_)) {
-    return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(),
-                 sizeof(header->target_path_));
-  }
-  memset(header->target_path_, 0, sizeof(header->target_path_));
-  memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
-
-  if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
-    return Error("overlay apk path \"%s\" longer than maximum size %zu", overlay_apk_path.c_str(),
-                 sizeof(header->target_path_));
-  }
-  memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
-  memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
-
-  auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
-  if (!overlay_info) {
-    return overlay_info.GetError();
+  auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name);
+  if (!info) {
+    return info.GetError();
   }
 
   LogInfo log_info;
   auto resource_mapping =
-      ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info,
+      ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info,
                                      fulfilled_policies, enforce_overlayable, log_info);
   if (!resource_mapping) {
     return resource_mapping.GetError();
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index 3037a79..7e090a9 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -39,6 +39,10 @@
           << TAB "target apk path  : " << header.GetTargetPath() << std::endl
           << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
 
+  if (!header.GetOverlayName().empty()) {
+    stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
+  }
+
   const std::string& debug = header.GetDebugInfo();
   if (!debug.empty()) {
     std::istringstream debug_stream(debug);
@@ -49,12 +53,12 @@
     }
   }
 
-  if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string())) {
+  if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) {
     target_am_.SetApkAssets({target_apk_.get()});
     apk_assets_.push_back(std::move(target_apk_));
   }
 
-  if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath().to_string())) {
+  if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) {
     overlay_am_.SetApkAssets({overlay_apk.get()});
     apk_assets_.push_back(std::move(overlay_apk));
   }
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index 82f5d26..b517aa3 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -43,20 +43,18 @@
   print(header.GetFulfilledPolicies(), "fulfilled policies: %s",
         PoliciesToDebugString(header.GetFulfilledPolicies()).c_str());
   print(static_cast<uint32_t>(header.GetEnforceOverlayable()), "enforce overlayable");
-  print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path");
-  print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path");
+  print(header.GetTargetPath(), true /* print_value */, "target path");
+  print(header.GetOverlayPath(), true /* print_value */, "overlay path");
+  print(header.GetOverlayName(), true /* print_value */, "overlay name");
+  print(header.GetDebugInfo(), false /* print_value */, "debug info");
 
-  uint32_t debug_info_size = header.GetDebugInfo().size();
-  print(debug_info_size, "debug info size");
-  print("...", debug_info_size + CalculatePadding(debug_info_size), "debug info");
-
-  auto target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
+  auto target_apk_ = ApkAssets::Load(header.GetTargetPath());
   if (target_apk_) {
     target_am_.SetApkAssets({target_apk_.get()});
     apk_assets_.push_back(std::move(target_apk_));
   }
 
-  auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath().to_string());
+  auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath());
   if (overlay_apk_) {
     overlay_am_.SetApkAssets({overlay_apk_.get()});
     apk_assets_.push_back(std::move(overlay_apk_));
@@ -100,7 +98,7 @@
       print(target_entry.target_id, "target id");
     }
 
-    print("...", sizeof(Res_value::size) + sizeof(Res_value::res0), "padding");
+    pad(sizeof(Res_value::size) + sizeof(Res_value::res0));
 
     print(target_entry.value.data_type, "type: %s",
           utils::DataTypeToString(target_entry.value.data_type).data());
@@ -143,15 +141,13 @@
     }
   }
 
-  uint32_t string_pool_size = data.GetStringPoolData().size();
-  print(string_pool_size, "string pool size");
-  print("...", string_pool_size + CalculatePadding(string_pool_size), "string pool");
+  print(data.GetStringPoolData(), false /* print_value */, "string pool");
 }
 
 void RawPrintVisitor::visit(const IdmapData::Header& header) {
   print(header.GetTargetPackageId(), "target package id");
   print(header.GetOverlayPackageId(), "overlay package id");
-  print("...", sizeof(Idmap_data_header::p0), "padding");
+  align();
   print(header.GetTargetEntryCount(), "target entry count");
   print(header.GetTargetInlineEntryCount(), "target inline entry count");
   print(header.GetOverlayEntryCount(), "overlay entry count");
@@ -168,7 +164,6 @@
 
   stream_ << base::StringPrintf("%08zx:       %02x", offset_, value) << "  " << comment
           << std::endl;
-
   offset_ += sizeof(uint8_t);
 }
 
@@ -181,7 +176,6 @@
   va_end(ap);
 
   stream_ << base::StringPrintf("%08zx:     %04x", offset_, value) << "  " << comment << std::endl;
-
   offset_ += sizeof(uint16_t);
 }
 
@@ -194,22 +188,35 @@
   va_end(ap);
 
   stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << "  " << comment << std::endl;
-
   offset_ += sizeof(uint32_t);
 }
 
 // NOLINTNEXTLINE(cert-dcl50-cpp)
-void RawPrintVisitor::print(const std::string& value, size_t encoded_size, const char* fmt, ...) {
+void RawPrintVisitor::print(const std::string& value, bool print_value, const char* fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   std::string comment;
   base::StringAppendV(&comment, fmt, ap);
   va_end(ap);
 
-  stream_ << base::StringPrintf("%08zx: ", offset_) << "........  " << comment << ": " << value
-          << std::endl;
+  stream_ << base::StringPrintf("%08zx: %08x", offset_, (uint32_t)value.size()) << "  " << comment
+          << " size" << std::endl;
+  offset_ += sizeof(uint32_t);
 
-  offset_ += encoded_size;
+  stream_ << base::StringPrintf("%08zx: ", offset_) << "........  " << comment;
+  offset_ += value.size() + CalculatePadding(value.size());
+
+  if (print_value) {
+    stream_ << ": " << value;
+  }
+  stream_ << std::endl;
 }
 
+void RawPrintVisitor::align() {
+  offset_ += CalculatePadding(offset_);
+}
+
+void RawPrintVisitor::pad(size_t padding) {
+  offset_ += padding;
+}
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index d777cbf..9abb9e4 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -230,8 +230,8 @@
         base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
     auto target_resource_result = target_am->GetResourceId(full_name);
     if (!target_resource_result.has_value()) {
-      log_info.Warning(LogMessage() << "failed to find resource \"" << full_name
-                                    << "\" in target resources");
+      log_info.Warning(LogMessage()
+                       << "failed to find resource \"" << full_name << "\" in target resources");
       continue;
     }
 
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index e817140..4e85e57 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -32,6 +32,12 @@
 using android::util::Utf16ToUtf8;
 
 namespace android::idmap2::utils {
+namespace {
+constexpr ResourceId kAttrName = 0x01010003;
+constexpr ResourceId kAttrResourcesMap = 0x01010609;
+constexpr ResourceId kAttrTargetName = 0x0101044d;
+constexpr ResourceId kAttrTargetPackage = 0x01010021;
+}  // namespace
 
 bool IsReference(uint8_t data_type) {
   return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
@@ -92,7 +98,7 @@
 }
 
 Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       bool assert_overlay) {
+                                                       const std::string& name) {
   std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
   if (!zip) {
     return Error("failed to open %s as a zip file", path.c_str());
@@ -113,65 +119,49 @@
     return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
   }
 
-  auto overlay_it = std::find_if(manifest_it.begin(), manifest_it.end(), [](const auto& it) {
-    return it.event() == XmlParser::Event::START_TAG && it.name() == "overlay";
-  });
-
-  OverlayManifestInfo info{};
-  if (overlay_it == manifest_it.end()) {
-    if (!assert_overlay) {
-      return info;
+  for (auto&& it : manifest_it) {
+    if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
+      continue;
     }
-    return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str());
-  }
 
-  if (auto result_str = overlay_it->GetAttributeStringValue("targetPackage")) {
-    info.target_package = *result_str;
-  } else {
-    return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
-                 result_str.GetErrorMessage().c_str());
-  }
+    OverlayManifestInfo info{};
+    if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
+      if (*result_str != name) {
+        // A value for android:name was found, but either a the name does not match the requested
+        // name, or an <overlay> tag with no name was requested.
+        continue;
+      }
+      info.name = *result_str;
+    } else if (!name.empty()) {
+      // This tag does not have a value for android:name, but an <overlay> tag with a specific name
+      // has been requested.
+      continue;
+    }
 
-  if (auto result_str = overlay_it->GetAttributeStringValue("targetName")) {
-    info.target_name = *result_str;
-  }
-
-  if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) {
-    if (IsReference((*result_value).dataType)) {
-      info.resource_mapping = (*result_value).data;
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
+      info.target_package = *result_str;
     } else {
-      return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
-                   path.c_str());
+      return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
+                   result_str.GetErrorMessage().c_str());
     }
-  }
 
-  if (auto result_value = overlay_it->GetAttributeValue("isStatic")) {
-    if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
-        (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
-      info.is_static = (*result_value).data != 0U;
-    } else {
-      return Error("android:isStatic is not a boolean in AndroidManifest.xml of %s", path.c_str());
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
+      info.target_name = *result_str;
     }
-  }
 
-  if (auto result_value = overlay_it->GetAttributeValue("priority")) {
-    if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
-        (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
-      info.priority = (*result_value).data;
-    } else {
-      return Error("android:priority is not an integer in AndroidManifest.xml of %s", path.c_str());
+    if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
+      if (IsReference((*result_value).dataType)) {
+        info.resource_mapping = (*result_value).data;
+      } else {
+        return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
+                     path.c_str());
+      }
     }
+    return info;
   }
 
-  if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyName")) {
-    info.requiredSystemPropertyName = *result_str;
-  }
-
-  if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyValue")) {
-    info.requiredSystemPropertyValue = *result_str;
-  }
-
-  return info;
+  return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s",
+               name.c_str(), path.c_str());
 }
 
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 4030b83..00baea4 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -90,15 +90,27 @@
   return String8(key16).c_str();
 }
 
-Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const {
-  auto value = GetAttributeValue(name);
-  if (!value) {
-    return value.GetError();
+template <typename Func>
+Result<Res_value> FindAttribute(const ResXMLParser& parser, const std::string& label,
+                                Func&& predicate) {
+  for (size_t i = 0; i < parser.getAttributeCount(); i++) {
+    if (!predicate(i)) {
+      continue;
+    }
+    Res_value res_value{};
+    if (parser.getAttributeValue(i, &res_value) == BAD_TYPE) {
+      return Error(R"(Bad value for attribute "%s")", label.c_str());
+    }
+    return res_value;
   }
+  return Error(R"(Failed to find attribute "%s")", label.c_str());
+}
 
-  switch ((*value).dataType) {
+Result<std::string> GetStringValue(const ResXMLParser& parser, const Res_value& value,
+                                   const std::string& label) {
+  switch (value.dataType) {
     case Res_value::TYPE_STRING: {
-      if (auto str = parser_.getStrings().string8ObjectAt((*value).data); str.ok()) {
+      if (auto str = parser.getStrings().string8ObjectAt(value.data); str.ok()) {
         return std::string(str->string());
       }
       break;
@@ -106,31 +118,37 @@
     case Res_value::TYPE_INT_DEC:
     case Res_value::TYPE_INT_HEX:
     case Res_value::TYPE_INT_BOOLEAN: {
-      return std::to_string((*value).data);
+      return std::to_string(value.data);
     }
   }
+  return Error(R"(Failed to convert attribute "%s" value to a string)", label.c_str());
+}
 
-  return Error(R"(Failed to convert attribute "%s" value to a string)", name.c_str());
+Result<Res_value> XmlParser::Node::GetAttributeValue(ResourceId attr,
+                                                     const std::string& label) const {
+  return FindAttribute(parser_, label, [&](size_t index) -> bool {
+    return parser_.getAttributeNameResID(index) == attr;
+  });
 }
 
 Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const {
-  size_t len;
-  for (size_t i = 0; i < parser_.getAttributeCount(); i++) {
-    const String16 key16(parser_.getAttributeName(i, &len));
+  return FindAttribute(parser_, name, [&](size_t index) -> bool {
+    size_t len;
+    const String16 key16(parser_.getAttributeName(index, &len));
     std::string key = String8(key16).c_str();
-    if (key != name) {
-      continue;
-    }
+    return key == name;
+  });
+}
 
-    Res_value res_value{};
-    if (parser_.getAttributeValue(i, &res_value) == BAD_TYPE) {
-      return Error(R"(Bad value for attribute "%s")", name.c_str());
-    }
+Result<std::string> XmlParser::Node::GetAttributeStringValue(ResourceId attr,
+                                                             const std::string& label) const {
+  auto value = GetAttributeValue(attr, label);
+  return value ? GetStringValue(parser_, *value, label) : value.GetError();
+}
 
-    return res_value;
-  }
-
-  return Error(R"(Failed to find attribute "%s")", name.c_str());
+Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const {
+  auto value = GetAttributeValue(name);
+  return value ? GetStringValue(parser_, *value, name) : value.GetError();
 }
 
 Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index c3a3e0b..524aabc 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -33,7 +33,7 @@
 namespace android::idmap2 {
 
 TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   auto result1 = Idmap::FromBinaryStream(raw_stream);
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index e7e9e4c..a55b41b 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -35,6 +35,7 @@
 #include <vector>
 
 #include "R.h"
+#include "TestConstants.h"
 #include "TestHelpers.h"
 #include "androidfw/PosixUtils.h"
 #include "gmock/gmock.h"
@@ -43,6 +44,7 @@
 #include "idmap2/Idmap.h"
 #include "private/android_filesystem_config.h"
 
+using ::android::base::StringPrintf;
 using ::android::util::ExecuteBinary;
 using ::testing::NotNull;
 
@@ -90,6 +92,7 @@
                                "create",
                                "--target-apk-path", GetTargetApkPath(),
                                "--overlay-apk-path", GetOverlayApkPath(),
+                               "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
   ASSERT_THAT(result, NotNull());
@@ -116,6 +119,7 @@
                                "create",
                                "--target-apk-path", GetTargetApkPath(),
                                "--overlay-apk-path", GetOverlayApkPath(),
+                               "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
   ASSERT_THAT(result, NotNull());
@@ -128,14 +132,23 @@
   // clang-format on
   ASSERT_THAT(result, NotNull());
   ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
-  ASSERT_NE(result->stdout.find(R::target::integer::literal::int1 + " -> 0x7f010000"),
-            std::string::npos);
-  ASSERT_NE(result->stdout.find(R::target::string::literal::str1 + " -> 0x7f020000"),
-            std::string::npos);
-  ASSERT_NE(result->stdout.find(R::target::string::literal::str3 + " -> 0x7f020001"),
-            std::string::npos);
-  ASSERT_NE(result->stdout.find(R::target::string::literal::str4 + " -> 0x7f020002"),
-            std::string::npos);
+
+  ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::integer::int1,
+                                             R::overlay::integer::int1)),
+            std::string::npos)
+      << result->stdout;
+  ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str1,
+                                             R::overlay::string::str1)),
+            std::string::npos)
+      << result->stdout;
+  ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str3,
+                                             R::overlay::string::str3)),
+            std::string::npos)
+      << result->stdout;
+  ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str4,
+                                             R::overlay::string::str4)),
+            std::string::npos)
+      << result->stdout;
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -167,6 +180,7 @@
                                "create",
                                "--target-apk-path", GetTargetApkPath(),
                                "--overlay-apk-path", GetOverlayApkPath(),
+                               "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
   ASSERT_THAT(result, NotNull());
@@ -177,7 +191,7 @@
                           "lookup",
                           "--idmap-path", GetIdmapPath(),
                           "--config", "",
-                          "--resid", R::target::string::literal::str1});
+                          "--resid", StringPrintf("0x%08x", R::target::string::str1)});
   // clang-format on
   ASSERT_THAT(result, NotNull());
   ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
@@ -229,6 +243,7 @@
                           "create",
                           "--target-apk-path", GetTargetApkPath(),
                           "--overlay-apk-path", GetOverlayApkPath(),
+                          "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                           "--idmap-path"});
   // clang-format on
   ASSERT_THAT(result, NotNull());
@@ -240,6 +255,7 @@
                           "create",
                           "--target-apk-path", invalid_target_apk_path,
                           "--overlay-apk-path", GetOverlayApkPath(),
+                          "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                           "--idmap-path", GetIdmapPath()});
   // clang-format on
   ASSERT_THAT(result, NotNull());
@@ -251,6 +267,7 @@
                           "create",
                           "--target-apk-path", GetTargetApkPath(),
                           "--overlay-apk-path", GetOverlayApkPath(),
+                          "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                           "--idmap-path", GetIdmapPath(),
                           "--policy", "this-does-not-exist"});
   // clang-format on
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 9b42a27..c13b049 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -27,6 +27,7 @@
 #include "TestHelpers.h"
 #include "android-base/macros.h"
 #include "androidfw/ApkAssets.h"
+#include "androidfw/ResourceUtils.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "idmap2/BinaryStreamVisitor.h"
@@ -35,7 +36,6 @@
 #include "idmap2/LogInfo.h"
 
 using android::Res_value;
-using ::testing::IsNull;
 using ::testing::NotNull;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
@@ -61,36 +61,25 @@
 }
 
 TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream stream(raw);
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
   ASSERT_EQ(header->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(header->GetVersion(), 0x05U);
+  ASSERT_EQ(header->GetVersion(), 0x07U);
   ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
   ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
   ASSERT_EQ(header->GetEnforceOverlayable(), true);
-  ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk");
-  ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk");
+  ASSERT_EQ(header->GetTargetPath(), "targetX.apk");
+  ASSERT_EQ(header->GetOverlayPath(), "overlayX.apk");
   ASSERT_EQ(header->GetDebugInfo(), "debug");
 }
 
-TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
-  // overwrite the target path string, including the terminating null, with '.'
-  for (size_t i = 0x18; i < 0x118; i++) {
-    raw[i] = '.';
-  }
-  std::istringstream stream(raw);
-  std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
-  ASSERT_THAT(header, IsNull());
-}
-
 TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
-  const size_t offset = 0x224;
+  const size_t offset = kIdmapRawDataOffset;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  idmap_raw_data_len - offset);
+                  kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
@@ -100,9 +89,9 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
-  const size_t offset = 0x224;
+  const size_t offset = kIdmapRawDataOffset;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  idmap_raw_data_len - offset);
+                  kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
@@ -127,7 +116,7 @@
 }
 
 TEST(IdmapTests, CreateIdmapFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   auto result = Idmap::FromBinaryStream(stream);
@@ -136,13 +125,14 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x05U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
-  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11);
+  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
   ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
-  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk");
-  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk");
+  ASSERT_EQ(idmap->GetHeader()->GetTargetPath(), kIdmapRawTargetPath);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), kIdmapRawOverlayPath);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), kIdmapRawOverlayName);
 
   const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
   ASSERT_EQ(dataBlocks.size(), 1U);
@@ -187,48 +177,23 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
+  auto idmap_result = Idmap::FromApkAssets(
+      *target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
+      /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x05U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
   ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
-  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetPath(), target_apk_path);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
-}
-
-Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
-    const android::StringPiece& local_target_apk_path,
-    const android::StringPiece& local_overlay_apk_path, const OverlayManifestInfo& overlay_info,
-    const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) {
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data());
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
-  }
-
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data());
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
-  }
-
-  LogInfo log_info;
-  auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info,
-                                                fulfilled_policies, enforce_overlayable, log_info);
-
-  if (!mapping) {
-    return mapping.GetError();
-  }
-
-  return IdmapData::FromResourceMapping(*mapping);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), TestConstants::OVERLAY_NAME_ALL_POLICIES);
 }
 
 TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
@@ -241,7 +206,8 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
+                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
                                            /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
@@ -271,6 +237,29 @@
   ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
 }
 
+TEST(IdmapTests, FailCreateIdmapInvalidName) {
+  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
+  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
+
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  {
+    auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC,
+                                             /* enforce_overlayable */ true);
+    ASSERT_FALSE(idmap_result);
+  }
+  {
+    auto idmap_result =
+        Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC,
+                             /* enforce_overlayable */ true);
+    ASSERT_FALSE(idmap_result);
+  }
+}
+
 TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
@@ -281,7 +270,8 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
+                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
                                            /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
@@ -296,34 +286,67 @@
   const auto& target_entries = data->GetTargetEntries();
   ASSERT_EQ(target_entries.size(), 4U);
   ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1,
-                      R::overlay_shared::integer::int1);
-  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, R::overlay_shared::string::str1);
-  ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, R::overlay_shared::string::str3);
-  ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, R::overlay_shared::string::str4);
+                      fix_package_id(R::overlay::integer::int1, 0));
+  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1,
+                      fix_package_id(R::overlay::string::str1, 0));
+  ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3,
+                      fix_package_id(R::overlay::string::str3, 0));
+  ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4,
+                      fix_package_id(R::overlay::string::str4, 0));
 
   const auto& target_inline_entries = data->GetTargetInlineEntries();
   ASSERT_EQ(target_inline_entries.size(), 0U);
 
   const auto& overlay_entries = data->GetOverlayEntries();
   ASSERT_EQ(target_entries.size(), 4U);
-  ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay_shared::integer::int1,
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], fix_package_id(R::overlay::integer::int1, 0),
                        R::target::integer::int1);
-  ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay_shared::string::str1,
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], fix_package_id(R::overlay::string::str1, 0),
                        R::target::string::str1);
-  ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay_shared::string::str3,
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], fix_package_id(R::overlay::string::str3, 0),
                        R::target::string::str3);
-  ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay_shared::string::str4,
+  ASSERT_OVERLAY_ENTRY(overlay_entries[3], fix_package_id(R::overlay::string::str4, 0),
                        R::target::string::str4);
 }
 
+Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
+    const std::string& local_target_apk_path, const std::string& local_overlay_apk_path,
+    const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
+    bool enforce_overlayable) {
+  auto overlay_info =
+      utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  if (!overlay_info) {
+    return overlay_info.GetError();
+  }
+
+  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
+  }
+
+  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  if (!overlay_apk) {
+    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+  }
+
+  LogInfo log_info;
+  auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
+                                                fulfilled_policies, enforce_overlayable, log_info);
+  if (!mapping) {
+    return mapping.GetError();
+  }
+
+  return IdmapData::FromResourceMapping(*mapping);
+}
+
 TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0x7f030001;  // xml/overlays_different_packages
-  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info,
-                                               PolicyFlags::PUBLIC,
-                                               /* enforce_overlayable */ false);
+  auto idmap_data =
+      TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages",
+
+                                 PolicyFlags::PUBLIC,
+                                 /* enforce_overlayable */ false);
 
   ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
   auto& data = *idmap_data;
@@ -343,12 +366,8 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataInlineResources) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0x7f030002;  // xml/overlays_inline
-  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info,
-                                               PolicyFlags::PUBLIC,
+  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk",
+                                               "Inline", PolicyFlags::PUBLIC,
                                                /* enforce_overlayable */ false);
 
   ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
@@ -357,7 +376,7 @@
   const auto& target_entries = data->GetTargetEntries();
   ASSERT_EQ(target_entries.size(), 0U);
 
-  constexpr size_t overlay_string_pool_size = 8U;
+  constexpr size_t overlay_string_pool_size = 10U;
   const auto& target_inline_entries = data->GetTargetInlineEntries();
   ASSERT_EQ(target_inline_entries.size(), 2U);
   ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
@@ -370,38 +389,20 @@
   ASSERT_EQ(overlay_entries.size(), 0U);
 }
 
-TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) {
-  std::string target_apk_path(GetTestDataPath());
-  for (int i = 0; i < 32; i++) {
-    target_apk_path += "/target/../";
-  }
-  target_apk_path += "/target/target.apk";
-  ASSERT_GT(target_apk_path.size(), kIdmapStringLength);
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
-
-  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
-
-  const auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
-  ASSERT_FALSE(result);
-}
-
 TEST(IdmapTests, IdmapHeaderIsUpToDate) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  const std::string target_apk_path = kIdmapRawTargetPath;
+  const std::string overlay_apk_path = kIdmapRawOverlayPath;
+  const std::string overlay_name = kIdmapRawOverlayName;
+  const PolicyBitmask policies = kIdmapRawDataPolicies;
+  const uint32_t target_crc = kIdmapRawDataTargetCrc;
+  const uint32_t overlay_crc = kIdmapRawOverlayCrc;
 
-  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::istringstream raw_stream(raw);
 
-  auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
-                                     /* enforce_overlayable */ true);
+  auto result = Idmap::FromBinaryStream(raw_stream);
   ASSERT_TRUE(result);
   const auto idmap = std::move(*result);
 
@@ -411,8 +412,9 @@
 
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
-  ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                 PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_TRUE(header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                 kIdmapRawDataTargetCrc, overlay_crc, policies,
+                                 /* enforce_overlayable */ true));
 
   // magic: bytes (0x0, 0x03)
   std::string bad_magic_string(stream.str());
@@ -425,8 +427,9 @@
       IdmapHeader::FromBinaryStream(bad_magic_stream);
   ASSERT_THAT(bad_magic_header, NotNull());
   ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                            target_crc, overlay_crc, policies,
+                                            /* enforce_overlayable */ true));
 
   // version: bytes (0x4, 0x07)
   std::string bad_version_string(stream.str());
@@ -439,8 +442,9 @@
       IdmapHeader::FromBinaryStream(bad_version_stream);
   ASSERT_THAT(bad_version_header, NotNull());
   ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                            target_crc, overlay_crc, policies,
+                                            /* enforce_overlayable */ true));
 
   // target crc: bytes (0x8, 0xb)
   std::string bad_target_crc_string(stream.str());
@@ -453,8 +457,9 @@
       IdmapHeader::FromBinaryStream(bad_target_crc_stream);
   ASSERT_THAT(bad_target_crc_header, NotNull());
   ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                            target_crc, overlay_crc, policies,
+                                            /* enforce_overlayable */ true));
 
   // overlay crc: bytes (0xc, 0xf)
   std::string bad_overlay_crc_string(stream.str());
@@ -467,8 +472,9 @@
       IdmapHeader::FromBinaryStream(bad_overlay_crc_stream);
   ASSERT_THAT(bad_overlay_crc_header, NotNull());
   ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                            target_crc, overlay_crc, policies,
+                                            /* enforce_overlayable */ true));
 
   // fulfilled policy: bytes (0x10, 0x13)
   std::string bad_policy_string(stream.str());
@@ -481,8 +487,9 @@
       IdmapHeader::FromBinaryStream(bad_policy_stream);
   ASSERT_THAT(bad_policy_header, NotNull());
   ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies());
-  ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                             PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                             target_crc, overlay_crc, policies,
+                                             /* enforce_overlayable */ true));
 
   // enforce overlayable: bytes (0x14)
   std::string bad_enforce_string(stream.str());
@@ -492,30 +499,47 @@
       IdmapHeader::FromBinaryStream(bad_enforce_stream);
   ASSERT_THAT(bad_enforce_header, NotNull());
   ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable());
-  ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                              PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                              target_crc, overlay_crc, policies,
+                                              /* enforce_overlayable */ true));
 
-  // target path: bytes (0x18, 0x117)
+  // target path: bytes (0x1c, 0x27)
   std::string bad_target_path_string(stream.str());
-  bad_target_path_string[0x18] = '\0';
+  bad_target_path_string[0x1c] = '\0';
   std::stringstream bad_target_path_stream(bad_target_path_string);
   std::unique_ptr<const IdmapHeader> bad_target_path_header =
       IdmapHeader::FromBinaryStream(bad_target_path_stream);
   ASSERT_THAT(bad_target_path_header, NotNull());
   ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                            target_crc, overlay_crc, policies,
+                                            /* enforce_overlayable */ true));
 
-  // overlay path: bytes (0x118, 0x217)
+  // overlay path: bytes (0x2c, 0x37)
   std::string bad_overlay_path_string(stream.str());
-  bad_overlay_path_string[0x118] = '\0';
+  bad_overlay_path_string[0x33] = '\0';
   std::stringstream bad_overlay_path_stream(bad_overlay_path_string);
   std::unique_ptr<const IdmapHeader> bad_overlay_path_header =
       IdmapHeader::FromBinaryStream(bad_overlay_path_stream);
   ASSERT_THAT(bad_overlay_path_header, NotNull());
   ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
-                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+  ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                                   target_crc, overlay_crc, policies,
+                                                   /* enforce_overlayable */ true));
+
+  // overlay path: bytes (0x3c, 0x47)
+  std::string bad_overlay_name_string(stream.str());
+  bad_overlay_name_string[0x3c] = '\0';
+  std::stringstream bad_overlay_name_stream(bad_overlay_name_string);
+  std::unique_ptr<const IdmapHeader> bad_overlay_name_header =
+      IdmapHeader::FromBinaryStream(bad_overlay_name_stream);
+  ASSERT_THAT(bad_overlay_name_header, NotNull());
+  ASSERT_NE(header->GetOverlayName(), bad_overlay_name_header->GetOverlayName());
+  ASSERT_FALSE(bad_overlay_name_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
+                                                   target_crc, overlay_crc, policies,
+                                                   /* enforce_overlayable */ true));
+
+  // overlay name: bytes (0x2c, 0x37)
 }
 
 class TestVisitor : public Visitor {
@@ -544,7 +568,7 @@
 };
 
 TEST(IdmapTests, TestVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(stream);
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index d30fbfc..87ce0f1 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -15,22 +15,21 @@
  */
 
 #include <memory>
-#include <sstream>
 #include <string>
 
 #include "R.h"
+#include "TestConstants.h"
 #include "TestHelpers.h"
 #include "androidfw/ApkAssets.h"
-#include "androidfw/Idmap.h"
 #include "androidfw/ResourceTypes.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "idmap2/Idmap.h"
 #include "idmap2/PrettyPrintVisitor.h"
 
-using ::testing::NotNull;
-
 using android::ApkAssets;
+using android::base::StringPrintf;
+using ::testing::NotNull;
 
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
@@ -46,7 +45,8 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk,
+                                          TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
@@ -56,15 +56,15 @@
 
   ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
   ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
-  ASSERT_NE(stream.str().find(R::target::integer::literal::int1 +
-                              " -> 0x7f010000 (integer/int1 -> integer/int1)\n"),
+  ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n",
+                                           R::target::integer::int1, R::overlay::integer::int1)),
             std::string::npos);
 }
 
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index 854b57f..ac9b058 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -23,22 +23,11 @@
 
 namespace android::idmap2 {
 
-static std::string hexify(ResourceId id) {
-  std::stringstream stream;
-  stream << std::hex << static_cast<uint32_t>(id);
-  return stream.str();
-}
-
 // clang-format off
 namespace R::target {
   namespace integer {  // NOLINT(runtime/indentation_namespace)
     constexpr ResourceId int1 = 0x7f010000;
-
-    namespace literal {  // NOLINT(runtime/indentation_namespace)
-      inline const std::string int1 = hexify(R::target::integer::int1);
-    }
   }
-
   namespace string {  // NOLINT(runtime/indentation_namespace)
     constexpr ResourceId not_overlayable = 0x7f020003;
     constexpr ResourceId other = 0x7f020004;
@@ -54,56 +43,31 @@
     constexpr ResourceId str1 = 0x7f02000e;
     constexpr ResourceId str3 = 0x7f020010;
     constexpr ResourceId str4 = 0x7f020011;
-
-    namespace literal {  // NOLINT(runtime/indentation_namespace)
-      inline const std::string str1 = hexify(R::target::string::str1);
-      inline const std::string str3 = hexify(R::target::string::str3);
-      inline const std::string str4 = hexify(R::target::string::str4);
-    }
   }  // namespace string
 }  // namespace R::target
 
 namespace R::overlay {
   namespace integer {  // NOLINT(runtime/indentation_namespace)
     constexpr ResourceId int1 = 0x7f010000;
+    constexpr ResourceId not_in_target = 0x7f010001;
   }
   namespace string {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId str1 = 0x7f020000;
-    constexpr ResourceId str3 = 0x7f020001;
-    constexpr ResourceId str4 = 0x7f020002;
+    constexpr ResourceId not_overlayable = 0x7f020000;
+    constexpr ResourceId other = 0x7f020001;
+    constexpr ResourceId policy_actor = 0x7f020002;
+    constexpr ResourceId policy_config_signature = 0x7f020003;
+    constexpr ResourceId policy_odm = 0x7f020004;
+    constexpr ResourceId policy_oem = 0x7f020005;
+    constexpr ResourceId policy_product = 0x7f020006;
+    constexpr ResourceId policy_public = 0x7f020007;
+    constexpr ResourceId policy_signature = 0x7f020008;
+    constexpr ResourceId policy_system = 0x7f020009;
+    constexpr ResourceId policy_system_vendor = 0x7f02000a;
+    constexpr ResourceId str1 = 0x7f02000b;
+    constexpr ResourceId str3 = 0x7f02000c;
+    constexpr ResourceId str4 = 0x7f02000d;
   }
 }
-
-namespace R::overlay_shared {
-  namespace integer {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId int1 = 0x00010000;
-  }
-  namespace string {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId str1 = 0x00020000;
-    constexpr ResourceId str3 = 0x00020001;
-    constexpr ResourceId str4 = 0x00020002;
-  }
-}
-
-namespace R::system_overlay::string {
-  constexpr ResourceId policy_public = 0x7f010000;
-  constexpr ResourceId policy_system = 0x7f010001;
-  constexpr ResourceId policy_system_vendor = 0x7f010002;
-}
-
-namespace R::system_overlay_invalid::string {
-  constexpr ResourceId not_overlayable = 0x7f010000;
-  constexpr ResourceId other = 0x7f010001;
-  constexpr ResourceId policy_actor = 0x7f010002;
-  constexpr ResourceId policy_config_signature = 0x7f010003;
-  constexpr ResourceId policy_odm = 0x7f010004;
-  constexpr ResourceId policy_oem = 0x7f010005;
-  constexpr ResourceId policy_product = 0x7f010006;
-  constexpr ResourceId policy_public = 0x7f010007;
-  constexpr ResourceId policy_signature = 0x7f010008;
-  constexpr ResourceId policy_system = 0x7f010009;
-  constexpr ResourceId policy_system_vendor = 0x7f01000a;
-}  // namespace R::system_overlay_invalid::string
 // clang-format on
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 95bd9473..88f85ef 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -56,8 +56,9 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ true);
+  const auto idmap =
+      Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT,
+                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
@@ -65,7 +66,7 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000005  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(
       StringPrintf(ADDRESS "%s  target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
       stream.str());
@@ -76,22 +77,34 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  enforce overlayable\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  string pool index offset\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool size\n", stream.str());
-  ASSERT_CONTAINS_REGEX("000002bc: ........  string pool: ...\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000000  target inline entry count", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "0000000a  string pool index offset", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool size", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "........  string pool", stream.str());
 }
 
 TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -102,11 +115,17 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000005  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00001234  target crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00005678  overlay crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000011  fulfilled policies: public|signature\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  enforce overlayable\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "0000000b  target path size\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "........  target path: targetX.apk\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "0000000c  overlay path size\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay path: overlayX.apk\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "0000000b  overlay name size\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay name: OverlayName\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000003  target entry count\n", stream.str());
@@ -121,7 +140,7 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  overlay id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f030002  target id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  string pool size\n", stream.str());
-  ASSERT_CONTAINS_REGEX("00000278: ........  string pool: ...\n", stream.str());
+  ASSERT_CONTAINS_REGEX("000000a8: ........  string pool\n", stream.str());
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 185e929..0362529 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -17,12 +17,10 @@
 #include <cstdio>  // fclose
 #include <fstream>
 #include <memory>
-#include <sstream>
 #include <string>
-#include <utility>
-#include <vector>
 
 #include "R.h"
+#include "TestConstants.h"
 #include "TestHelpers.h"
 #include "androidfw/ResourceTypes.h"
 #include "gmock/gmock.h"
@@ -43,38 +41,32 @@
     ASSERT_TRUE(result) << result.GetErrorMessage(); \
   } while (0)
 
-Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
-                                               const android::StringPiece& local_overlay_apk_path,
-                                               const OverlayManifestInfo& overlay_info,
+Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path,
+                                               const std::string& local_overlay_apk_path,
+                                               const std::string& overlay_name,
                                                const PolicyBitmask& fulfilled_policies,
                                                bool enforce_overlayable) {
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data());
+  auto overlay_info =
+      ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  if (!overlay_info) {
+    return overlay_info.GetError();
+  }
+
+  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
   std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
   if (!target_apk) {
     return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
   }
 
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data());
+  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   if (!overlay_apk) {
     return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
   }
 
   LogInfo log_info;
-  return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies,
-                                        enforce_overlayable, log_info);
-}
-
-Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
-                                               const android::StringPiece& local_overlay_apk_path,
-                                               const PolicyBitmask& fulfilled_policies,
-                                               bool enforce_overlayable) {
-  auto overlay_info = ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path.data());
-  if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-  return TestGetResourceMapping(local_target_apk_path, local_overlay_apk_path, *overlay_info,
-                                fulfilled_policies, enforce_overlayable);
+  return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
+                                        fulfilled_policies, enforce_overlayable, log_info);
 }
 
 Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource,
@@ -136,13 +128,8 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0U;  // no xml
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
-                                          PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ false);
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+                                          PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   auto& res = *resources;
@@ -158,11 +145,7 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0x7f030003;  // xml/overlays_swap
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -178,12 +161,8 @@
 }
 
 TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0x7f030001;  // xml/overlays_different_packages
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
-                                          PolicyFlags::PUBLIC,
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+                                          "DifferentPackages", PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
@@ -192,19 +171,15 @@
   ASSERT_EQ(res.GetOverlayToTargetMap().size(), 1U);
   ASSERT_RESULT(MappingExists(res, R::target::string::str1, 0x0104000a,
                               false /* rewrite */));  // -> android:string/ok
-  ASSERT_RESULT(MappingExists(res, R::target::string::str3, 0x7f020001, true /* rewrite */));
+  ASSERT_RESULT(
+      MappingExists(res, R::target::string::str3, R::overlay::string::str3, true /* rewrite */));
 }
 
 TEST(ResourceMappingTests, InlineResources) {
-  OverlayManifestInfo info{};
-  info.target_package = "test.target";
-  info.target_name = "TestResources";
-  info.resource_mapping = 0x7f030002;  // xml/overlays_inline
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
-                                          PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ false);
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline",
+                                          PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
-  constexpr size_t overlay_string_pool_size = 8U;
+  constexpr size_t overlay_string_pool_size = 10U;
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   auto& res = *resources;
   ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
@@ -215,28 +190,8 @@
 }
 
 TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
-  auto resources =
-      TestGetResourceMapping("/target/target.apk", "/system-overlay/system-overlay.apk",
-                             PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
-                             /* enforce_overlayable */ true);
-
-  ASSERT_TRUE(resources) << resources.GetErrorMessage();
-  auto& res = *resources;
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
-  ASSERT_RESULT(MappingExists(res, R::target::string::policy_public,
-                              R::system_overlay::string::policy_public, false /* rewrite */));
-  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system,
-                              R::system_overlay::string::policy_system, false /* rewrite */));
-  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor,
-                              R::system_overlay::string::policy_system_vendor,
-                              false /* rewrite */));
-}
-
-// Resources that are not declared as overlayable and resources that a protected by policies the
-// overlay does not fulfill must not map to overlay resources.
-TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
-  auto resources = TestGetResourceMapping("/target/target.apk",
-                                          "/system-overlay-invalid/system-overlay-invalid.apk",
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+                                          TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
 
@@ -244,22 +199,38 @@
   auto& res = *resources;
   ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_public,
-                              R::system_overlay_invalid::string::policy_public,
-                              false /* rewrite */));
+                              R::overlay::string::policy_public, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_system,
-                              R::system_overlay_invalid::string::policy_system,
-                              false /* rewrite */));
+                              R::overlay::string::policy_system, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor,
-                              R::system_overlay_invalid::string::policy_system_vendor,
-                              false /* rewrite */));
+                              R::overlay::string::policy_system_vendor, true /* rewrite */));
+}
+
+// Resources that are not declared as overlayable and resources that a protected by policies the
+// overlay does not fulfill must not map to overlay resources.
+TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+                                          TestConstants::OVERLAY_NAME_ALL_POLICIES,
+                                          PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_public,
+                              R::overlay::string::policy_public, true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system,
+                              R::overlay::string::policy_system, true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor,
+                              R::overlay::string::policy_system_vendor, true /* rewrite */));
 }
 
 // Resources that are not declared as overlayable and resources that a protected by policies the
 // overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
 // off.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
-  auto resources = TestGetResourceMapping("/target/target.apk",
-                                          "/system-overlay-invalid/system-overlay-invalid.apk",
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+                                          TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -267,41 +238,33 @@
   auto& res = *resources;
   ASSERT_EQ(res.GetTargetToOverlayMap().size(), 11U);
   ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable,
-                              R::system_overlay_invalid::string::not_overlayable,
-                              false /* rewrite */));
-  ASSERT_RESULT(MappingExists(res, R::target::string::other,
-                              R::system_overlay_invalid::string::other, false /* rewrite */));
+                              R::overlay::string::not_overlayable, true /* rewrite */));
+  ASSERT_RESULT(
+      MappingExists(res, R::target::string::other, R::overlay::string::other, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor,
-                              R::system_overlay_invalid::string::policy_actor,
-                              false /* rewrite */));
-  ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm,
-                              R::system_overlay_invalid::string::policy_odm, false /* rewrite */));
-  ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem,
-                              R::system_overlay_invalid::string::policy_oem, false /* rewrite */));
+                              R::overlay::string::policy_actor, true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, R::overlay::string::policy_odm,
+                              true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, R::overlay::string::policy_oem,
+                              true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_product,
-                              R::system_overlay_invalid::string::policy_product,
-                              false /* rewrite */));
+                              R::overlay::string::policy_product, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_public,
-                              R::system_overlay_invalid::string::policy_public,
-                              false /* rewrite */));
+                              R::overlay::string::policy_public, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature,
-                              R::system_overlay_invalid::string::policy_config_signature,
-                              false /* rewrite */));
+                              R::overlay::string::policy_config_signature, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature,
-                              R::system_overlay_invalid::string::policy_signature,
-                              false /* rewrite */));
+                              R::overlay::string::policy_signature, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_system,
-                              R::system_overlay_invalid::string::policy_system,
-                              false /* rewrite */));
+                              R::overlay::string::policy_system, true /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor,
-                              R::system_overlay_invalid::string::policy_system_vendor,
-                              false /* rewrite */));
+                              R::overlay::string::policy_system_vendor, true /* rewrite */));
 }
 
-// Overlays that do not target an <overlayable> tag can overlay resources defined within any
-// <overlayable> tag.
+// Overlays that do not target an <overlayable> tag can overlay any resource if overlayable
+// enforcement is disabled.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-no-name.apk",
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -321,9 +284,10 @@
 // Overlays that are neither pre-installed nor signed with the same signature as the target cannot
 // overlay packages that have not defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
-  auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
-                                          "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ true);
+  auto resources =
+      TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
+                             "NoTargetName", PolicyFlags::PUBLIC,
+                             /* enforce_overlayable */ true);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
@@ -334,46 +298,36 @@
 // defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
   auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
-    auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
-                                            "/system-overlay-invalid/system-overlay-invalid.apk",
-                                            fulfilled_policies,
-                                            /* enforce_overlayable */ true);
+    auto resources =
+        TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
+                               TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies,
+                               /* enforce_overlayable */ true);
 
     ASSERT_TRUE(resources) << resources.GetErrorMessage();
     auto& res = *resources;
     ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 11U);
     ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable,
-                                R::system_overlay_invalid::string::not_overlayable,
-                                false /* rewrite */));
-    ASSERT_RESULT(MappingExists(res, R::target::string::other,
-                                R::system_overlay_invalid::string::other, false /* rewrite */));
+                                R::overlay::string::not_overlayable, true /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::other, R::overlay::string::other,
+                                true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor,
-                                R::system_overlay_invalid::string::policy_actor,
-                                false /* rewrite */));
-    ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm,
-                                R::system_overlay_invalid::string::policy_odm,
-                                false /* rewrite */));
-    ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem,
-                                R::system_overlay_invalid::string::policy_oem,
-                                false /* rewrite */));
+                                R::overlay::string::policy_actor, true /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, R::overlay::string::policy_odm,
+                                true /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, R::overlay::string::policy_oem,
+                                true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_product,
-                                R::system_overlay_invalid::string::policy_product,
-                                false /* rewrite */));
+                                R::overlay::string::policy_product, true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_public,
-                                R::system_overlay_invalid::string::policy_public,
-                                false /* rewrite */));
+                                R::overlay::string::policy_public, true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature,
-                                R::system_overlay_invalid::string::policy_config_signature,
-                                false /* rewrite */));
+                                R::overlay::string::policy_config_signature, true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature,
-                                R::system_overlay_invalid::string::policy_signature,
-                                false /* rewrite */));
+                                R::overlay::string::policy_signature, true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_system,
-                                R::system_overlay_invalid::string::policy_system,
-                                false /* rewrite */));
+                                R::overlay::string::policy_system, true /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor,
-                                R::system_overlay_invalid::string::policy_system_vendor,
-                                false /* rewrite */));
+                                R::overlay::string::policy_system_vendor, true /* rewrite */));
   };
 
   CheckEntries(PolicyFlags::SIGNATURE);
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
index 9ed807c..1f6bf49 100644
--- a/cmds/idmap2/tests/ResourceUtilsTests.cpp
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -59,4 +59,26 @@
   ASSERT_FALSE(name);
 }
 
-}  // namespace android::idmap2
+TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) {
+  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
+                                                "InvalidName");
+  ASSERT_FALSE(info);
+}
+
+TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) {
+  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
+                                                "ValidName");
+  ASSERT_FALSE(info);
+}
+
+TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) {
+  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
+                                                "ValidNameAndTargetPackage");
+  ASSERT_TRUE(info);
+  ASSERT_EQ("ValidNameAndTargetPackage", info->name);
+  ASSERT_EQ("Valid", info->target_package);
+  ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
+  ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
+}
+
+}// namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index 69575b8..d5799ad 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -22,8 +22,11 @@
 constexpr const auto TARGET_CRC = 0x7c2d4719;
 constexpr const auto TARGET_CRC_STRING = "7c2d4719";
 
-constexpr const auto OVERLAY_CRC = 0x5afff726;
-constexpr const auto OVERLAY_CRC_STRING = "5afff726";
+constexpr const auto OVERLAY_CRC = 0xb71095cf;
+constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
+
+constexpr const char* OVERLAY_NAME_DEFAULT = "Default";
+constexpr const char* OVERLAY_NAME_ALL_POLICIES = "AllPolicies";
 
 }  // namespace android::idmap2::TestConstants
 
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index d0a8e3d..842af3d 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -30,7 +30,7 @@
     0x49, 0x44, 0x4d, 0x50,
 
     // 0x4: version
-    0x05, 0x00, 0x00, 0x00,
+    0x07, 0x00, 0x00, 0x00,
 
     // 0x8: target crc
     0x34, 0x12, 0x00, 0x00,
@@ -44,125 +44,114 @@
     // 0x14: enforce overlayable
     0x01, 0x00, 0x00, 0x00,
 
-    // 0x18: target path "targetX.apk"
-    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // 0x18: target path length
+    0x0b, 0x00, 0x00, 0x00,
 
-    // 0x118: overlay path "overlayX.apk"
-    0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // 0x1c: target path "targetX.apk"
+    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00,
 
-    // 0x218: debug string
+    // 0x28: overlay path length
+    0x0c, 0x00, 0x00, 0x00,
+
+    // 0x2c: overlay path "overlayX.apk"
+    0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b,
+
+    // 0x38: overlay name length
+    0x0b, 0x00, 0x00, 0x00,
+
+    // 0x3c: overlay name "OverlayName"
+    0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6D, 0x65, 0x00,
+
+    // 0x48 -> 4c: debug string
     // string length,
     0x05, 0x00, 0x00, 0x00,
 
-    // 0x21c string contents "debug\0\0\0" (padded to word alignment)
+    // 0x4c string contents "debug\0\0\0" (padded to word alignment)
     0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
 
     // DATA HEADER
-    // 0x224: target_package_id
+    // 0x54: target_package_id
     0x7f,
 
-    // 0x225: overlay_package_id
+    // 0x55: overlay_package_id
     0x7f,
 
-    // 0x226: padding
+    // 0x56: padding
     0x00, 0x00,
 
-    // 0x228: target_entry_count
+    // 0x58: target_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x22c: target_inline_entry_count
+    // 0x5c: target_inline_entry_count
     0x01, 0x00, 0x00, 0x00,
 
-    // 0x230: overlay_entry_count
+    // 0x60: overlay_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x234: string_pool_offset
+    // 0x64: string_pool_offset
     0x00, 0x00, 0x00, 0x00,
 
     // TARGET ENTRIES
-    // 0x238: target id (0x7f020000)
+    // 0x68: target id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x23c: overlay_id (0x7f020000)
+    // 0x6c: overlay_id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x240: target id (0x7f030000)
+    // 0x70: target id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x244: overlay_id (0x7f030000)
+    // 0x74: overlay_id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x248: target id (0x7f030002)
+    // 0x78: target id (0x7f030002)
     0x02, 0x00, 0x03, 0x7f,
 
-    // 0x24c: overlay_id (0x7f030001)
+    // 0x7c: overlay_id (0x7f030001)
     0x01, 0x00, 0x03, 0x7f,
 
     // INLINE TARGET ENTRIES
 
-    // 0x250: target_id
+    // 0x80: target_id
     0x00, 0x00, 0x04, 0x7f,
 
-    // 0x254: Res_value::size (value ignored by idmap)
+    // 0x84: Res_value::size (value ignored by idmap)
     0x08, 0x00,
 
-    // 0x256: Res_value::res0 (value ignored by idmap)
+    // 0x87: Res_value::res0 (value ignored by idmap)
     0x00,
 
-    // 0x257: Res_value::dataType (TYPE_INT_HEX)
+    // 0x88: Res_value::dataType (TYPE_INT_HEX)
     0x11,
 
-    // 0x258: Res_value::data
+    // 0x8c: Res_value::data
     0x78, 0x56, 0x34, 0x12,
 
     // OVERLAY ENTRIES
-    // 0x25c: 0x7f020000 -> 0x7f020000
+    // 0x90: 0x7f020000 -> 0x7f020000
     0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
 
-    // 0x264: 0x7f030000 -> 0x7f030000
+    // 0x98: 0x7f030000 -> 0x7f030000
     0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
 
-    // 0x26c: 0x7f030001 -> 0x7f030002
+    // 0xa0: 0x7f030001 -> 0x7f030002
     0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
 
-    // 0x274: string pool
+    // 0xa4: string pool
     // string length,
     0x04, 0x00, 0x00, 0x00,
 
-    // 0x278 string contents "test" (padded to word alignment)
+    // 0xa8 string contents "test"
     0x74, 0x65, 0x73, 0x74};
 
-const unsigned int idmap_raw_data_len = 0x27c;
+const unsigned int kIdmapRawDataLen = 0xac;
+const unsigned int kIdmapRawDataOffset = 0x54;
+const unsigned int kIdmapRawDataTargetCrc = 0x1234;
+const unsigned int kIdmapRawOverlayCrc = 0x5678;
+const unsigned int kIdmapRawDataPolicies = 0x11;
+inline const std::string kIdmapRawTargetPath = "targetX.apk";
+inline const std::string kIdmapRawOverlayPath = "overlayX.apk";
+inline const std::string kIdmapRawOverlayName = "OverlayName";
 
 std::string GetTestDataPath();
 
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
index cf3691c..2c50dee 100644
--- a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
@@ -13,14 +13,37 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay">
-
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="test.overlay">
     <application android:hasCode="false"/>
 
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"
-        android:resourcesMap="@xml/overlays"/>
+    <overlay android:name="Default"
+             android:targetPackage="test.target"
+             android:targetName="TestResources"
+             android:resourcesMap="@xml/overlays"/>
+
+    <overlay android:name="NoTargetName"
+             android:targetPackage="test.target"
+             android:resourcesMap="@xml/overlays"/>
+
+    <overlay android:name="Inline"
+             android:targetName="TestResources"
+             android:targetPackage="test.target"
+             android:resourcesMap="@xml/overlays_inline"/>
+
+    <overlay android:name="DifferentPackages"
+             android:targetName="TestResources"
+             android:targetPackage="test.target"
+             android:resourcesMap="@xml/overlays_different_package"/>
+
+    <overlay android:name="SwapNames"
+             android:targetName="TestResources"
+             android:targetPackage="test.target"
+             android:resourcesMap="@xml/overlays_swap"/>
+
+    <overlay android:name="AllPolicies"
+             android:targetName="TestResources"
+             android:targetPackage="test.target"
+             android:resourcesMap="@xml/overlays_policies"/>
+
 </manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestInvalid.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestInvalid.xml
new file mode 100644
index 0000000..d61c36c
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestInvalid.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="test.overlay">
+    <application android:hasCode="false"/>
+
+    <overlay name="InvalidName"
+         android:targetName="Invalid"
+         android:targetPackage="Invalid"
+         android:resourcesMap="@xml/overlays_swap"/>
+
+    <overlay android:name="ValidName"
+         targetName="Invalid"
+         targetPackage="Invalid"
+         resourcesMap="@xml/overlays_swap"/>
+
+    <overlay android:name="ValidNameAndTargetPackage"
+         targetName="Invalid"
+         android:targetPackage="Valid"
+         resourcesMap="@xml/overlays_swap"/>
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestLegacy.xml
similarity index 71%
rename from cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
rename to cmds/idmap2/tests/data/overlay/AndroidManifestLegacy.xml
index 70efc86..9fc2105 100644
--- a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestLegacy.xml
@@ -13,12 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.static2">
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"
-        android:isStatic="true"
-        android:priority="2" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="test.overlay">
+    <application android:hasCode="false"/>
+
+    <overlay android:targetPackage="test.target"/>
 </manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestNoName.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestNoName.xml
deleted file mode 100644
index bc6b733..0000000
--- a/cmds/idmap2/tests/data/overlay/AndroidManifestNoName.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.no.name">
-    <overlay
-        android:targetPackage="test.target"/>
-</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestNoNameStatic.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestNoNameStatic.xml
deleted file mode 100644
index ed327ce..0000000
--- a/cmds/idmap2/tests/data/overlay/AndroidManifestNoNameStatic.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.no.name.static">
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"
-        android:isStatic="true"
-        android:priority="1" />
-</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
deleted file mode 100644
index 1c4dae6..0000000
--- a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.static1">
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"
-        android:isStatic="true"
-        android:priority="1" />
-</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
index 114b099..7b1a66f 100755
--- a/cmds/idmap2/tests/data/overlay/build
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -26,37 +26,23 @@
 aapt2 link \
     --no-resource-removal \
     -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifestNoName.xml \
-    -o overlay-no-name.apk \
-    compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifestNoNameStatic.xml \
-    -o overlay-no-name-static.apk \
-    compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifestStatic1.xml \
-    -o overlay-static-1.apk \
-    compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifestStatic2.xml \
-    -o overlay-static-2.apk \
-    compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    --shared-lib \
-    -I "$FRAMEWORK_RES_APK" \
     --manifest AndroidManifest.xml \
     -o overlay-shared.apk \
+    --shared-lib \
+    compiled.flata
+
+aapt2 link \
+    --no-resource-removal \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifestLegacy.xml \
+    -o overlay-legacy.apk \
+    compiled.flata
+
+aapt2 link \
+    --no-resource-removal \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifestInvalid.xml \
+    -o overlay-invalid.apk \
     compiled.flata
 
 rm compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-invalid.apk b/cmds/idmap2/tests/data/overlay/overlay-invalid.apk
new file mode 100644
index 0000000..888c871e
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-invalid.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-legacy.apk b/cmds/idmap2/tests/data/overlay/overlay-legacy.apk
new file mode 100644
index 0000000..f03eebb
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-legacy.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
deleted file mode 100644
index dab25b1..0000000
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
deleted file mode 100644
index c8b95c2..0000000
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
index 0a8b737..3c896ea7 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-shared.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
deleted file mode 100644
index fd41182..0000000
--- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
deleted file mode 100644
index b24765f..0000000
--- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
index 870575e..c7ea623 100644
--- a/cmds/idmap2/tests/data/overlay/overlay.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml
index 815d1a8..6e98b21 100644
--- a/cmds/idmap2/tests/data/overlay/res/values/values.xml
+++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml
@@ -18,4 +18,25 @@
     <string name="str3">overlay-3</string>
     <integer name="int1">-1</integer>
     <integer name="not_in_target">-1</integer>
+
+    <!-- This overlay will fulfill the policies "public|system". This allows it overlay the
+     following resources. -->
+    <string name="overlay_policy_system">overlaid</string>
+    <string name="overlay_policy_system_vendor">overlaid</string>
+    <string name="overlay_policy_public">overlaid</string>
+
+    <!-- Requests to overlay a resource that belongs to a policy the overlay does not fulfill. -->
+    <string name="overlay_policy_product">overlaid</string>
+    <string name="overlay_policy_signature">overlaid</string>
+    <string name="overlay_policy_odm">overlaid</string>
+    <string name="overlay_policy_oem">overlaid</string>
+    <string name="overlay_policy_actor">overlaid</string>
+    <string name="overlay_policy_config_signature">overlaid</string>
+
+    <!-- Requests to overlay a resource that is not declared as overlayable. -->
+    <string name="overlay_not_overlayable">overlaid</string>
+
+    <!-- Requests to overlay a resource that is defined in an overlayable with a name other than
+         the targetName in the manifest. -->
+    <string name="overlay_other">overlaid</string>
 </resources>
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml
new file mode 100644
index 0000000..747f448
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<overlay>
+    <item target="string/policy_system" value="@string/overlay_policy_system"/>
+    <item target="string/policy_system_vendor" value="@string/overlay_policy_system_vendor" />
+    <item target="string/policy_public" value="@string/overlay_policy_public" />
+    <item target="string/policy_product" value="@string/overlay_policy_product"/>
+    <item target="string/policy_signature" value="@string/overlay_policy_signature" />
+    <item target="string/policy_odm" value="@string/overlay_policy_odm" />
+    <item target="string/policy_oem" value="@string/overlay_policy_oem"/>
+    <item target="string/policy_actor" value="@string/overlay_policy_actor" />
+    <item target="string/policy_config_signature" value="@string/overlay_policy_config_signature" />
+
+    <!-- Requests to overlay a resource that is not declared as overlayable. -->
+    <item target="string/not_overlayable" value="@string/overlay_not_overlayable" />
+
+    <!-- Requests to overlay a resource that is defined in an overlayable with a name other than
+         the targetName in the manifest. -->
+    <item target="string/other" value="@string/overlay_other" />
+</overlay>
\ No newline at end of file
diff --git a/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml
deleted file mode 100644
index 5df0bea..0000000
--- a/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.system">
-    <application android:hasCode="false"/>
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"
-        android:isStatic="true"
-        android:priority="10"/>
-</manifest>
diff --git a/cmds/idmap2/tests/data/signature-overlay/build b/cmds/idmap2/tests/data/signature-overlay/build
deleted file mode 100755
index fdd8301..0000000
--- a/cmds/idmap2/tests/data/signature-overlay/build
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
-
-aapt2 compile --dir res -o compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifest.xml \
-    -o signature-overlay.apk \
-    compiled.flata
-
-rm compiled.flata
diff --git a/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml b/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml
deleted file mode 100644
index 59e7d8e..0000000
--- a/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
-    <!-- This overlay will fulfill the policy "signature". This allows it overlay the
-     following resources. -->
-    <string name="policy_signature">policy_signature</string>
-</resources>
diff --git a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk b/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk
deleted file mode 100644
index e0fd204..0000000
--- a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml b/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml
deleted file mode 100644
index c7b652c..0000000
--- a/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.system.invalid">
-    <application android:hasCode="false"/>
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"/>
-</manifest>
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/build b/cmds/idmap2/tests/data/system-overlay-invalid/build
deleted file mode 100755
index 920e1f8..0000000
--- a/cmds/idmap2/tests/data/system-overlay-invalid/build
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
-
-aapt2 compile --dir res -o compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifest.xml \
-    -o system-overlay-invalid.apk \
-    compiled.flata
-
-rm compiled.flata
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
deleted file mode 100644
index ebaf49c..0000000
--- a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
-    <!-- This overlay will fulfill the policies "public|system". This allows it overlay the
-         following resources. -->
-    <string name="policy_system">policy_system</string>
-    <string name="policy_system_vendor">policy_system_vendor</string>
-    <string name="policy_public">policy_public</string>
-
-    <!-- Requests to overlay a resource that belongs to a policy the overlay does not fulfill. -->
-    <string name="policy_product">policy_product</string>
-    <string name="policy_signature">policy_signature</string>
-    <string name="policy_odm">policy_odm</string>
-    <string name="policy_oem">policy_oem</string>
-    <string name="policy_actor">policy_actor</string>
-    <string name="policy_config_signature">policy_config_signature</string>
-
-    <!-- Requests to overlay a resource that is not declared as overlayable. -->
-    <string name="not_overlayable">not_overlayable</string>
-
-    <!-- Requests to overlay a resource that is defined in an overlayable with a name other than
-         the targetName in the manifest. -->
-    <string name="other">other</string>
-</resources>
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
deleted file mode 100644
index a63daf8..0000000
--- a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
+++ /dev/null
Binary files differ
diff --git a/cmds/idmap2/tests/data/system-overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/system-overlay/AndroidManifest.xml
deleted file mode 100644
index 9e6a453..0000000
--- a/cmds/idmap2/tests/data/system-overlay/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="test.overlay.system">
-    <application android:hasCode="false"/>
-    <overlay
-        android:targetPackage="test.target"
-        android:targetName="TestResources"/>
-</manifest>
diff --git a/cmds/idmap2/tests/data/system-overlay/build b/cmds/idmap2/tests/data/system-overlay/build
deleted file mode 100755
index be0d239..0000000
--- a/cmds/idmap2/tests/data/system-overlay/build
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
-
-aapt2 compile --dir res -o compiled.flata
-
-aapt2 link \
-    --no-resource-removal \
-    -I "$FRAMEWORK_RES_APK" \
-    --manifest AndroidManifest.xml \
-    -o system-overlay.apk \
-    compiled.flata
-
-rm compiled.flata
diff --git a/cmds/idmap2/tests/data/system-overlay/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay/res/values/values.xml
deleted file mode 100644
index 6aaa0b0..0000000
--- a/cmds/idmap2/tests/data/system-overlay/res/values/values.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
-    <!-- This overlay will fulfill the policies "public|system". This allows it overlay the
-     following resources. -->
-    <string name="policy_system">policy_system</string>
-    <string name="policy_system_vendor">policy_system_vendor</string>
-    <string name="policy_public">policy_public</string>
-</resources>
diff --git a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk b/cmds/idmap2/tests/data/system-overlay/system-overlay.apk
deleted file mode 100644
index 90d2803..0000000
--- a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk
+++ /dev/null
Binary files differ
diff --git a/config/hiddenapi-temp-blocklist.txt b/config/hiddenapi-temp-blocklist.txt
index 246eeea..753bc69b 100644
--- a/config/hiddenapi-temp-blocklist.txt
+++ b/config/hiddenapi-temp-blocklist.txt
@@ -47,9 +47,6 @@
 Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V
 Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z
 Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I
-Lcom/android/internal/location/ILocationProvider$Stub;-><init>()V
-Lcom/android/internal/location/ILocationProviderManager$Stub;-><init>()V
-Lcom/android/internal/location/ILocationProviderManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProviderManager;
 Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String;
 Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I
 Lcom/android/internal/widget/IRemoteViewsFactory$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/IRemoteViewsFactory;
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 8a377ac..90a526b 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -269,7 +269,6 @@
 Lcom/android/internal/app/IVoiceInteractionManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IVoiceInteractionManagerService;
 Lcom/android/internal/appwidget/IAppWidgetService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/appwidget/IAppWidgetService;
 Lcom/android/internal/backup/IBackupTransport$Stub;-><init>()V
-Lcom/android/internal/location/ILocationProvider$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProvider;
 Lcom/android/internal/os/IDropBoxManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/os/IDropBoxManagerService;
 Lcom/android/internal/policy/IKeyguardService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardService;
 Lcom/android/internal/policy/IKeyguardStateCallback$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardStateCallback;
diff --git a/core/api/current.txt b/core/api/current.txt
index 4ad5e49..2ce0b35 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19320,6 +19320,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.OnNmeaMessageListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent);
     method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int);
+    method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties);
     method @Deprecated public void clearTestProviderEnabled(@NonNull String);
     method @Deprecated public void clearTestProviderLocation(@NonNull String);
     method @Deprecated public void clearTestProviderStatus(@NonNull String);
@@ -19334,7 +19335,7 @@
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.location.GpsStatus getGpsStatus(@Nullable android.location.GpsStatus);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.location.Location getLastKnownLocation(@NonNull String);
     method @Deprecated @Nullable public android.location.LocationProvider getProvider(@NonNull String);
-    method @Nullable public android.location.ProviderProperties getProviderProperties(@NonNull String);
+    method @Nullable public android.location.provider.ProviderProperties getProviderProperties(@NonNull String);
     method @NonNull public java.util.List<java.lang.String> getProviders(boolean);
     method @NonNull public java.util.List<java.lang.String> getProviders(@NonNull android.location.Criteria, boolean);
     method public boolean hasProvider(@NonNull String);
@@ -19466,6 +19467,24 @@
     method public void onNmeaMessage(String, long);
   }
 
+  public abstract class SettingInjectorService extends android.app.Service {
+    ctor public SettingInjectorService(String);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method protected abstract boolean onGetEnabled();
+    method protected abstract String onGetSummary();
+    method public final void onStart(android.content.Intent, int);
+    method public final int onStartCommand(android.content.Intent, int, int);
+    method public static final void refreshSettings(@NonNull android.content.Context);
+    field public static final String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged";
+    field public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
+    field public static final String ATTRIBUTES_NAME = "injected-location-setting";
+    field public static final String META_DATA_NAME = "android.location.SettingInjectorService";
+  }
+
+}
+
+package android.location.provider {
+
   public final class ProviderProperties implements android.os.Parcelable {
     method public int describeContents();
     method public int getAccuracy();
@@ -19480,24 +19499,25 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int ACCURACY_COARSE = 2; // 0x2
     field public static final int ACCURACY_FINE = 1; // 0x1
-    field @NonNull public static final android.os.Parcelable.Creator<android.location.ProviderProperties> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ProviderProperties> CREATOR;
     field public static final int POWER_USAGE_HIGH = 3; // 0x3
     field public static final int POWER_USAGE_LOW = 1; // 0x1
     field public static final int POWER_USAGE_MEDIUM = 2; // 0x2
   }
 
-  public abstract class SettingInjectorService extends android.app.Service {
-    ctor public SettingInjectorService(String);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method protected abstract boolean onGetEnabled();
-    method protected abstract String onGetSummary();
-    method public final void onStart(android.content.Intent, int);
-    method public final int onStartCommand(android.content.Intent, int, int);
-    method public static final void refreshSettings(@NonNull android.content.Context);
-    field public static final String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged";
-    field public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
-    field public static final String ATTRIBUTES_NAME = "injected-location-setting";
-    field public static final String META_DATA_NAME = "android.location.SettingInjectorService";
+  public static final class ProviderProperties.Builder {
+    ctor public ProviderProperties.Builder();
+    ctor public ProviderProperties.Builder(@NonNull android.location.provider.ProviderProperties);
+    method @NonNull public android.location.provider.ProviderProperties build();
+    method @NonNull public android.location.provider.ProviderProperties.Builder setAccuracy(int);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasAltitudeSupport(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasBearingSupport(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasCellRequirement(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasMonetaryCost(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasNetworkRequirement(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasSatelliteRequirement(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setHasSpeedSupport(boolean);
+    method @NonNull public android.location.provider.ProviderProperties.Builder setPowerUsage(int);
   }
 
 }
@@ -39775,6 +39795,8 @@
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method public void notifyConfigChangedForSubId(int);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
+    field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0
+    field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; // 0x1
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
     field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
@@ -39816,6 +39838,7 @@
     field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array";
     field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
     field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
+    field public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = "carrier_cross_sim_ims_available_bool";
     field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
     field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
     field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
@@ -39867,6 +39890,7 @@
     field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
     field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
+    field public static final String KEY_CROSS_SIM_SPN_FORMAT_INT = "cross_sim_spn_format_int";
     field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
     field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
@@ -54046,7 +54070,7 @@
     ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews);
     ctor public RemoteViews(android.widget.RemoteViews);
     ctor public RemoteViews(android.os.Parcel);
-    method public void addView(int, android.widget.RemoteViews);
+    method public void addView(@IdRes int, android.widget.RemoteViews);
     method public android.view.View apply(android.content.Context, android.view.ViewGroup);
     method @Deprecated public android.widget.RemoteViews clone();
     method public int describeContents();
@@ -54054,53 +54078,53 @@
     method public String getPackage();
     method @Deprecated public boolean onLoadClass(Class);
     method public void reapply(android.content.Context, android.view.View);
-    method public void removeAllViews(int);
-    method public void setAccessibilityTraversalAfter(int, int);
-    method public void setAccessibilityTraversalBefore(int, int);
-    method public void setBitmap(int, String, android.graphics.Bitmap);
-    method public void setBoolean(int, String, boolean);
-    method public void setBundle(int, String, android.os.Bundle);
-    method public void setByte(int, String, byte);
-    method public void setChar(int, String, char);
-    method public void setCharSequence(int, String, CharSequence);
-    method public void setChronometer(int, long, String, boolean);
-    method public void setChronometerCountDown(int, boolean);
-    method public void setContentDescription(int, CharSequence);
-    method public void setDisplayedChild(int, int);
-    method public void setDouble(int, String, double);
-    method public void setEmptyView(int, int);
-    method public void setFloat(int, String, float);
-    method public void setIcon(int, String, android.graphics.drawable.Icon);
-    method public void setImageViewBitmap(int, android.graphics.Bitmap);
-    method public void setImageViewIcon(int, android.graphics.drawable.Icon);
-    method public void setImageViewResource(int, int);
-    method public void setImageViewUri(int, android.net.Uri);
-    method public void setInt(int, String, int);
-    method public void setIntent(int, String, android.content.Intent);
-    method public void setLabelFor(int, int);
+    method public void removeAllViews(@IdRes int);
+    method public void setAccessibilityTraversalAfter(@IdRes int, @IdRes int);
+    method public void setAccessibilityTraversalBefore(@IdRes int, @IdRes int);
+    method public void setBitmap(@IdRes int, String, android.graphics.Bitmap);
+    method public void setBoolean(@IdRes int, String, boolean);
+    method public void setBundle(@IdRes int, String, android.os.Bundle);
+    method public void setByte(@IdRes int, String, byte);
+    method public void setChar(@IdRes int, String, char);
+    method public void setCharSequence(@IdRes int, String, CharSequence);
+    method public void setChronometer(@IdRes int, long, String, boolean);
+    method public void setChronometerCountDown(@IdRes int, boolean);
+    method public void setContentDescription(@IdRes int, CharSequence);
+    method public void setDisplayedChild(@IdRes int, int);
+    method public void setDouble(@IdRes int, String, double);
+    method public void setEmptyView(@IdRes int, @IdRes int);
+    method public void setFloat(@IdRes int, String, float);
+    method public void setIcon(@IdRes int, String, android.graphics.drawable.Icon);
+    method public void setImageViewBitmap(@IdRes int, android.graphics.Bitmap);
+    method public void setImageViewIcon(@IdRes int, android.graphics.drawable.Icon);
+    method public void setImageViewResource(@IdRes int, @DrawableRes int);
+    method public void setImageViewUri(@IdRes int, android.net.Uri);
+    method public void setInt(@IdRes int, String, int);
+    method public void setIntent(@IdRes int, String, android.content.Intent);
+    method public void setLabelFor(@IdRes int, @IdRes int);
     method public void setLightBackgroundLayoutId(@LayoutRes int);
-    method public void setLong(int, String, long);
-    method public void setOnClickFillInIntent(int, android.content.Intent);
-    method public void setOnClickPendingIntent(int, android.app.PendingIntent);
-    method public void setOnClickResponse(int, @NonNull android.widget.RemoteViews.RemoteResponse);
-    method public void setPendingIntentTemplate(int, android.app.PendingIntent);
-    method public void setProgressBar(int, int, int, boolean);
-    method public void setRelativeScrollPosition(int, int);
-    method @Deprecated public void setRemoteAdapter(int, int, android.content.Intent);
-    method public void setRemoteAdapter(int, android.content.Intent);
-    method public void setScrollPosition(int, int);
-    method public void setShort(int, String, short);
-    method public void setString(int, String, String);
-    method public void setTextColor(int, @ColorInt int);
-    method public void setTextViewCompoundDrawables(int, int, int, int, int);
-    method public void setTextViewCompoundDrawablesRelative(int, int, int, int, int);
-    method public void setTextViewText(int, CharSequence);
-    method public void setTextViewTextSize(int, int, float);
-    method public void setUri(int, String, android.net.Uri);
-    method public void setViewPadding(int, int, int, int, int);
-    method public void setViewVisibility(int, int);
-    method public void showNext(int);
-    method public void showPrevious(int);
+    method public void setLong(@IdRes int, String, long);
+    method public void setOnClickFillInIntent(@IdRes int, android.content.Intent);
+    method public void setOnClickPendingIntent(@IdRes int, android.app.PendingIntent);
+    method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse);
+    method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent);
+    method public void setProgressBar(@IdRes int, int, int, boolean);
+    method public void setRelativeScrollPosition(@IdRes int, int);
+    method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
+    method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+    method public void setScrollPosition(@IdRes int, int);
+    method public void setShort(@IdRes int, String, short);
+    method public void setString(@IdRes int, String, String);
+    method public void setTextColor(@IdRes int, @ColorInt int);
+    method public void setTextViewCompoundDrawables(@IdRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int);
+    method public void setTextViewCompoundDrawablesRelative(@IdRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int);
+    method public void setTextViewText(@IdRes int, CharSequence);
+    method public void setTextViewTextSize(@IdRes int, int, float);
+    method public void setUri(@IdRes int, String, android.net.Uri);
+    method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
+    method public void setViewVisibility(@IdRes int, int);
+    method public void showNext(@IdRes int);
+    method public void showPrevious(@IdRes int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR;
     field public static final String EXTRA_SHARED_ELEMENT_BOUNDS = "android.widget.extra.SHARED_ELEMENT_BOUNDS";
@@ -54113,7 +54137,7 @@
 
   public static class RemoteViews.RemoteResponse {
     ctor public RemoteViews.RemoteResponse();
-    method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(int, @NonNull String);
+    method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String);
     method @NonNull public static android.widget.RemoteViews.RemoteResponse fromFillInIntent(@NonNull android.content.Intent);
     method @NonNull public static android.widget.RemoteViews.RemoteResponse fromPendingIntent(@NonNull android.app.PendingIntent);
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index f22b0f4..ef6c8b7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -109,8 +109,11 @@
 
   public final class MediaSessionManager {
     method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, int, @Nullable android.os.Handler);
+    method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent);
+    method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean);
     method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
     method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
+    method public void dispatchVolumeKeyEvent(@NonNull android.view.KeyEvent, int, boolean);
     method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
     method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
     method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessionsForUser(@Nullable android.content.ComponentName, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 58965ef..9a15e77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -661,6 +661,10 @@
     field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400
   }
 
+  public static class Notification.Action implements android.os.Parcelable {
+    field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
+  }
+
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
     ctor public Notification.TvExtender();
     ctor public Notification.TvExtender(android.app.Notification);
@@ -1366,6 +1370,8 @@
     method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
@@ -1801,6 +1807,7 @@
     field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET;
     field @NonNull public static final android.os.ParcelUuid BASE_UUID;
     field @NonNull public static final android.os.ParcelUuid BNEP;
+    field @NonNull public static final android.os.ParcelUuid DIP;
     field @NonNull public static final android.os.ParcelUuid HEARING_AID;
     field @NonNull public static final android.os.ParcelUuid HFP;
     field @NonNull public static final android.os.ParcelUuid HFP_AG;
@@ -4375,6 +4382,56 @@
 
 }
 
+package android.location.provider {
+
+  public abstract class LocationProviderBase {
+    ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties);
+    method @Nullable public final android.os.IBinder getBinder();
+    method @NonNull public android.location.provider.ProviderProperties getProperties();
+    method public boolean isAllowed();
+    method public abstract void onFlush(@NonNull android.location.provider.LocationProviderBase.OnFlushCompleteCallback);
+    method public abstract void onSendExtraCommand(@NonNull String, @Nullable android.os.Bundle);
+    method public abstract void onSetRequest(@NonNull android.location.provider.ProviderRequest);
+    method public void reportLocation(@NonNull android.location.Location);
+    method public void reportLocation(@NonNull android.location.LocationResult);
+    method public void setAllowed(boolean);
+    method public void setProperties(@NonNull android.location.provider.ProviderProperties);
+    field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider";
+    field public static final String ACTION_NETWORK_PROVIDER = "com.android.location.service.v3.NetworkLocationProvider";
+  }
+
+  public static interface LocationProviderBase.OnFlushCompleteCallback {
+    method public void onFlushComplete();
+  }
+
+  public final class ProviderRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0) public long getIntervalMillis();
+    method @IntRange(from=0) public long getMaxUpdateDelayMillis();
+    method public int getQuality();
+    method @NonNull public android.os.WorkSource getWorkSource();
+    method public boolean isActive();
+    method public boolean isLocationSettingsIgnored();
+    method public boolean isLowPower();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ProviderRequest> CREATOR;
+    field @NonNull public static final android.location.provider.ProviderRequest EMPTY_REQUEST;
+    field public static final long INTERVAL_DISABLED = 9223372036854775807L; // 0x7fffffffffffffffL
+  }
+
+  public static final class ProviderRequest.Builder {
+    ctor public ProviderRequest.Builder();
+    method @NonNull public android.location.provider.ProviderRequest build();
+    method @NonNull public android.location.provider.ProviderRequest.Builder setIntervalMillis(@IntRange(from=0) long);
+    method @NonNull public android.location.provider.ProviderRequest.Builder setLocationSettingsIgnored(boolean);
+    method @NonNull public android.location.provider.ProviderRequest.Builder setLowPower(boolean);
+    method @NonNull public android.location.provider.ProviderRequest.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long);
+    method @NonNull public android.location.provider.ProviderRequest.Builder setQuality(int);
+    method @NonNull public android.location.provider.ProviderRequest.Builder setWorkSource(@NonNull android.os.WorkSource);
+  }
+
+}
+
 package android.media {
 
   public final class AudioAttributes implements android.os.Parcelable {
@@ -6847,7 +6904,6 @@
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
-    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1086577..91212be 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -278,6 +278,10 @@
     method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
   }
 
+  public final class PendingIntent implements android.os.Parcelable {
+    field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
+  }
+
   public final class PictureInPictureParams implements android.os.Parcelable {
     method public java.util.List<android.app.RemoteAction> getActions();
     method public float getAspectRatio();
@@ -292,7 +296,9 @@
   }
 
   public class TaskInfo {
+    method public boolean containsLaunchCookie(@NonNull android.os.IBinder);
     method @NonNull public android.content.res.Configuration getConfiguration();
+    method public int getParentTaskId();
     method @Nullable public android.app.PictureInPictureParams getPictureInPictureParams();
     method @NonNull public android.window.WindowContainerToken getToken();
     method public boolean hasParentTask();
@@ -368,6 +374,7 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method public void forceUpdateUserSetupComplete();
     method public long getLastBugReportRequestTime();
     method public long getLastNetworkLogRetrievalTime();
     method public long getLastSecurityLogRetrievalTime();
@@ -441,11 +448,6 @@
 
 package android.app.role {
 
-  public class RoleControllerManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-  }
-
   public final class RoleManager {
     method @Nullable public String getSmsRoleHolder(int);
   }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8242e4d..e56274a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -30,7 +30,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.Px;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -83,6 +82,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
@@ -218,6 +218,11 @@
     private static final int MAX_REPLY_HISTORY = 5;
 
     /**
+     * Maximum aspect ratio of the large icon. 16:9
+     */
+    private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
+
+    /**
      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
      * handled separately).
      * @hide
@@ -641,7 +646,8 @@
     public static final int FLAG_IMMEDIATE_FGS_DISPLAY = 0x00002000;
 
     /** @hide */
-    @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
+            FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE,
             FLAG_IMMEDIATE_FGS_DISPLAY})
@@ -1523,6 +1529,14 @@
          */
         public static final int SEMANTIC_ACTION_CALL = 10;
 
+        /**
+         * {@code SemanticAction}: Mark the conversation associated with the notification as a
+         * priority. Note that this is only for use by the notification assistant services.
+         * @hide
+         */
+        @SystemApi
+        public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
+
         private final Bundle mExtras;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         private Icon mIcon;
@@ -2233,7 +2247,8 @@
                 SEMANTIC_ACTION_UNMUTE,
                 SEMANTIC_ACTION_THUMBS_UP,
                 SEMANTIC_ACTION_THUMBS_DOWN,
-                SEMANTIC_ACTION_CALL
+                SEMANTIC_ACTION_CALL,
+                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface SemanticAction {}
@@ -4228,9 +4243,9 @@
         /**
          * Add a large icon to the notification content view.
          *
-         * In the platform template, this image will be shown on the left of the notification view
-         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
-         * badge atop the large icon).
+         * In the platform template, this image will be shown either on the right of the
+         * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
+         * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
          */
         @NonNull
         public Builder setLargeIcon(Bitmap b) {
@@ -4240,9 +4255,9 @@
         /**
          * Add a large icon to the notification content view.
          *
-         * In the platform template, this image will be shown on the left of the notification view
-         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
-         * badge atop the large icon).
+         * In the platform template, this image will be shown either on the right of the
+         * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
+         * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
          */
         @NonNull
         public Builder setLargeIcon(Icon icon) {
@@ -4911,7 +4926,8 @@
                 setTextViewColorPrimary(contentView, R.id.title, p);
                 contentView.setViewLayoutWidth(R.id.title, showProgress
                         ? ViewGroup.LayoutParams.WRAP_CONTENT
-                        : ViewGroup.LayoutParams.MATCH_PARENT);
+                        : ViewGroup.LayoutParams.MATCH_PARENT,
+                        TypedValue.COMPLEX_UNIT_PX);
             }
             if (p.text != null && p.text.length() != 0) {
                 int textId = showProgress ? com.android.internal.R.id.text_line_1
@@ -5109,8 +5125,7 @@
             if (result == null) {
                 result = new TemplateBindResult();
             }
-            final boolean largeIconShown = bindLargeIcon(contentView, p);
-            calculateLargeIconMarginEnd(largeIconShown, result);
+            bindLargeIcon(contentView, p, result);
             if (p.mHeaderless) {
                 // views in the headerless (collapsed) state
                 result.mHeadingExtraMarginSet.applyToView(contentView,
@@ -5122,28 +5137,54 @@
             }
         }
 
-        private void calculateLargeIconMarginEnd(boolean largeIconShown,
+        // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
+        // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
+        // the change's state in NotificationManagerService were very complex. These behavior
+        // changes are entirely visual, and should otherwise be undetectable by apps.
+        @SuppressWarnings("AndroidFrameworkCompatChange")
+        private void calculateLargeIconDimens(boolean largeIconShown,
                 @NonNull TemplateBindResult result) {
             final Resources resources = mContext.getResources();
-            final int contentMargin = resources.getDimensionPixelOffset(
-                    R.dimen.notification_content_margin_end);
-            final int expanderSize = resources.getDimensionPixelSize(
-                    R.dimen.notification_header_expand_icon_size) - contentMargin;
-            final int extraMarginEndIfVisible = resources.getDimensionPixelSize(
-                    R.dimen.notification_right_icon_size) + contentMargin;
-            result.setRightIconState(largeIconShown, extraMarginEndIfVisible, expanderSize);
+            final float density = resources.getDisplayMetrics().density;
+            final float contentMarginDp = resources.getDimension(
+                    R.dimen.notification_content_margin_end) / density;
+            final float expanderSizeDp = resources.getDimension(
+                    R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
+            final float viewHeightDp = resources.getDimension(
+                    R.dimen.notification_right_icon_size) / density;
+            float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
+            if (largeIconShown && (
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
+                            || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
+                Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
+                if (drawable != null) {
+                    int iconWidth = drawable.getIntrinsicWidth();
+                    int iconHeight = drawable.getIntrinsicHeight();
+                    if (iconWidth > iconHeight && iconHeight > 0) {
+                        final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
+                        viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
+                                maxViewWidthDp);
+                    }
+                }
+            }
+            final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
+            result.setRightIconState(largeIconShown, viewWidthDp,
+                    extraMarginEndDpIfVisible, expanderSizeDp);
         }
 
         /**
          * Bind the large icon.
-         * @return if the largeIcon is visible
          */
-        private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
+        private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
+                @NonNull TemplateBindResult result) {
             if (mN.mLargeIcon == null && mN.largeIcon != null) {
                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
             }
             boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
+            calculateLargeIconDimens(showLargeIcon, result);
             if (showLargeIcon) {
+                contentView.setViewLayoutWidth(R.id.right_icon,
+                        result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                 processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
@@ -5153,7 +5194,6 @@
                 // visibility) is used by NotificationGroupingUtil to set the visibility.
                 contentView.setImageViewIcon(R.id.right_icon, null);
             }
-            return showLargeIcon;
         }
 
         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
@@ -5356,8 +5396,9 @@
             final boolean snoozeEnabled = mContext.getContentResolver() != null
                     && (Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1);
-            big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
-                    snoozeEnabled ? 0 : R.dimen.notification_content_margin);
+            int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin;
+            big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+                    RemoteViews.MARGIN_BOTTOM, bottomMarginDimen);
         }
 
         private static List<Notification.Action> filterOutContextualActions(
@@ -5389,7 +5430,8 @@
             if (N > 0) {
                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
-                big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
+                big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+                        RemoteViews.MARGIN_BOTTOM, 0);
                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
                 for (int i=0; i<N; i++) {
                     Action action = nonContextualActions.get(i);
@@ -7788,8 +7830,9 @@
                 // also update the end margin if there is an image
                 // NOTE: This template doesn't support moving this icon to the left, so we don't
                 // need to fully apply the MarginSet
-                contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
-                        bindResult.mHeadingExtraMarginSet.getValue());
+                contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END,
+                        bindResult.mHeadingExtraMarginSet.getDpValue(),
+                        TypedValue.COMPLEX_UNIT_DIP);
             }
             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                     mBuilder.isColorized(p)
@@ -8613,7 +8656,8 @@
             if (mBuilder.mN.hasLargeIcon()) {
                 endMargin = R.dimen.notification_media_image_margin_end;
             }
-            view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
+            view.setViewLayoutMarginDimen(R.id.notification_main_column,
+                            RemoteViews.MARGIN_END, endMargin);
             return view;
         }
 
@@ -8650,8 +8694,8 @@
 
         private void handleImage(RemoteViews contentView) {
             if (mBuilder.mN.hasLargeIcon()) {
-                contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
-                contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
+                contentView.setViewLayoutMarginDimen(R.id.line1, RemoteViews.MARGIN_END, 0);
+                contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0);
             }
         }
 
@@ -8787,7 +8831,8 @@
                 // also update the end margin to account for the large icon or expander
                 Resources resources = mBuilder.mContext.getResources();
                 result.mTitleMarginSet.applyToView(remoteViews, R.id.notification_main_column,
-                        resources.getDimensionPixelOffset(R.dimen.notification_content_margin_end));
+                        resources.getDimension(R.dimen.notification_content_margin_end)
+                                / resources.getDisplayMetrics().density);
             }
         }
 
@@ -11025,6 +11070,7 @@
      */
     private static class TemplateBindResult {
         boolean mRightIconVisible;
+        float mRightIconWidthDp;
 
         /**
          * The margin end that needs to be added to the heading so that it won't overlap
@@ -11049,11 +11095,13 @@
          */
         public final MarginSet mTitleMarginSet = new MarginSet();
 
-        public void setRightIconState(boolean visible, int marginEndIfVisible, int expanderSize) {
+        public void setRightIconState(boolean visible, float widthDp,
+                float marginEndDpIfVisible, float expanderSizeDp) {
             mRightIconVisible = visible;
-            mHeadingExtraMarginSet.setValues(0, marginEndIfVisible);
-            mHeadingFullMarginSet.setValues(expanderSize, marginEndIfVisible + expanderSize);
-            mTitleMarginSet.setValues(0, marginEndIfVisible + expanderSize);
+            mRightIconWidthDp = widthDp;
+            mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
+            mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
+            mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
         }
 
         /**
@@ -11062,10 +11110,10 @@
          * left_icon and adjust the margins, and to undo that change as well.
          */
         private class MarginSet {
-            private int mValueIfGone;
-            private int mValueIfVisible;
+            private float mValueIfGone;
+            private float mValueIfVisible;
 
-            public void setValues(int valueIfGone, int valueIfVisible) {
+            public void setValues(float valueIfGone, float valueIfVisible) {
                 mValueIfGone = valueIfGone;
                 mValueIfVisible = valueIfVisible;
             }
@@ -11075,22 +11123,26 @@
             }
 
             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
-                    @Px int extraMargin) {
-                final int marginEnd = getValue() + extraMargin;
+                    float extraMarginDp) {
+                final float marginEndDp = getDpValue() + extraMarginDp;
                 if (viewId == R.id.notification_header) {
-                    views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd);
+                    views.setFloat(R.id.notification_header,
+                            "setTopLineExtraMarginEndDp", marginEndDp);
                 } else {
-                    views.setViewLayoutMarginEnd(viewId, marginEnd);
+                    views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
+                                    marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
                 }
                 if (mRightIconVisible) {
                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
-                            mValueIfVisible + extraMargin);
+                            TypedValue.createComplexDimension(
+                                    mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
-                            mValueIfGone + extraMargin);
+                            TypedValue.createComplexDimension(
+                                    mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
                 }
             }
 
-            public int getValue() {
+            public float getDpValue() {
                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
             }
         }
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 9dbf1ff6..602e9a3 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -221,6 +222,7 @@
      * @hide
      */
     @Deprecated
+    @TestApi
     public static final int FLAG_MUTABLE_UNAUDITED = FLAG_MUTABLE;
 
     /**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ae1c894..2ce0e87 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -30,7 +30,6 @@
 import android.app.contentsuggestions.IContentSuggestionsManager;
 import android.app.job.JobSchedulerFrameworkInitializer;
 import android.app.prediction.AppPredictionManager;
-import android.app.role.RoleControllerManager;
 import android.app.role.RoleManager;
 import android.app.search.SearchUiManager;
 import android.app.slice.SliceManager;
@@ -1291,14 +1290,6 @@
                         return new RoleManager(ctx.getOuterContext());
                     }});
 
-        registerService(Context.ROLE_CONTROLLER_SERVICE, RoleControllerManager.class,
-                new CachedServiceFetcher<RoleControllerManager>() {
-                    @Override
-                    public RoleControllerManager createService(ContextImpl ctx)
-                            throws ServiceNotFoundException {
-                        return new RoleControllerManager(ctx.getOuterContext());
-                    }});
-
         registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
                 new CachedServiceFetcher<DynamicSystemManager>() {
                     @Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 61c4d39..623c878d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -36,6 +36,7 @@
 import android.window.WindowContainerToken;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -279,6 +280,24 @@
         launchCookies.add(cookie);
     }
 
+    /**
+     * @return {@code true} if this task contains the launch cookie.
+     * @hide
+     */
+    @TestApi
+    public boolean containsLaunchCookie(@NonNull IBinder cookie) {
+        return launchCookies.contains(cookie);
+    }
+
+    /**
+     * @return The parent task id of this task.
+     * @hide
+     */
+    @TestApi
+    public int getParentTaskId() {
+        return parentTaskId;
+    }
+
     /** @hide */
     @TestApi
     public boolean hasParentTask() {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 787393e..05872ba 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -438,8 +438,8 @@
      * Adopt the permission identity of the shell UID for all permissions. This allows
      * you to call APIs protected permissions which normal apps cannot hold but are
      * granted to the shell UID. If you already adopted all shell permissions by calling
-     * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call
-     * would be a no-op. Note that your permission state becomes that of the shell UID
+     * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call will
+     * replace any previous adoption. Note that your permission state becomes that of the shell UID
      * and it is not a combination of your and the shell UID permissions.
      * <p>
      * <strong>Note:<strong/> Calling this method adopts all shell permissions and overrides
@@ -460,13 +460,13 @@
     /**
      * Adopt the permission identity of the shell UID only for the provided permissions.
      * This allows you to call APIs protected permissions which normal apps cannot hold
-     * but are granted to the shell UID. If you already adopted the specified shell
-     * permissions by calling this method or {@link #adoptShellPermissionIdentity()} a
-     * subsequent call would be a no-op. Note that your permission state becomes that of the
-     * shell UID and it is not a combination of your and the shell UID permissions.
+     * but are granted to the shell UID. If you already adopted shell permissions by calling
+     * this method, or {@link #adoptShellPermissionIdentity()} a subsequent call will replace any
+     * previous adoption.
      * <p>
-     * <strong>Note:<strong/> Calling this method adopts only the specified shell permissions
-     * and overrides all adopted permissions via {@link #adoptShellPermissionIdentity()}.
+     * <strong>Note:<strong/> This method behave differently from
+     * {@link #adoptShellPermissionIdentity()}. Only the listed permissions will use the shell
+     * identity and other permissions will still check against the original UID
      *
      * @param permissions The permissions to adopt or <code>null</code> to adopt all.
      *
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 259c1a1..806cb49 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11241,6 +11241,7 @@
      *         {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is
      *         not {@link UserHandle#SYSTEM_USER}
      */
+    @TestApi
     public void forceUpdateUserSetupComplete() {
         try {
             mService.forceUpdateUserSetupComplete();
diff --git a/core/java/android/app/role/RoleControllerManager.java b/core/java/android/app/role/RoleControllerManager.java
index 8dde2c5..ba1f612 100644
--- a/core/java/android/app/role/RoleControllerManager.java
+++ b/core/java/android/app/role/RoleControllerManager.java
@@ -20,8 +20,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,8 +46,6 @@
  *
  * @hide
  */
-@SystemService(Context.ROLE_CONTROLLER_SERVICE)
-@TestApi
 public class RoleControllerManager {
 
     private static final String LOG_TAG = RoleControllerManager.class.getSimpleName();
@@ -199,32 +195,11 @@
     }
 
     /**
-     * @see RoleControllerService#onIsApplicationQualifiedForRole(String, String)
-     *
-     * @deprecated Use {@link #isApplicationVisibleForRole(String, String, Executor, Consumer)}
-     *             instead.
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.isApplicationQualifiedForRole(roleName, packageName,
-                    new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "isApplicationQualifiedForRole", executor, callback);
-    }
-
-    /**
      * @see RoleControllerService#onIsApplicationVisibleForRole(String, String)
      *
      * @hide
      */
     @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @TestApi
     public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
             @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
         AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
@@ -242,7 +217,6 @@
      * @hide
      */
     @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @TestApi
     public void isRoleVisible(@NonNull String roleName,
             @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
         AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 8b2e07b..9ddd5be 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -174,6 +174,9 @@
     @NonNull
     private final Object mListenersLock = new Object();
 
+    @NonNull
+    private final RoleControllerManager mRoleControllerManager;
+
     /**
      * @hide
      */
@@ -181,6 +184,7 @@
         mContext = context;
         mService = IRoleManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
                 Context.ROLE_SERVICE));
+        mRoleControllerManager = new RoleControllerManager(context);
     }
 
     /**
@@ -676,6 +680,44 @@
         }
     }
 
+    /**
+     * Check whether a role should be visible to user.
+     *
+     * @param roleName name of the role to check for
+     * @param executor the executor to execute callback on
+     * @param callback the callback to receive whether the role should be visible to user
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public void isRoleVisible(@NonNull String roleName,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        mRoleControllerManager.isRoleVisible(roleName, executor, callback);
+    }
+
+    /**
+     * Check whether an application is visible for a role.
+     *
+     * While an application can be qualified for a role, it can still stay hidden from user (thus
+     * not visible). If an application is visible for a role, we may show things related to the role
+     * for it, e.g. showing an entry pointing to the role settings in its application info page.
+     *
+     * @param roleName the name of the role to check for
+     * @param packageName the package name of the application to check for
+     * @param executor the executor to execute callback on
+     * @param callback the callback to receive whether the application is visible for the role
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        mRoleControllerManager.isApplicationVisibleForRole(roleName, packageName, executor,
+                callback);
+    }
+
     private static class OnRoleHoldersChangedListenerDelegate
             extends IOnRoleHoldersChangedListener.Stub {
 
diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java
index d909c0c..71a800f 100644
--- a/core/java/android/app/time/LocationTimeZoneManager.java
+++ b/core/java/android/app/time/LocationTimeZoneManager.java
@@ -40,17 +40,58 @@
     public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";
 
     /**
-     * Shell command that starts the service (after stop).
+     * A shell command that starts the service (after stop).
      */
     public static final String SHELL_COMMAND_START = "start";
 
     /**
-     * Shell command that stops the service.
+     * A shell command that stops the service.
      */
     public static final String SHELL_COMMAND_STOP = "stop";
 
     /**
-     * Shell command that sends test commands to a provider
+     * A shell command that can put providers into different modes. Takes effect next time the
+     * service is started.
+     */
+    public static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE =
+            "set_provider_mode_override";
+
+    /**
+     * The default provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    public static final String PROVIDER_MODE_OVERRIDE_NONE = "none";
+
+    /**
+     * The "simulated" provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    public static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";
+
+    /**
+     * The "disabled" provider mode (equivalent to there being no provider configured).
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    public static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";
+
+    /**
+     * A shell command that tells the service to record state information during tests. The next
+     * argument value is "true" or "false".
+     */
+    public static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+
+    /**
+     * A shell command that tells the service to dump its current state.
+     */
+    public static final String SHELL_COMMAND_DUMP_STATE = "dump_state";
+
+    /**
+     * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
+     */
+    public static final String DUMP_STATE_OPTION_PROTO = "--proto";
+
+    /**
+     * A shell command that sends test commands to a provider
      */
     public static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
             "send_provider_test_command";
@@ -88,35 +129,6 @@
      */
     public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";
 
-    private static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX =
-            "persist.sys.geotz.";
-
-    /**
-     * The name of the system property that can be used to set the primary provider into test mode
-     * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
-     * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
-     */
-    public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY =
-            SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + PRIMARY_PROVIDER_NAME;
-
-    /**
-     * The name of the system property that can be used to set the secondary provider into test mode
-     * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
-     * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
-     */
-    public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY =
-            SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + SECONDARY_PROVIDER_NAME;
-
-    /**
-     * The value of the provider mode system property to put a provider into test mode.
-     */
-    public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED = "simulated";
-
-    /**
-     * The value of the provider mode system property to put a provider into disabled mode.
-     */
-    public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED = "disabled";
-
     private LocationTimeZoneManager() {
         // No need to instantiate.
     }
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index e0b7ffe..ee718b35 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -29,11 +29,69 @@
 @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
 public interface TimeZoneDetector {
 
-    /** @hide */
+    /**
+     * The name of the service for shell commands.
+     * @hide
+     */
+    String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";
+
+    /**
+     * A shell command that prints the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
+
+    /**
+     * A shell command that sets the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";
+
+    /**
+     * A shell command that prints whether the geolocation-based time zone detection feature is
+     * supported on the device.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";
+
+    /**
+     * A shell command that prints the current user's "location enabled" setting.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_LOCATION_ENABLED = "is_location_enabled";
+
+    /**
+     * A shell command that prints the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";
+
+    /**
+     * A shell command that sets the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
+
+    /**
+     * A shell command that injects a geolocation time zone suggestion (as if from the
+     * location_time_zone_manager).
+     * @hide
+     */
     String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
-    /** @hide */
+
+    /**
+     * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
+     * similar).
+     * @hide
+     */
     String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
-    /** @hide */
+
+    /**
+     * A shell command that injects a telephony time zone suggestion (as if from the phone app).
+     * @hide
+     */
     String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
 
     /**
diff --git a/core/java/android/apphibernation/OWNERS b/core/java/android/apphibernation/OWNERS
new file mode 100644
index 0000000..587c719
--- /dev/null
+++ b/core/java/android/apphibernation/OWNERS
@@ -0,0 +1,2 @@
+kevhan@google.com
+rajekumar@google.com
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 56c4824..c0736a6 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -162,6 +162,11 @@
     /** @hide */
     @NonNull
     @SystemApi
+    public static final ParcelUuid DIP =
+            ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
     public static final ParcelUuid BASE_UUID =
             ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
 
diff --git a/core/java/android/bluetooth/SdpDipRecord.java b/core/java/android/bluetooth/SdpDipRecord.java
new file mode 100644
index 0000000..84b0eef
--- /dev/null
+++ b/core/java/android/bluetooth/SdpDipRecord.java
@@ -0,0 +1,104 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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.bluetooth;
+
+import java.util.Arrays;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data representation of a Object Push Profile Server side SDP record.
+ */
+/** @hide */
+public class SdpDipRecord implements Parcelable {
+    private final int mSpecificationId;
+    private final int mVendorId;
+    private final int mVendorIdSource;
+    private final int mProductId;
+    private final int mVersion;
+    private final boolean mPrimaryRecord;
+
+    public SdpDipRecord(int specificationId,
+            int vendorId, int vendorIdSource,
+            int productId, int version,
+            boolean primaryRecord) {
+        super();
+        this.mSpecificationId = specificationId;
+        this.mVendorId = vendorId;
+        this.mVendorIdSource = vendorIdSource;
+        this.mProductId = productId;
+        this.mVersion = version;
+        this.mPrimaryRecord = primaryRecord;
+    }
+
+    public SdpDipRecord(Parcel in) {
+        this.mSpecificationId = in.readInt();
+        this.mVendorId = in.readInt();
+        this.mVendorIdSource = in.readInt();
+        this.mProductId = in.readInt();
+        this.mVersion = in.readInt();
+        this.mPrimaryRecord = in.readBoolean();
+    }
+
+    public int getSpecificationId() {
+        return mSpecificationId;
+    }
+
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    public int getVendorIdSource() {
+        return mVendorIdSource;
+    }
+
+    public int getProductId() {
+        return mProductId;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public boolean getPrimaryRecord() {
+        return mPrimaryRecord;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSpecificationId);
+        dest.writeInt(mVendorId);
+        dest.writeInt(mVendorIdSource);
+        dest.writeInt(mProductId);
+        dest.writeInt(mVersion);
+        dest.writeBoolean(mPrimaryRecord);
+    }
+
+    @Override
+    public int describeContents() {
+        /* No special objects */
+        return 0;
+    }
+
+    public static  final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpDipRecord createFromParcel(Parcel in) {
+            return new SdpDipRecord(in);
+        }
+        public SdpDipRecord[] newArray(int size) {
+            return new SdpDipRecord[size];
+        }
+    };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 29ffa0b..33f9607 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4821,16 +4821,6 @@
     public static final String ROLE_SERVICE = "role";
 
     /**
-     * Official published name of the (internal) role controller service.
-     *
-     * @see #getSystemService(String)
-     * @see android.app.role.RoleControllerService
-     *
-     * @hide
-     */
-    public static final String ROLE_CONTROLLER_SERVICE = "role_controller";
-
-    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.hardware.camera2.CameraManager} for interacting with
      * camera devices.
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 4e368d0..a54e88f 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -26,18 +26,19 @@
     // =============== Beginning of transactions used on native side as well ======================
     void addSensorPrivacyListener(in ISensorPrivacyListener listener);
 
+    void addIndividualSensorPrivacyListener(int userId, int sensor,
+            in ISensorPrivacyListener listener);
+
     void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
 
     boolean isSensorPrivacyEnabled();
 
+    boolean isIndividualSensorPrivacyEnabled(int userId, int sensor);
+
     void setSensorPrivacy(boolean enable);
+
+    void setIndividualSensorPrivacy(int userId, int sensor, boolean enable);
+
+    void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable);
     // =============== End of transactions used on native side as well ============================
-
-    // TODO(evanseverson) add to native interface
-    boolean isIndividualSensorPrivacyEnabled(int sensor);
-
-    // TODO(evanseverson) add to native interface
-    void setIndividualSensorPrivacy(int sensor, boolean enable);
-
-    // TODO(evanseverson) listeners
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index c647239..b3b2dd8 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -160,6 +161,37 @@
     }
 
     /**
+     * Registers a new listener to receive notification when the state of sensor privacy
+     * changes.
+     *
+     * @param userId the user's id
+     * @param sensor the sensor to listen to changes to
+     * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
+     *                 privacy changes.
+     */
+    public void addSensorPrivacyListener(@UserIdInt int userId, @IndividualSensor int sensor,
+            final OnSensorPrivacyChangedListener listener) {
+        synchronized (mListeners) {
+            ISensorPrivacyListener iListener = mListeners.get(listener);
+            if (iListener == null) {
+                iListener = new ISensorPrivacyListener.Stub() {
+                    @Override
+                    public void onSensorPrivacyChanged(boolean enabled) {
+                        listener.onSensorPrivacyChanged(enabled);
+                    }
+                };
+                mListeners.put(listener, iListener);
+            }
+
+            try {
+                mService.addIndividualSensorPrivacyListener(userId, sensor, iListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Unregisters the specified listener from receiving notifications when the state of sensor
      * privacy changes.
      *
@@ -198,9 +230,10 @@
      *
      * @return true if sensor privacy is currently enabled, false otherwise.
      */
-    public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) {
+    public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId,
+            @IndividualSensor int sensor) {
         try {
-            return mService.isIndividualSensorPrivacyEnabled(sensor);
+            return mService.isIndividualSensorPrivacyEnabled(userId, sensor);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -212,9 +245,26 @@
      * @param enable the state to which sensor privacy should be set.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)
-    public void setIndividualSensorPrivacy(@IndividualSensor int sensor, boolean enable) {
+    public void setIndividualSensorPrivacy(@UserIdInt int userId, @IndividualSensor int sensor,
+            boolean enable) {
         try {
-            mService.setIndividualSensorPrivacy(sensor, enable);
+            mService.setIndividualSensorPrivacy(userId, sensor, enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets sensor privacy to the specified state for an individual sensor for the profile group of
+     * the given user.
+     *
+     * @param enable the state to which sensor privacy should be set.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId,
+            @IndividualSensor int sensor, boolean enable) {
+        try {
+            mService.setIndividualSensorPrivacyForProfileGroup(userId, sensor, enable);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index a4e5738..582570e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -568,11 +568,13 @@
      * @param cancel an object that can be used to cancel enrollment
      * @param userId the user to whom this fingerprint will belong to
      * @param callback an object to receive enrollment events
+     * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
+     * should be logged.
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId,
-            EnrollmentCallback callback) {
+            EnrollmentCallback callback, boolean shouldLogMetrics) {
         if (userId == UserHandle.USER_CURRENT) {
             userId = getCurrentUserId();
         }
@@ -593,7 +595,7 @@
             try {
                 mEnrollmentCallback = callback;
                 mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), shouldLogMetrics);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 64abbea..ac026c7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -77,7 +77,7 @@
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
-            String opPackageName);
+            String opPackageName, boolean shouldLogMetrics);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
index c29d23d..fe1268d 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -272,6 +272,7 @@
          * Sets the subscriber ID for this network.
          *
          * @return this builder, to facilitate chaining.
+         * @hide
          */
         @NonNull
         public Builder setSubscriberId(@Nullable String subscriberId) {
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 9dd0114..04b585c 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -23,6 +23,6 @@
  * @hide
  */
 interface IVcnManagementService {
-    void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config);
+    void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
     void clearVcnConfig(in ParcelUuid subscriptionGroup);
 }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index d4a3fa7..ede8faa 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -45,11 +46,17 @@
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
 
+    private static final String PACKAGE_NAME_KEY = "mPackageName";
+    @NonNull private final String mPackageName;
+
     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
 
-    private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) {
-        mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs);
+    private VcnConfig(
+            @NonNull String packageName,
+            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
+        mPackageName = packageName;
+        mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
 
         validate();
     }
@@ -61,6 +68,8 @@
      */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public VcnConfig(@NonNull PersistableBundle in) {
+        mPackageName = in.getString(PACKAGE_NAME_KEY);
+
         final PersistableBundle gatewayConnectionConfigsBundle =
                 in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
         mGatewayConnectionConfigs =
@@ -72,8 +81,19 @@
     }
 
     private void validate() {
+        Objects.requireNonNull(mPackageName, "packageName was null");
         Preconditions.checkCollectionNotEmpty(
-                mGatewayConnectionConfigs, "gatewayConnectionConfigs");
+                mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+    }
+
+    /**
+     * Retrieve the package name of the provisioning app.
+     *
+     * @hide
+     */
+    @NonNull
+    public String getProvisioningPackageName() {
+        return mPackageName;
     }
 
     /** Retrieves the set of configured tunnels. */
@@ -91,6 +111,8 @@
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = new PersistableBundle();
 
+        result.putString(PACKAGE_NAME_KEY, mPackageName);
+
         final PersistableBundle gatewayConnectionConfigsBundle =
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mGatewayConnectionConfigs),
@@ -102,7 +124,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mGatewayConnectionConfigs);
+        return Objects.hash(mPackageName, mGatewayConnectionConfigs);
     }
 
     @Override
@@ -112,7 +134,8 @@
         }
 
         final VcnConfig rhs = (VcnConfig) other;
-        return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
+        return mPackageName.equals(rhs.mPackageName)
+                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
     }
 
     // Parcelable methods
@@ -143,9 +166,17 @@
 
     /** This class is used to incrementally build {@link VcnConfig} objects. */
     public static class Builder {
+        @NonNull private final String mPackageName;
+
         @NonNull
         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
 
+        public Builder(@NonNull Context context) {
+            Objects.requireNonNull(context, "context was null");
+
+            mPackageName = context.getOpPackageName();
+        }
+
         /**
          * Adds a configuration for an individual gateway connection.
          *
@@ -168,7 +199,7 @@
          */
         @NonNull
         public VcnConfig build() {
-            return new VcnConfig(mGatewayConnectionConfigs);
+            return new VcnConfig(mPackageName, mGatewayConnectionConfigs);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 19c183f..b881a339 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -101,7 +101,7 @@
         requireNonNull(config, "config was null");
 
         try {
-            mService.setVcnConfig(subscriptionGroup, config);
+            mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
         } catch (ServiceSpecificException e) {
             throw new IOException(e);
         } catch (RemoteException e) {
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index c014ef6..0326b72 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1463,8 +1463,7 @@
             Uri uri = MediaStore.scanFile(resolver, realFile);
             if (uri != null) {
                 Bundle opts = new Bundle();
-                // TODO(b/158465539): Use API constant
-                opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true);
+                opts.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
                 AssetFileDescriptor afd = resolver.openTypedAssetFileDescriptor(uri, "*/*", opts);
                 Log.i(TAG, "Changed to modern format dataSource for: " + realFile);
                 return afd.getFileDescriptor();
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 7243234..4868827 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -241,7 +241,7 @@
                             opEntry.getAttributedOpEntries().get(attributionTag);
 
                     long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags);
-                    if (lastAccessTime < recentThreshold) {
+                    if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
                         continue;
                     }
                     if (!isUserSensitive(packageName, user, op)
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index ac6208b..021d5fc 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -38,12 +38,11 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
-import android.telephony.emergency.EmergencyNumber;
-import android.telephony.ims.ImsReasonInfo;
 import android.telephony.NetworkRegistrationInfo.Domain;
 import android.telephony.TelephonyManager.DataEnabledReason;
 import android.telephony.TelephonyManager.DataState;
-import android.util.Log;
+import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -1082,7 +1081,9 @@
 
     /**
      * Create a PhoneStateListener for the Phone with the default subscription.
-     * This class requires Looper.myLooper() not return null.
+     * If this is created for use with deprecated API
+     * {@link TelephonyManager#listen(PhoneStateListener, int)}, then this class requires
+     * Looper.myLooper() not return null.
      */
     public PhoneStateListener() {
         this(null, Looper.myLooper());
@@ -1120,7 +1121,10 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public PhoneStateListener(Integer subId, Looper looper) {
-        this(subId, new HandlerExecutor(new Handler(looper)));
+        if (looper != null) {
+            setExecutor(new HandlerExecutor(new Handler(looper)));
+        }
+        mSubId = subId;
         if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion()
                 >= Build.VERSION_CODES.Q) {
             throw new IllegalArgumentException("PhoneStateListener with subId: "
@@ -1140,7 +1144,8 @@
      */
     @Deprecated
     public PhoneStateListener(@NonNull Executor executor) {
-        this(null, executor);
+        setExecutor(executor);
+        mSubId = null;
     }
 
     private @NonNull Executor mExecutor;
@@ -1153,12 +1158,14 @@
             throw new IllegalArgumentException("PhoneStateListener Executor must be non-null");
         }
         mExecutor = executor;
+        callback = new IPhoneStateListenerStub(this, mExecutor);
     }
 
-    private PhoneStateListener(Integer subId, Executor executor) {
-        setExecutor(executor);
-        mSubId = subId;
-        callback = new IPhoneStateListenerStub(this, mExecutor);
+    /**
+     * @hide
+     */
+    public boolean isExecutorSet() {
+        return mExecutor != null;
     }
 
     /**
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 7f1ee30..19de396 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -17,8 +17,14 @@
 package android.util;
 
 import android.annotation.AnyRes;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.content.pm.ActivityInfo.Config;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Container for a dynamically typed data value.  Primarily used with
  * {@link android.content.res.Resources} for holding resource values.
@@ -95,6 +101,18 @@
      *  defined below. */
     public static final int COMPLEX_UNIT_MASK = 0xf;
 
+    /** @hide **/
+    @IntDef(prefix = "COMPLEX_UNIT_", value = {
+            COMPLEX_UNIT_PX,
+            COMPLEX_UNIT_DIP,
+            COMPLEX_UNIT_SP,
+            COMPLEX_UNIT_PT,
+            COMPLEX_UNIT_IN,
+            COMPLEX_UNIT_MM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ComplexDimensionUnit {}
+
     /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
     public static final int COMPLEX_UNIT_PX = 0;
     /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
@@ -381,7 +399,7 @@
      * @return The complex floating point value multiplied by the appropriate 
      * metrics depending on its unit. 
      */
-    public static float applyDimension(int unit, float value,
+    public static float applyDimension(@ComplexDimensionUnit int unit, float value,
                                        DisplayMetrics metrics)
     {
         switch (unit) {
@@ -417,6 +435,130 @@
     }
 
     /**
+     * Construct a complex data integer.  This validates the radix and the magnitude of the
+     * mantissa, and sets the {@link TypedValue#COMPLEX_MANTISSA_MASK} and
+     * {@link TypedValue#COMPLEX_RADIX_MASK} components as provided. The units are not set.
+     **
+     * @param mantissa an integer representing the mantissa.
+     * @param radix a radix option, e.g. {@link TypedValue#COMPLEX_RADIX_23p0}.
+     * @return A complex data integer representing the value.
+     * @hide
+     */
+    private static int createComplex(@IntRange(from = -0x800000, to = 0x7FFFFF) int mantissa,
+            int radix) {
+        if (mantissa < -0x800000 || mantissa >= 0x800000) {
+            throw new IllegalArgumentException("Magnitude of mantissa is too large: " + mantissa);
+        }
+        if (radix < TypedValue.COMPLEX_RADIX_23p0 || radix > TypedValue.COMPLEX_RADIX_0p23) {
+            throw new IllegalArgumentException("Invalid radix: " + radix);
+        }
+        return ((mantissa & TypedValue.COMPLEX_MANTISSA_MASK) << TypedValue.COMPLEX_MANTISSA_SHIFT)
+                | (radix << TypedValue.COMPLEX_RADIX_SHIFT);
+    }
+
+    /**
+     * Convert a base value to a complex data integer.  This sets the {@link
+     * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the
+     * data to create a floating point representation of the given value. The units are not set.
+     *
+     * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}.
+     *
+     * @param value An integer value.
+     * @return A complex data integer representing the value.
+     * @hide
+     */
+    public static int intToComplex(int value) {
+        if (value < -0x800000 || value >= 0x800000) {
+            throw new IllegalArgumentException("Magnitude of the value is too large: " + value);
+        }
+        return createComplex(value, TypedValue.COMPLEX_RADIX_23p0);
+    }
+
+    /**
+     * Convert a base value to a complex data integer.  This sets the {@link
+     * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the
+     * data to create a floating point representation of the given value. The units are not set.
+     *
+     * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}.
+     *
+     * @param value A floating point value.
+     * @return A complex data integer representing the value.
+     * @hide
+     */
+    public static int floatToComplex(@FloatRange(from = -0x800000, to = 0x7FFFFF) float value) {
+        // validate that the magnitude fits in this representation
+        if (value < (float) -0x800000 - .5f || value >= (float) 0x800000 - .5f) {
+            throw new IllegalArgumentException("Magnitude of the value is too large: " + value);
+        }
+        try {
+            // If there's no fraction, use integer representation, as that's clearer
+            if (value == (float) (int) value) {
+                return createComplex((int) value, TypedValue.COMPLEX_RADIX_23p0);
+            }
+            float absValue = Math.abs(value);
+            // If the magnitude is 0, we don't need any magnitude digits
+            if (absValue < 1f) {
+                return createComplex(Math.round(value * (1 << 23)), TypedValue.COMPLEX_RADIX_0p23);
+            }
+            // If the magnitude is less than 2^8, use 8 magnitude digits
+            if (absValue < (float) (1 << 8)) {
+                return createComplex(Math.round(value * (1 << 15)), TypedValue.COMPLEX_RADIX_8p15);
+            }
+            // If the magnitude is less than 2^16, use 16 magnitude digits
+            if (absValue < (float) (1 << 16)) {
+                return createComplex(Math.round(value * (1 << 7)), TypedValue.COMPLEX_RADIX_16p7);
+            }
+            // The magnitude requires all 23 digits
+            return createComplex(Math.round(value), TypedValue.COMPLEX_RADIX_23p0);
+        } catch (IllegalArgumentException ex) {
+            // Wrap exception so as to include the value argument in the message.
+            throw new IllegalArgumentException("Unable to convert value to complex: " + value, ex);
+        }
+    }
+
+    /**
+     * <p>Creates a complex data integer that stores a dimension value and units.
+     *
+     * <p>The resulting value can be passed to e.g.
+     * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel
+     * value for the dimension.
+     *
+     * @param value the value of the dimension
+     * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
+     * @return A complex data integer representing the value and units of the dimension.
+     * @hide
+     */
+    public static int createComplexDimension(
+            @IntRange(from = -0x800000, to = 0x7FFFFF) int value,
+            @ComplexDimensionUnit int units) {
+        if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) {
+            throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units);
+        }
+        return intToComplex(value) | units;
+    }
+
+    /**
+     * <p>Creates a complex data integer that stores a dimension value and units.
+     *
+     * <p>The resulting value can be passed to e.g.
+     * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel
+     * value for the dimension.
+     *
+     * @param value the value of the dimension
+     * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
+     * @return A complex data integer representing the value and units of the dimension.
+     * @hide
+     */
+    public static int createComplexDimension(
+            @FloatRange(from = -0x800000, to = 0x7FFFFF) float value,
+            @ComplexDimensionUnit int units) {
+        if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) {
+            throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units);
+        }
+        return floatToComplex(value) | units;
+    }
+
+    /**
      * Converts a complex data value holding a fraction to its final floating 
      * point value. The given <var>data</var> must be structured as a 
      * {@link #TYPE_FRACTION}.
diff --git a/core/java/android/uwb/CloseReason.aidl b/core/java/android/uwb/CloseReason.aidl
deleted file mode 100644
index bef129e..0000000
--- a/core/java/android/uwb/CloseReason.aidl
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 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.uwb;
-
-/**
- * @hide
- */
-@Backing(type="int")
-enum CloseReason {
-  /**
-   * Unknown reason
-   */
-  UNKNOWN,
-
-  /**
-   * A local API call triggered the close, such as a call to
-   * IUwbAdapter.stopRanging.
-   */
-  LOCAL_API,
-
-  /**
-   * The maximum number of sessions has been reached. This error may be generated
-   * for an active session if a higher priority session begins.
-   */
-  MAX_SESSIONS_REACHED,
-
-  /**
-   * The system state has changed resulting in the session ending (e.g. the user
-   * disables UWB, or the user's locale changes and an active channel is no longer
-   * permitted to be used).
-   */
-  SYSTEM_POLICY,
-
-  /**
-   * The remote device has requested to terminate the session
-   */
-  REMOTE_REQUEST,
-
-  /**
-   * The session was closed for a protocol specific reason
-   */
-  PROTOCOL_SPECIFIC,
-}
-
diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl
index 2c8b2e4..b9c5508 100644
--- a/core/java/android/uwb/IUwbAdapter.aidl
+++ b/core/java/android/uwb/IUwbAdapter.aidl
@@ -119,42 +119,96 @@
   PersistableBundle getSpecificationInfo();
 
   /**
-   * Request to start a new ranging session
+   * Request to open a new ranging session
    *
-   * This function must return before calling IUwbAdapterCallbacks
-   * #onRangingStarted, #onRangingClosed, or #onRangingResult.
+   * This function must return before calling any functions in
+   * IUwbAdapterCallbacks.
    *
-   * A ranging session does not need to be started before returning.
+   * This function does not start the ranging session, but all necessary
+   * components must be initialized and ready to start a new ranging
+   * session prior to calling IUwbAdapterCallback#onRangingOpened.
    *
-   * IUwbAdapterCallbacks#onRangingStarted must be called within
-   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
-   * if the ranging session is scheduled to start successfully.
+   * IUwbAdapterCallbacks#onRangingOpened must be called within
+   * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being
+   * called if the ranging session is opened successfully.
    *
-   * IUwbAdapterCallbacks#onRangingStartFailed must be called within
-   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
-   * if the ranging session fails to be scheduled to start successfully.
+   * IUwbAdapterCallbacks#onRangingOpenFailed must be called within
+   * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being called
+   * if the ranging session fails to be opened.
    *
    * @param rangingCallbacks the callbacks used to deliver ranging information
    * @param parameters the configuration to use for ranging
    * @return a SessionHandle used to identify this ranging request
    */
-  SessionHandle startRanging(in IUwbRangingCallbacks rangingCallbacks,
-                             in PersistableBundle parameters);
+  SessionHandle openRanging(in IUwbRangingCallbacks rangingCallbacks,
+                            in PersistableBundle parameters);
 
   /**
-   * Stop and close ranging for the session associated with the given handle
+   * Request to start ranging
+   *
+   * IUwbAdapterCallbacks#onRangingStarted must be called within
+   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being
+   * called if the ranging session starts successfully.
+   *
+   * IUwbAdapterCallbacks#onRangingStartFailed must be called within
+   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being
+   * called if the ranging session fails to be started.
+   *
+   * @param sessionHandle the session handle to start ranging for
+   * @param parameters additional configuration required to start ranging
+   */
+  void startRanging(in SessionHandle sessionHandle,
+                    in PersistableBundle parameters);
+
+  /**
+   * Request to reconfigure ranging
+   *
+   * IUwbAdapterCallbacks#onRangingReconfigured must be called after
+   * successfully reconfiguring the session.
+   *
+   * IUwbAdapterCallbacks#onRangingReconfigureFailed must be called after
+   * failing to reconfigure the session.
+   *
+   * A session must not be modified by a failed call to #reconfigureRanging.
+   *
+   * @param sessionHandle the session handle to start ranging for
+   * @param parameters the parameters to reconfigure and their new values
+   */
+  void reconfigureRanging(in SessionHandle sessionHandle,
+                          in PersistableBundle parameters);
+
+  /**
+   * Request to stop ranging
+   *
+   * IUwbAdapterCallbacks#onRangingStopped must be called after
+   * successfully stopping the session.
+   *
+   * IUwbAdapterCallbacks#onRangingStopFailed must be called after failing
+   * to stop the session.
+   *
+   * @param sessionHandle the session handle to stop ranging for
+   */
+  void stopRanging(in SessionHandle sessionHandle);
+
+  /**
+   * Close ranging for the session associated with the given handle
    *
    * Calling with an invalid handle or a handle that has already been closed
    * is a no-op.
    *
    * IUwbAdapterCallbacks#onRangingClosed must be called within
-   * RANGING_SESSION_CLOSE_THRESHOLD_MS of #stopRanging being called.
+   * RANGING_SESSION_CLOSE_THRESHOLD_MS of #closeRanging being called.
    *
-   * @param sessionHandle the session handle to stop ranging for
+   * @param sessionHandle the session handle to close ranging for
    */
   void closeRanging(in SessionHandle sessionHandle);
 
   /**
+   * The maximum allowed time to open a ranging session.
+   */
+  const int RANGING_SESSION_OPEN_THRESHOLD_MS = 3000; // Value TBD
+
+  /**
    * The maximum allowed time to start a ranging session.
    */
   const int RANGING_SESSION_START_THRESHOLD_MS = 3000; // Value TBD
diff --git a/core/java/android/uwb/IUwbRangingCallbacks.aidl b/core/java/android/uwb/IUwbRangingCallbacks.aidl
index 1fc3bfd..f71f3ff 100644
--- a/core/java/android/uwb/IUwbRangingCallbacks.aidl
+++ b/core/java/android/uwb/IUwbRangingCallbacks.aidl
@@ -17,16 +17,33 @@
 package android.uwb;
 
 import android.os.PersistableBundle;
-import android.uwb.CloseReason;
+import android.uwb.RangingChangeReason;
 import android.uwb.RangingReport;
 import android.uwb.SessionHandle;
-import android.uwb.StartFailureReason;
 
 /**
  * @hide
  */
 interface IUwbRangingCallbacks {
   /**
+   * Called when the ranging session has been opened
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   */
+  void onRangingOpened(in SessionHandle sessionHandle);
+
+  /**
+   * Called when a ranging session fails to start
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param reason the reason the session failed to start
+   * @param parameters protocol specific parameters
+   */
+  void onRangingOpenFailed(in SessionHandle sessionHandle,
+                           RangingChangeReason reason,
+                           in PersistableBundle parameters);
+
+  /**
    * Called when ranging has started
    *
    * May output parameters generated by the lower layers that must be sent to the
@@ -47,8 +64,49 @@
    * @param reason the reason the session failed to start
    * @param parameters protocol specific parameters
    */
-  void onRangingStartFailed(in SessionHandle sessionHandle, StartFailureReason reason,
+  void onRangingStartFailed(in SessionHandle sessionHandle,
+                            RangingChangeReason reason,
                             in PersistableBundle parameters);
+
+   /**
+   * Called when ranging has been reconfigured
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param parameters the updated ranging configuration
+   */
+  void onRangingReconfigured(in SessionHandle sessionHandle,
+                             in PersistableBundle parameters);
+
+  /**
+   * Called when a ranging session fails to be reconfigured
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param reason the reason the session failed to reconfigure
+   * @param parameters protocol specific parameters
+   */
+  void onRangingReconfigureFailed(in SessionHandle sessionHandle,
+                                  RangingChangeReason reason,
+                                  in PersistableBundle parameters);
+
+  /**
+   * Called when the ranging session has been stopped
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   */
+
+  void onRangingStopped(in SessionHandle sessionHandle);
+
+  /**
+   * Called when a ranging session fails to stop
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param reason the reason the session failed to stop
+   * @param parameters protocol specific parameters
+   */
+  void onRangingStopFailed(in SessionHandle sessionHandle,
+                           RangingChangeReason reason,
+                           in PersistableBundle parameters);
+
   /**
    * Called when a ranging session is closed
    *
@@ -56,7 +114,8 @@
    * @param reason the reason the session was closed
    * @param parameters protocol specific parameters
    */
-  void onRangingClosed(in SessionHandle sessionHandle, CloseReason reason,
+  void onRangingClosed(in SessionHandle sessionHandle,
+                       RangingChangeReason reason,
                        in PersistableBundle parameters);
 
   /**
diff --git a/core/java/android/uwb/RangingChangeReason.aidl b/core/java/android/uwb/RangingChangeReason.aidl
new file mode 100644
index 0000000..19d4b39
--- /dev/null
+++ b/core/java/android/uwb/RangingChangeReason.aidl
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum RangingChangeReason {
+  /**
+   * Unknown reason
+   */
+  UNKNOWN,
+
+  /**
+   * A local API call triggered the change, such as a call to
+   * IUwbAdapter.closeRanging.
+   */
+  LOCAL_API,
+
+  /**
+   * The maximum number of sessions has been reached. This may be generated for
+   * an active session if a higher priority session begins.
+   */
+  MAX_SESSIONS_REACHED,
+
+  /**
+   * The system state has changed resulting in the session changing (e.g. the
+   * user disables UWB, or the user's locale changes and an active channel is no
+   * longer permitted to be used).
+   */
+  SYSTEM_POLICY,
+
+  /**
+   * The remote device has requested to change the session
+   */
+  REMOTE_REQUEST,
+
+  /**
+   * The session changed for a protocol specific reason
+   */
+  PROTOCOL_SPECIFIC,
+
+  /**
+   * The provided parameters were invalid
+   */
+  BAD_PARAMETERS,
+}
+
diff --git a/core/java/android/uwb/RangingManager.java b/core/java/android/uwb/RangingManager.java
index a9bf4ab..5ac95d4 100644
--- a/core/java/android/uwb/RangingManager.java
+++ b/core/java/android/uwb/RangingManager.java
@@ -50,7 +50,7 @@
             @NonNull RangingSession.Callback callbacks) {
         SessionHandle sessionHandle;
         try {
-            sessionHandle = mAdapter.startRanging(this, params);
+            sessionHandle = mAdapter.openRanging(this, params);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -59,7 +59,7 @@
             if (hasSession(sessionHandle)) {
                 Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle");
                 executor.execute(() -> callbacks.onClosed(
-                        RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR,
+                        RangingSession.Callback.REASON_GENERIC_ERROR,
                         new PersistableBundle()));
             }
 
@@ -75,6 +75,67 @@
     }
 
     @Override
+    public void onRangingOpened(SessionHandle sessionHandle) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG,
+                        "onRangingOpened - received unexpected SessionHandle: " + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingOpened();
+        }
+    }
+
+    @Override
+    public void onRangingOpenFailed(SessionHandle sessionHandle, @RangingChangeReason int reason,
+            PersistableBundle parameters) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG,
+                        "onRangingOpened - received unexpected SessionHandle: " + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingOpenFailed(convertToReason(reason), parameters);
+            mRangingSessionTable.remove(sessionHandle);
+        }
+    }
+
+    @Override
+    public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle parameters) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG,
+                        "onRangingReconfigured - received unexpected SessionHandle: "
+                                + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingReconfigured(parameters);
+        }
+    }
+
+    @Override
+    public void onRangingReconfigureFailed(SessionHandle sessionHandle,
+            @RangingChangeReason int reason, PersistableBundle params) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG, "onRangingStartFailed - received unexpected SessionHandle: "
+                        + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingReconfigureFailed(convertToReason(reason), params);
+        }
+    }
+
+
+    @Override
     public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters) {
         synchronized (this) {
             if (!hasSession(sessionHandle)) {
@@ -89,7 +150,7 @@
     }
 
     @Override
-    public void onRangingStartFailed(SessionHandle sessionHandle, int reason,
+    public void onRangingStartFailed(SessionHandle sessionHandle, @RangingChangeReason int reason,
             PersistableBundle params) {
         synchronized (this) {
             if (!hasSession(sessionHandle)) {
@@ -99,13 +160,42 @@
             }
 
             RangingSession session = mRangingSessionTable.get(sessionHandle);
-            session.onRangingClosed(convertStartFailureToCloseReason(reason), params);
-            mRangingSessionTable.remove(sessionHandle);
+            session.onRangingStartFailed(convertToReason(reason), params);
         }
     }
 
     @Override
-    public void onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params) {
+    public void onRangingStopped(SessionHandle sessionHandle) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG, "onRangingStopped - received unexpected SessionHandle: "
+                        + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingStopped();
+        }
+    }
+
+    @Override
+    public void onRangingStopFailed(SessionHandle sessionHandle, @RangingChangeReason int reason,
+            PersistableBundle parameters) {
+        synchronized (this) {
+            if (!hasSession(sessionHandle)) {
+                Log.w(TAG, "onRangingStopFailed - received unexpected SessionHandle: "
+                        + sessionHandle);
+                return;
+            }
+
+            RangingSession session = mRangingSessionTable.get(sessionHandle);
+            session.onRangingStopFailed(convertToReason(reason), parameters);
+        }
+    }
+
+    @Override
+    public void onRangingClosed(SessionHandle sessionHandle, @RangingChangeReason int reason,
+            PersistableBundle params) {
         synchronized (this) {
             if (!hasSession(sessionHandle)) {
                 Log.w(TAG, "onRangingClosed - received unexpected SessionHandle: " + sessionHandle);
@@ -113,7 +203,7 @@
             }
 
             RangingSession session = mRangingSessionTable.get(sessionHandle);
-            session.onRangingClosed(convertToCloseReason(reason), params);
+            session.onRangingClosed(convertToReason(reason), params);
             mRangingSessionTable.remove(sessionHandle);
         }
     }
@@ -131,48 +221,30 @@
         }
     }
 
-    @RangingSession.Callback.CloseReason
-    private static int convertToCloseReason(@CloseReason int reason) {
+    @RangingSession.Callback.Reason
+    private static int convertToReason(@RangingChangeReason int reason) {
         switch (reason) {
-            case CloseReason.LOCAL_API:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API;
+            case RangingChangeReason.LOCAL_API:
+                return RangingSession.Callback.REASON_LOCAL_REQUEST;
 
-            case CloseReason.MAX_SESSIONS_REACHED:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;
+            case RangingChangeReason.MAX_SESSIONS_REACHED:
+                return RangingSession.Callback.REASON_MAX_SESSIONS_REACHED;
 
-            case CloseReason.SYSTEM_POLICY:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;
+            case RangingChangeReason.SYSTEM_POLICY:
+                return RangingSession.Callback.REASON_SYSTEM_POLICY;
 
-            case CloseReason.REMOTE_REQUEST:
-                return RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST;
+            case RangingChangeReason.REMOTE_REQUEST:
+                return RangingSession.Callback.REASON_REMOTE_REQUEST;
 
-            case CloseReason.PROTOCOL_SPECIFIC:
-                return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;
+            case RangingChangeReason.PROTOCOL_SPECIFIC:
+                return RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR;
 
-            case CloseReason.UNKNOWN:
+            case RangingChangeReason.BAD_PARAMETERS:
+                return RangingSession.Callback.REASON_BAD_PARAMETERS;
+
+            case RangingChangeReason.UNKNOWN:
             default:
-                return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
-        }
-    }
-
-    @RangingSession.Callback.CloseReason
-    private static int convertStartFailureToCloseReason(@StartFailureReason int reason) {
-        switch (reason) {
-            case StartFailureReason.BAD_PARAMETERS:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS;
-
-            case StartFailureReason.MAX_SESSIONS_REACHED:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;
-
-            case StartFailureReason.SYSTEM_POLICY:
-                return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;
-
-            case StartFailureReason.PROTOCOL_SPECIFIC:
-                return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;
-
-            case StartFailureReason.UNKNOWN:
-            default:
-                return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
+                return RangingSession.Callback.REASON_UNKNOWN;
         }
     }
 }
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index b0dbd85..0f87af4 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -36,9 +36,9 @@
  * <p>To get an instance of {@link RangingSession}, first use
  * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a
  * session. Once the session is opened, a {@link RangingSession} object is provided through
- * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a
- * session fails, the failure is reported through
- * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} with the failure reason.
+ * {@link RangingSession.Callback#onOpened(RangingSession)}. If opening a session fails, the failure
+ * is reported through {@link RangingSession.Callback#onOpenFailed(int, PersistableBundle)} with the
+ * failure reason.
  *
  * @hide
  */
@@ -50,103 +50,162 @@
     private final Callback mCallback;
 
     private enum State {
+        /**
+         * The state of the {@link RangingSession} until
+         * {@link RangingSession.Callback#onOpened(RangingSession)} is invoked
+         */
         INIT,
-        OPEN,
-        CLOSED,
+
+        /**
+         * The {@link RangingSession} is initialized and ready to begin ranging
+         */
+        IDLE,
+
+        /**
+         * The {@link RangingSession} is actively ranging
+         */
+        ACTIVE,
+
+        /**
+         * The {@link RangingSession} is closed and may not be used for ranging.
+         */
+        CLOSED
     }
 
-    private State mState;
+    private State mState = State.INIT;
 
     /**
      * Interface for receiving {@link RangingSession} events
      */
     public interface Callback {
         /**
-         * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
-         * is successful
-         *
-         * @param session the newly opened {@link RangingSession}
-         * @param sessionInfo session specific parameters from lower layers
-         */
-        void onOpenSuccess(@NonNull RangingSession session, @NonNull PersistableBundle sessionInfo);
-
-        /**
          * @hide
          */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(value = {
-                CLOSE_REASON_UNKNOWN,
-                CLOSE_REASON_LOCAL_CLOSE_API,
-                CLOSE_REASON_LOCAL_BAD_PARAMETERS,
-                CLOSE_REASON_LOCAL_GENERIC_ERROR,
-                CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED,
-                CLOSE_REASON_LOCAL_SYSTEM_POLICY,
-                CLOSE_REASON_REMOTE_GENERIC_ERROR,
-                CLOSE_REASON_REMOTE_REQUEST})
-        @interface CloseReason {}
+                REASON_UNKNOWN,
+                REASON_LOCAL_REQUEST,
+                REASON_REMOTE_REQUEST,
+                REASON_BAD_PARAMETERS,
+                REASON_GENERIC_ERROR,
+                REASON_MAX_SESSIONS_REACHED,
+                REASON_SYSTEM_POLICY,
+                REASON_PROTOCOL_SPECIFIC_ERROR})
+        @interface Reason {}
 
         /**
          * Indicates that the session was closed or failed to open due to an unknown reason
          */
-        int CLOSE_REASON_UNKNOWN = 0;
+        int REASON_UNKNOWN = 0;
 
         /**
          * Indicates that the session was closed or failed to open because
          * {@link AutoCloseable#close()} or {@link RangingSession#close()} was called
          */
-        int CLOSE_REASON_LOCAL_CLOSE_API = 1;
-
-        /**
-         * Indicates that the session failed to open due to erroneous parameters passed
-         * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
-         */
-        int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2;
-
-        /**
-         * Indicates that the session was closed due to some local error on this device besides the
-         * error code already listed
-         */
-        int CLOSE_REASON_LOCAL_GENERIC_ERROR = 3;
-
-        /**
-         * Indicates that the session failed to open because the number of currently open sessions
-         * is equal to {@link UwbManager#getMaxSimultaneousSessions()}
-         */
-        int CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED = 4;
-
-        /**
-         * Indicates that the session was closed or failed to open due to local system policy, such
-         * as privacy policy, power management policy, permissions, and more.
-         */
-        int CLOSE_REASON_LOCAL_SYSTEM_POLICY = 5;
-
-        /**
-         * Indicates that the session was closed or failed to open due to an error with the remote
-         * device besides error codes already listed.
-         */
-        int CLOSE_REASON_REMOTE_GENERIC_ERROR = 6;
+        int REASON_LOCAL_REQUEST = 1;
 
         /**
          * Indicates that the session was closed or failed to open due to an explicit request from
          * the remote device.
          */
-        int CLOSE_REASON_REMOTE_REQUEST = 7;
+        int REASON_REMOTE_REQUEST = 2;
 
         /**
-         * Indicates that the session was closed for a protocol specific reason. The associated
-         * {@link PersistableBundle} should be consulted for additional information.
+         * Indicates that the session was closed or failed to open due to erroneous parameters
          */
-        int CLOSE_REASON_PROTOCOL_SPECIFIC = 8;
+        int REASON_BAD_PARAMETERS = 3;
 
         /**
+         * Indicates an error on this device besides the error code already listed
+         */
+        int REASON_GENERIC_ERROR = 4;
+
+        /**
+         * Indicates that the number of currently open sessions is equal to
+         * {@link UwbManager#getMaxSimultaneousSessions()} and additional sessions may not be
+         * opened.
+         */
+        int REASON_MAX_SESSIONS_REACHED = 5;
+
+        /**
+         * Indicates that the local system policy caused the change, such
+         * as privacy policy, power management policy, permissions, and more.
+         */
+        int REASON_SYSTEM_POLICY = 6;
+
+        /**
+         * Indicates a protocol specific error. The associated {@link PersistableBundle} should be
+         * consulted for additional information.
+         */
+        int REASON_PROTOCOL_SPECIFIC_ERROR = 7;
+
+        /**
+         * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
+         * is successful
+         *
+         * @param session the newly opened {@link RangingSession}
+         */
+        void onOpened(@NonNull RangingSession session);
+
+        /**
+         * Invoked if {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}}
+         * fails
+         *
+         * @param reason the failure reason
+         * @param params protocol specific parameters
+         */
+        void onOpenFailed(@Reason int reason, @NonNull PersistableBundle params);
+
+        /**
+         * Invoked when {@link RangingSession#start(PersistableBundle)} is successful
+         * @param sessionInfo session specific parameters from the lower layers
+         */
+        void onStarted(@NonNull PersistableBundle sessionInfo);
+
+        /**
+         * Invoked when {@link RangingSession#start(PersistableBundle)} fails
+         *
+         * @param reason the failure reason
+         * @param params protocol specific parameters
+         */
+        void onStartFailed(@Reason int reason, @NonNull PersistableBundle params);
+
+        /**
+         * Invoked when a request to reconfigure the session succeeds
+         *
+         * @param params the updated ranging configuration
+         */
+        void onReconfigured(@NonNull PersistableBundle params);
+
+        /**
+         * Invoked when a request to reconfigure the session fails
+         *
+         * @param reason reason the session failed to be reconfigured
+         * @param params protocol specific failure reasons
+         */
+        void onReconfigureFailed(@Reason int reason, @NonNull PersistableBundle params);
+
+        /**
+         * Invoked when a request to stop the session succeeds
+         */
+        void onStopped();
+
+        /**
+         * Invoked when a request to stop the session fails
+         *
+         * @param reason reason the session failed to be stopped
+         * @param params protocol specific failure reasons
+         */
+        void onStopFailed(@Reason int reason, @NonNull PersistableBundle params);
+
+       /**
          * Invoked when session is either closed spontaneously, or per user request via
-         * {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed
-         * to open.
+         * {@link RangingSession#close()} or {@link AutoCloseable#close()}.
          *
          * @param reason reason for the session closure
          * @param parameters protocol specific parameters related to the close reason
          */
-        void onClosed(@CloseReason int reason, @NonNull PersistableBundle parameters);
+        void onClosed(@Reason int reason, @NonNull PersistableBundle parameters);
 
         /**
          * Called once per ranging interval even when a ranging measurement fails
@@ -172,12 +231,95 @@
      * @hide
      */
     public boolean isOpen() {
-        return mState == State.OPEN;
+        return mState == State.IDLE || mState == State.ACTIVE;
+    }
+
+    /**
+     * Begins ranging for the session.
+     *
+     * <p>On successfully starting a ranging session,
+     * {@link RangingSession.Callback#onStarted(PersistableBundle)} is invoked.
+     *
+     * <p>On failure to start the session,
+     * {@link RangingSession.Callback#onStartFailed(int, PersistableBundle)} is invoked.
+     *
+     * @param params configuration parameters for starting the session
+     */
+    public void start(@NonNull PersistableBundle params) {
+        if (mState != State.IDLE) {
+            throw new IllegalStateException();
+        }
+
+        try {
+            mAdapter.startRanging(mSessionHandle, params);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attempts to reconfigure the session with the given parameters
+     * <p>This call may be made when the session is open.
+     *
+     * <p>On successfully reconfiguring the session
+     * {@link RangingSession.Callback#onReconfigured(PersistableBundle)} is invoked.
+     *
+     * <p>On failure to reconfigure the session,
+     * {@link RangingSession.Callback#onReconfigureFailed(int, PersistableBundle)} is invoked.
+     *
+     * @param params the parameters to reconfigure and their new values
+     */
+    public void reconfigure(@NonNull PersistableBundle params) {
+        if (mState != State.ACTIVE && mState != State.IDLE) {
+            throw new IllegalStateException();
+        }
+
+        try {
+            mAdapter.reconfigureRanging(mSessionHandle, params);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stops actively ranging
+     *
+     * <p>A session that has been stopped may be resumed by calling
+     * {@link RangingSession#start(PersistableBundle)} without the need to open a new session.
+     *
+     * <p>Stopping a {@link RangingSession} is useful when the lower layers should not discard
+     * the parameters of the session, or when a session needs to be able to be resumed quickly.
+     *
+     * <p>If the {@link RangingSession} is no longer needed, use {@link RangingSession#close()} to
+     * completely close the session and allow lower layers of the stack to perform necessarily
+     * cleanup.
+     *
+     * <p>Stopped sessions may be closed by the system at any time. In such a case,
+     * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} is invoked.
+     *
+     * <p>On failure to stop the session,
+     * {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked.
+     */
+    public void stop() {
+        if (mState != State.ACTIVE) {
+            throw new IllegalStateException();
+        }
+
+        try {
+            mAdapter.stopRanging(mSessionHandle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * Close the ranging session
-     * <p>If this session is currently open, it will close and stop the session.
+     *
+     * <p>After calling this function, in order resume ranging, a new {@link RangingSession} must
+     * be opened by calling
+     * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}.
+     *
+     * <p>If this session is currently ranging, it will stop and close the session.
      * <p>If the session is in the process of being opened, it will attempt to stop the session from
      * being opened.
      * <p>If the session is already closed, the registered
@@ -192,7 +334,7 @@
     public void close() {
         if (mState == State.CLOSED) {
             mExecutor.execute(() -> mCallback.onClosed(
-                    Callback.CLOSE_REASON_LOCAL_CLOSE_API, new PersistableBundle()));
+                    Callback.REASON_LOCAL_REQUEST, new PersistableBundle()));
             return;
         }
 
@@ -206,32 +348,114 @@
     /**
      * @hide
      */
+    public void onRangingOpened() {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingOpened invoked for a closed session");
+            return;
+        }
+
+        mState = State.IDLE;
+        executeCallback(() -> mCallback.onOpened(this));
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingOpenFailed(@Callback.Reason int reason,
+            @NonNull PersistableBundle params) {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingOpenFailed invoked for a closed session");
+            return;
+        }
+
+        mState = State.CLOSED;
+        executeCallback(() -> mCallback.onOpenFailed(reason, params));
+    }
+
+    /**
+     * @hide
+     */
     public void onRangingStarted(@NonNull PersistableBundle parameters) {
         if (mState == State.CLOSED) {
             Log.w(TAG, "onRangingStarted invoked for a closed session");
             return;
         }
 
-        mState = State.OPEN;
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mExecutor.execute(() -> mCallback.onOpenSuccess(this, parameters));
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        mState = State.ACTIVE;
+        executeCallback(() -> mCallback.onStarted(parameters));
     }
 
     /**
      * @hide
      */
-    public void onRangingClosed(@Callback.CloseReason int reason, PersistableBundle parameters) {
-        mState = State.CLOSED;
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mExecutor.execute(() -> mCallback.onClosed(reason, parameters));
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+    public void onRangingStartFailed(@Callback.Reason int reason,
+            @NonNull PersistableBundle params) {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingStartFailed invoked for a closed session");
+            return;
         }
+
+        executeCallback(() -> mCallback.onStartFailed(reason, params));
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingReconfigured(@NonNull PersistableBundle params) {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingReconfigured invoked for a closed session");
+            return;
+        }
+
+        executeCallback(() -> mCallback.onReconfigured(params));
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingReconfigureFailed(@Callback.Reason int reason,
+            @NonNull PersistableBundle params) {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingReconfigureFailed invoked for a closed session");
+            return;
+        }
+
+        executeCallback(() -> mCallback.onReconfigureFailed(reason, params));
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingStopped() {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingStopped invoked for a closed session");
+            return;
+        }
+
+        mState = State.IDLE;
+        executeCallback(() -> mCallback.onStopped());
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingStopFailed(@Callback.Reason int reason,
+            @NonNull PersistableBundle params) {
+        if (mState == State.CLOSED) {
+            Log.w(TAG, "onRangingStopFailed invoked for a closed session");
+            return;
+        }
+
+        executeCallback(() -> mCallback.onStopFailed(reason, params));
+    }
+
+    /**
+     * @hide
+     */
+    public void onRangingClosed(@Callback.Reason int reason,
+            @NonNull PersistableBundle parameters) {
+        mState = State.CLOSED;
+        executeCallback(() -> mCallback.onClosed(reason, parameters));
     }
 
     /**
@@ -243,9 +467,16 @@
             return;
         }
 
+        executeCallback(() -> mCallback.onReportReceived(report));
+    }
+
+    /**
+     * @hide
+     */
+    private void executeCallback(@NonNull Runnable runnable) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            mExecutor.execute(() -> mCallback.onReportReceived(report));
+            mExecutor.execute(runnable);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/uwb/StartFailureReason.aidl b/core/java/android/uwb/StartFailureReason.aidl
deleted file mode 100644
index 4d9c962..0000000
--- a/core/java/android/uwb/StartFailureReason.aidl
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 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.uwb;
-
-/**
- * @hide
- */
-@Backing(type="int")
-enum StartFailureReason {
-  /**
-   * Unknown start failure reason
-   */
-  UNKNOWN,
-
-  /**
-   * The provided parameters were invalid and ranging could not start
-   */
-  BAD_PARAMETERS,
-
-  /**
-   * The maximum number of sessions has been reached. This error may be generated
-   * for an active session if a higher priority session begins.
-   */
-  MAX_SESSIONS_REACHED,
-
-  /**
-   * The system state has changed resulting in the session ending (e.g. the user
-   * disables UWB, or the user's locale changes and an active channel is no longer
-   * permitted to be used).
-   */
-  SYSTEM_POLICY,
-
-  /**
-   * The session could not start because of a protocol specific reason.
-   */
-  PROTOCOL_SPECIFIC,
-}
-
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index f4d8018..15ee5b5 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -369,13 +369,13 @@
 
     /**
      * Open a {@link RangingSession} with the given parameters
-     * <p>This function is asynchronous and will return before ranging begins. The
-     * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)} function is
-     * called with a {@link RangingSession} object used to control ranging when the session is
-     * successfully opened.
+     * <p>The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a
+     * {@link RangingSession} object used to control ranging when the session is successfully
+     * opened.
      *
-     * <p>If a session cannot be opened, then {@link RangingSession.Callback#onClosed(int)} will be
-     * invoked with the appropriate {@link RangingSession.Callback.CloseReason}.
+     * <p>If a session cannot be opened, then
+     * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} will be invoked with the
+     * appropriate {@link RangingSession.Callback.Reason}.
      *
      * <p>An open {@link RangingSession} will be automatically closed if client application process
      * dies.
@@ -391,7 +391,7 @@
      * @return an {@link AutoCloseable} that is able to be used to close or cancel the opening of a
      *         {@link RangingSession} that has been requested through {@link #openRangingSession}
      *         but has not yet been made available by
-     *         {@link RangingSession.Callback#onOpenSuccess}.
+     *         {@link RangingSession.Callback#onOpened(RangingSession)}.
      */
     @NonNull
     public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 673ed0d..5ce4c50 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -156,14 +156,25 @@
      * Sets the extra margin at the end of the top line of left-aligned text + icons.
      * This value will have the margin required to accommodate the expand button added to it.
      *
-     * @param extraMarginEnd extra margin
+     * @param extraMarginEnd extra margin in px
      */
-    @RemotableViewMethod
     public void setTopLineExtraMarginEnd(int extraMarginEnd) {
         mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin);
     }
 
     /**
+     * Sets the extra margin at the end of the top line of left-aligned text + icons.
+     * This value will have the margin required to accommodate the expand button added to it.
+     *
+     * @param extraMarginEndDp extra margin in dp
+     */
+    @RemotableViewMethod
+    public void setTopLineExtraMarginEndDp(float extraMarginEndDp) {
+        setTopLineExtraMarginEnd(
+                (int) (extraMarginEndDp * getResources().getDisplayMetrics().density));
+    }
+
+    /**
      * Get the current margin end value for the header text.
      * Add this to {@link #getTopLineBaseMarginEnd()} to get the total margin of the top line.
      *
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f642d75..2f97357 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -333,6 +333,8 @@
      */
     public long mNativeObject;
     private long mNativeHandle;
+    private boolean mDebugRelease = false;
+    private Throwable mReleaseStack = null;
 
     // TODO: Move width/height to native and fix locking through out.
     private final Object mLock = new Object();
@@ -580,6 +582,13 @@
         }
         mNativeObject = nativeObject;
         mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
+        if (mNativeObject == 0) {
+            if (mDebugRelease) {
+                mReleaseStack = new Throwable("assigned zero nativeObject here");
+            }
+        } else {
+            mReleaseStack = null;
+        }
     }
 
     /**
@@ -590,6 +599,7 @@
         mWidth = other.mWidth;
         mHeight = other.mHeight;
         mLocalOwnerView = other.mLocalOwnerView;
+        mDebugRelease = other.mDebugRelease;
         assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite);
     }
 
@@ -1419,6 +1429,7 @@
         mName = in.readString8();
         mWidth = in.readInt();
         mHeight = in.readInt();
+        mDebugRelease = in.readBoolean();
 
         long object = 0;
         if (in.readInt() != 0) {
@@ -1437,8 +1448,12 @@
         dest.writeString8(mName);
         dest.writeInt(mWidth);
         dest.writeInt(mHeight);
+        dest.writeBoolean(mDebugRelease);
         if (mNativeObject == 0) {
             dest.writeInt(0);
+            if (mReleaseStack != null) {
+                Log.w(TAG, "Sending invalid " + this + " caused by:", mReleaseStack);
+            }
         } else {
             dest.writeInt(1);
         }
@@ -1450,6 +1465,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setDebugRelease(boolean debug) {
+        mDebugRelease = debug;
+    }
+
+    /**
      * Checks whether two {@link SurfaceControl} objects represent the same surface.
      *
      * @param other The other object to check
@@ -1519,6 +1541,9 @@
             nativeRelease(mNativeObject);
             mNativeObject = 0;
             mNativeHandle = 0;
+            if (mDebugRelease) {
+                mReleaseStack = new Throwable("released here");
+            }
             mCloseGuard.close();
         }
     }
@@ -1534,8 +1559,11 @@
     }
 
     private void checkNotReleased() {
-        if (mNativeObject == 0) throw new NullPointerException(
-                "Invalid " + this + ", mNativeObject is null. Have you called release() already?");
+        if (mNativeObject == 0) {
+            Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack);
+            throw new NullPointerException(
+                "mNativeObject of " + this + " is null. Have you called release() already?");
+        }
     }
 
     /**
@@ -2383,6 +2411,7 @@
     public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
         long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
         SurfaceControl sc = new SurfaceControl();
+        sc.mDebugRelease = mirrorOf.mDebugRelease;
         sc.assignNativeObject(nativeObj, "mirrorSurface");
         return sc;
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6d88d63..482f2f0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -386,6 +386,16 @@
      * @hide
      */
     int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
+    /**
+     * The first slot for custom transition types. Callers (like Shell) can make use of custom
+     * transition types for dealing with special cases. These types are effectively ignored by
+     * Core and will just be passed along as part of TransitionInfo objects. An example is
+     * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type,
+     * Shell can properly dispatch the results of that transition to the split-screen
+     * implementation.
+     * @hide
+     */
+    int TRANSIT_FIRST_CUSTOM = 10;
 
     /**
      * @hide
@@ -401,6 +411,7 @@
             TRANSIT_KEYGUARD_GOING_AWAY,
             TRANSIT_KEYGUARD_OCCLUDE,
             TRANSIT_KEYGUARD_UNOCCLUDE,
+            TRANSIT_FIRST_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionType {}
diff --git a/core/java/android/webkit/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING
new file mode 100644
index 0000000..bd25200
--- /dev/null
+++ b/core/java/android/webkit/TEST_MAPPING
@@ -0,0 +1,36 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWebkitTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsHostsideWebViewTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "GtsWebViewTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.test.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "GtsWebViewHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.test.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 4ba1ca8..220a31c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -18,10 +18,13 @@
 
 import android.annotation.ColorInt;
 import android.annotation.DimenRes;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
 import android.annotation.IntDef;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.Px;
 import android.annotation.StyleRes;
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -63,12 +66,15 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
+import android.util.TypedValue;
+import android.util.TypedValue.ComplexDimensionUnit;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.LayoutInflater.Filter;
 import android.view.RemotableViewMethod;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewStub;
 import android.widget.AdapterView.OnItemClickListener;
 
@@ -173,6 +179,48 @@
     private static final int SET_INT_TAG_TAG = 22;
 
     /** @hide **/
+    @IntDef(prefix = "MARGIN_", value = {
+            MARGIN_LEFT,
+            MARGIN_TOP,
+            MARGIN_RIGHT,
+            MARGIN_BOTTOM,
+            MARGIN_START,
+            MARGIN_END
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MarginType {}
+    /**
+     * The value will apply to the marginLeft.
+     * @hide
+     */
+    public static final int MARGIN_LEFT = 0;
+    /**
+     * The value will apply to the marginTop.
+     * @hide
+     */
+    public static final int MARGIN_TOP = 1;
+    /**
+     * The value will apply to the marginRight.
+     * @hide
+     */
+    public static final int MARGIN_RIGHT = 2;
+    /**
+     * The value will apply to the marginBottom.
+     * @hide
+     */
+    public static final int MARGIN_BOTTOM = 3;
+    /**
+     * The value will apply to the marginStart.
+     * @hide
+     */
+    public static final int MARGIN_START = 4;
+    /**
+     * The value will apply to the marginEnd.
+     * @hide
+     */
+    public static final int MARGIN_END = 5;
+
+    /** @hide **/
     @IntDef(flag = true, value = {
             FLAG_REAPPLY_DISALLOWED,
             FLAG_WIDGET_IS_COLLECTION_CHILD,
@@ -283,7 +331,7 @@
     /**
      * @hide
      */
-    public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
+    public void setRemoteInputs(@IdRes int viewId, RemoteInput[] remoteInputs) {
         mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
     }
 
@@ -319,7 +367,7 @@
      *
      * @hide
      */
-    public void setIntTag(int viewId, int key, int tag) {
+    public void setIntTag(@IdRes int viewId, @IdRes int key, int tag) {
         addAction(new SetIntTagAction(viewId, key, tag));
     }
 
@@ -475,6 +523,7 @@
             // Nothing to visit by default
         }
 
+        @IdRes
         @UnsupportedAppUsage
         int viewId;
     }
@@ -599,7 +648,7 @@
     private class SetEmptyView extends Action {
         int emptyViewId;
 
-        SetEmptyView(int viewId, int emptyViewId) {
+        SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) {
             this.viewId = viewId;
             this.emptyViewId = emptyViewId;
         }
@@ -634,7 +683,7 @@
     }
 
     private class SetPendingIntentTemplate extends Action {
-        public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
+        public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) {
             this.viewId = id;
             this.pendingIntentTemplate = pendingIntentTemplate;
         }
@@ -705,7 +754,8 @@
     }
 
     private class SetRemoteViewsAdapterList extends Action {
-        public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
+        public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list,
+                int viewTypeCount) {
             this.viewId = id;
             this.list = list;
             this.viewTypeCount = viewTypeCount;
@@ -770,7 +820,7 @@
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
-        public SetRemoteViewsAdapterIntent(int id, Intent intent) {
+        public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) {
             this.viewId = id;
             this.intent = intent;
         }
@@ -845,7 +895,7 @@
      */
     private class SetOnClickResponse extends Action {
 
-        SetOnClickResponse(int id, RemoteResponse response) {
+        SetOnClickResponse(@IdRes int id, RemoteResponse response) {
             this.viewId = id;
             this.mResponse = response;
         }
@@ -1007,8 +1057,8 @@
      * <p>
      */
     private class SetDrawableTint extends Action {
-        SetDrawableTint(int id, boolean targetBackground,
-                int colorFilter, @NonNull PorterDuff.Mode mode) {
+        SetDrawableTint(@IdRes int id, boolean targetBackground,
+                @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
             this.viewId = id;
             this.targetBackground = targetBackground;
             this.colorFilter = colorFilter;
@@ -1054,7 +1104,7 @@
         }
 
         boolean targetBackground;
-        int colorFilter;
+        @ColorInt int colorFilter;
         PorterDuff.Mode filterMode;
     }
 
@@ -1071,7 +1121,7 @@
 
         ColorStateList mColorStateList;
 
-        SetRippleDrawableColor(int id, ColorStateList colorStateList) {
+        SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) {
             this.viewId = id;
             this.mColorStateList = colorStateList;
         }
@@ -1108,7 +1158,7 @@
     private final class ViewContentNavigation extends Action {
         final boolean mNext;
 
-        ViewContentNavigation(int viewId, boolean next) {
+        ViewContentNavigation(@IdRes int viewId, boolean next) {
             this.viewId = viewId;
             this.mNext = next;
         }
@@ -1205,7 +1255,7 @@
         @UnsupportedAppUsage
         String methodName;
 
-        BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
+        BitmapReflectionAction(@IdRes int viewId, String methodName, Bitmap bitmap) {
             this.bitmap = bitmap;
             this.viewId = viewId;
             this.methodName = methodName;
@@ -1274,7 +1324,7 @@
         @UnsupportedAppUsage
         Object value;
 
-        ReflectionAction(int viewId, String methodName, int type, Object value) {
+        ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
             this.viewId = viewId;
             this.methodName = methodName;
             this.type = type;
@@ -1568,11 +1618,11 @@
         private RemoteViews mNestedViews;
         private int mIndex;
 
-        ViewGroupActionAdd(int viewId, RemoteViews nestedViews) {
+        ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) {
             this(viewId, nestedViews, -1 /* index */);
         }
 
-        ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) {
+        ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) {
             this.viewId = viewId;
             mNestedViews = nestedViews;
             mIndex = index;
@@ -1682,11 +1732,11 @@
 
         private int mViewIdToKeep;
 
-        ViewGroupActionRemove(int viewId) {
+        ViewGroupActionRemove(@IdRes int viewId) {
             this(viewId, REMOVE_ALL_VIEWS_ID);
         }
 
-        ViewGroupActionRemove(int viewId, int viewIdToKeep) {
+        ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) {
             this.viewId = viewId;
             mViewIdToKeep = viewIdToKeep;
         }
@@ -1730,8 +1780,16 @@
 
             final ViewGroup targetVg = (ViewGroup) target.mRoot;
 
-            // Clear all children when nested views omitted
-            target.mChildren = null;
+            if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
+                // Clear all children when there's no excepted view
+                target.mChildren = null;
+            } else {
+                // Remove just the children which don't match the excepted view
+                target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep);
+                if (target.mChildren.isEmpty()) {
+                    target.mChildren = null;
+                }
+            }
             return new RuntimeAction() {
                 @Override
                 public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
@@ -1777,7 +1835,8 @@
      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
      */
     private class TextViewDrawableAction extends Action {
-        public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
+        public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1,
+                @DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) {
             this.viewId = viewId;
             this.isRelative = isRelative;
             this.useIcons = false;
@@ -1787,7 +1846,7 @@
             this.d4 = d4;
         }
 
-        public TextViewDrawableAction(int viewId, boolean isRelative,
+        public TextViewDrawableAction(@IdRes int viewId, boolean isRelative,
                 Icon i1, Icon i2, Icon i3, Icon i4) {
             this.viewId = viewId;
             this.isRelative = isRelative;
@@ -1922,13 +1981,13 @@
      * Helper action to set text size on a TextView in any supported units.
      */
     private class TextViewSizeAction extends Action {
-        public TextViewSizeAction(int viewId, int units, float size) {
+        TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) {
             this.viewId = viewId;
             this.units = units;
             this.size = size;
         }
 
-        public TextViewSizeAction(Parcel parcel) {
+        TextViewSizeAction(Parcel parcel) {
             viewId = parcel.readInt();
             units = parcel.readInt();
             size  = parcel.readFloat();
@@ -1960,7 +2019,8 @@
      * Helper action to set padding on a View.
      */
     private class ViewPaddingAction extends Action {
-        public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) {
+        public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top,
+                @Px int right, @Px int bottom) {
             this.viewId = viewId;
             this.left = left;
             this.top = top;
@@ -1996,7 +2056,7 @@
             return VIEW_PADDING_ACTION_TAG;
         }
 
-        int left, top, right, bottom;
+        @Px int left, top, right, bottom;
     }
 
     /**
@@ -2004,36 +2064,56 @@
      */
     private static class LayoutParamAction extends Action {
 
-        /** Set marginEnd */
-        public static final int LAYOUT_MARGIN_END_DIMEN = 1;
-        /** Set width */
-        public static final int LAYOUT_WIDTH = 2;
-        public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
-        public static final int LAYOUT_MARGIN_END = 4;
+        static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT;
+        static final int LAYOUT_MARGIN_TOP = MARGIN_TOP;
+        static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT;
+        static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM;
+        static final int LAYOUT_MARGIN_START = MARGIN_START;
+        static final int LAYOUT_MARGIN_END = MARGIN_END;
+        static final int LAYOUT_WIDTH = 8;
+        static final int LAYOUT_HEIGHT = 9;
 
         final int mProperty;
+        final boolean mIsDimen;
         final int mValue;
 
         /**
          * @param viewId ID of the view alter
          * @param property which layout parameter to alter
          * @param value new value of the layout parameter
+         * @param units the units of the given value
          */
-        public LayoutParamAction(int viewId, int property, int value) {
+        LayoutParamAction(@IdRes int viewId, int property, float value,
+                @ComplexDimensionUnit int units) {
             this.viewId = viewId;
             this.mProperty = property;
-            this.mValue = value;
+            this.mIsDimen = false;
+            this.mValue = TypedValue.createComplexDimension(value, units);
+        }
+
+        /**
+         * @param viewId ID of the view alter
+         * @param property which layout parameter to alter
+         * @param dimen new dimension with the value of the layout parameter
+         */
+        LayoutParamAction(@IdRes int viewId, int property, @DimenRes int dimen) {
+            this.viewId = viewId;
+            this.mProperty = property;
+            this.mIsDimen = true;
+            this.mValue = dimen;
         }
 
         public LayoutParamAction(Parcel parcel) {
             viewId = parcel.readInt();
             mProperty = parcel.readInt();
+            mIsDimen = parcel.readBoolean();
             mValue = parcel.readInt();
         }
 
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(viewId);
             dest.writeInt(mProperty);
+            dest.writeBoolean(mIsDimen);
             dest.writeInt(mValue);
         }
 
@@ -2047,26 +2127,49 @@
             if (layoutParams == null) {
                 return;
             }
-            int value = mValue;
             switch (mProperty) {
-                case LAYOUT_MARGIN_END_DIMEN:
-                    value = resolveDimenPixelOffset(target, mValue);
-                    // fall-through
-                case LAYOUT_MARGIN_END:
-                    if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(value);
+                case LAYOUT_MARGIN_LEFT:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target);
                         target.setLayoutParams(layoutParams);
                     }
                     break;
-                case LAYOUT_MARGIN_BOTTOM_DIMEN:
-                    if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        int resolved = resolveDimenPixelOffset(target, mValue);
-                        ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
+                case LAYOUT_MARGIN_TOP:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target);
+                        target.setLayoutParams(layoutParams);
+                    }
+                    break;
+                case LAYOUT_MARGIN_RIGHT:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target);
+                        target.setLayoutParams(layoutParams);
+                    }
+                    break;
+                case LAYOUT_MARGIN_BOTTOM:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target);
+                        target.setLayoutParams(layoutParams);
+                    }
+                    break;
+                case LAYOUT_MARGIN_START:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target));
+                        target.setLayoutParams(layoutParams);
+                    }
+                    break;
+                case LAYOUT_MARGIN_END:
+                    if (layoutParams instanceof MarginLayoutParams) {
+                        ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target));
                         target.setLayoutParams(layoutParams);
                     }
                     break;
                 case LAYOUT_WIDTH:
-                    layoutParams.width = mValue;
+                    layoutParams.width = getPixelSize(target);
+                    target.setLayoutParams(layoutParams);
+                    break;
+                case LAYOUT_HEIGHT:
+                    layoutParams.height = getPixelSize(target);
                     target.setLayoutParams(layoutParams);
                     break;
                 default:
@@ -2074,11 +2177,26 @@
             }
         }
 
-        private static int resolveDimenPixelOffset(View target, int value) {
-            if (value == 0) {
-                return 0;
+        private int getPixelOffset(View target) {
+            if (mIsDimen) {
+                if (mValue == 0) {
+                    return 0;
+                }
+                return target.getResources().getDimensionPixelOffset(mValue);
             }
-            return target.getContext().getResources().getDimensionPixelOffset(value);
+            return TypedValue.complexToDimensionPixelOffset(mValue,
+                    target.getResources().getDisplayMetrics());
+        }
+
+        private int getPixelSize(View target) {
+            if (mIsDimen) {
+                if (mValue == 0) {
+                    return 0;
+                }
+                return target.getResources().getDimensionPixelSize(mValue);
+            }
+            return TypedValue.complexToDimensionPixelSize(mValue,
+                    target.getResources().getDisplayMetrics());
         }
 
         @Override
@@ -2097,7 +2215,7 @@
      */
     private class SetRemoteInputsAction extends Action {
 
-        public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
+        public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) {
             this.viewId = viewId;
             this.remoteInputs = remoteInputs;
         }
@@ -2175,11 +2293,11 @@
     }
 
     private class SetIntTagAction extends Action {
-        private final int mViewId;
-        private final int mKey;
+        @IdRes private final int mViewId;
+        @IdRes private final int mKey;
         private final int mTag;
 
-        SetIntTagAction(int viewId, int key, int tag) {
+        SetIntTagAction(@IdRes int viewId, @IdRes int key, int tag) {
             mViewId = viewId;
             mKey = key;
             mTag = tag;
@@ -2511,7 +2629,8 @@
      * @param viewId The id of the parent {@link ViewGroup} to add child into.
      * @param nestedView {@link RemoteViews} that describes the child.
      */
-    public void addView(int viewId, RemoteViews nestedView) {
+    public void addView(@IdRes int viewId, RemoteViews nestedView) {
+        // Clear all children when nested views omitted
         addAction(nestedView == null
                 ? new ViewGroupActionRemove(viewId)
                 : new ViewGroupActionAdd(viewId, nestedView));
@@ -2528,7 +2647,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public void addView(int viewId, RemoteViews nestedView, int index) {
+    public void addView(@IdRes int viewId, RemoteViews nestedView, int index) {
         addAction(new ViewGroupActionAdd(viewId, nestedView, index));
     }
 
@@ -2538,7 +2657,7 @@
      * @param viewId The id of the parent {@link ViewGroup} to remove all
      *            children from.
      */
-    public void removeAllViews(int viewId) {
+    public void removeAllViews(@IdRes int viewId) {
         addAction(new ViewGroupActionRemove(viewId));
     }
 
@@ -2551,7 +2670,7 @@
      *
      * @hide
      */
-    public void removeAllViewsExceptId(int viewId, int viewIdToKeep) {
+    public void removeAllViewsExceptId(@IdRes int viewId, @IdRes int viewIdToKeep) {
         addAction(new ViewGroupActionRemove(viewId, viewIdToKeep));
     }
 
@@ -2560,7 +2679,7 @@
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
      */
-    public void showNext(int viewId) {
+    public void showNext(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, true /* next */));
     }
 
@@ -2569,7 +2688,7 @@
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
      */
-    public void showPrevious(int viewId) {
+    public void showPrevious(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, false /* next */));
     }
 
@@ -2579,7 +2698,7 @@
      * @param viewId The id of the view on which to call
      *               {@link AdapterViewAnimator#setDisplayedChild(int)}
      */
-    public void setDisplayedChild(int viewId, int childIndex) {
+    public void setDisplayedChild(@IdRes int viewId, int childIndex) {
         setInt(viewId, "setDisplayedChild", childIndex);
     }
 
@@ -2589,7 +2708,7 @@
      * @param viewId The id of the view whose visibility should change
      * @param visibility The new visibility for the view
      */
-    public void setViewVisibility(int viewId, int visibility) {
+    public void setViewVisibility(@IdRes int viewId, @View.Visibility int visibility) {
         setInt(viewId, "setVisibility", visibility);
     }
 
@@ -2599,7 +2718,7 @@
      * @param viewId The id of the view whose text should change
      * @param text The new text for the view
      */
-    public void setTextViewText(int viewId, CharSequence text) {
+    public void setTextViewText(@IdRes int viewId, CharSequence text) {
         setCharSequence(viewId, "setText", text);
     }
 
@@ -2610,7 +2729,7 @@
      * @param units The units of size (e.g. COMPLEX_UNIT_SP)
      * @param size The size of the text
      */
-    public void setTextViewTextSize(int viewId, int units, float size) {
+    public void setTextViewTextSize(@IdRes int viewId, int units, float size) {
         addAction(new TextViewSizeAction(viewId, units, size));
     }
 
@@ -2624,7 +2743,8 @@
      * @param right The id of a drawable to place to the right of the text, or 0
      * @param bottom The id of a drawable to place below the text, or 0
      */
-    public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
+    public void setTextViewCompoundDrawables(@IdRes int viewId, @DrawableRes int left,
+            @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
     }
 
@@ -2639,7 +2759,8 @@
      * @param end The id of a drawable to place after the text, or 0
      * @param bottom The id of a drawable to place below the text, or 0
      */
-    public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
+    public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, @DrawableRes int start,
+            @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
     }
 
@@ -2656,7 +2777,8 @@
      *
      * @hide
      */
-    public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) {
+    public void setTextViewCompoundDrawables(@IdRes int viewId,
+            Icon left, Icon top, Icon right, Icon bottom) {
         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
     }
 
@@ -2674,7 +2796,8 @@
      *
      * @hide
      */
-    public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) {
+    public void setTextViewCompoundDrawablesRelative(@IdRes int viewId,
+            Icon start, Icon top, Icon end, Icon bottom) {
         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
     }
 
@@ -2684,7 +2807,7 @@
      * @param viewId The id of the view whose drawable should change
      * @param srcId The new resource id for the drawable
      */
-    public void setImageViewResource(int viewId, int srcId) {
+    public void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId) {
         setInt(viewId, "setImageResource", srcId);
     }
 
@@ -2694,7 +2817,7 @@
      * @param viewId The id of the view whose drawable should change
      * @param uri The Uri for the image
      */
-    public void setImageViewUri(int viewId, Uri uri) {
+    public void setImageViewUri(@IdRes int viewId, Uri uri) {
         setUri(viewId, "setImageURI", uri);
     }
 
@@ -2704,7 +2827,7 @@
      * @param viewId The id of the view whose bitmap should change
      * @param bitmap The new Bitmap for the drawable
      */
-    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
+    public void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap) {
         setBitmap(viewId, "setImageBitmap", bitmap);
     }
 
@@ -2714,7 +2837,7 @@
      * @param viewId The id of the view whose bitmap should change
      * @param icon The new Icon for the ImageView
      */
-    public void setImageViewIcon(int viewId, Icon icon) {
+    public void setImageViewIcon(@IdRes int viewId, Icon icon) {
         setIcon(viewId, "setImageIcon", icon);
     }
 
@@ -2724,7 +2847,7 @@
      * @param viewId The id of the view on which to set the empty view
      * @param emptyViewId The view id of the empty view
      */
-    public void setEmptyView(int viewId, int emptyViewId) {
+    public void setEmptyView(@IdRes int viewId, @IdRes int emptyViewId) {
         addAction(new SetEmptyView(viewId, emptyViewId));
     }
 
@@ -2744,7 +2867,7 @@
      *
      * @see #setChronometerCountDown(int, boolean)
      */
-    public void setChronometer(int viewId, long base, String format, boolean started) {
+    public void setChronometer(@IdRes int viewId, long base, String format, boolean started) {
         setLong(viewId, "setBase", base);
         setString(viewId, "setFormat", format);
         setBoolean(viewId, "setStarted", started);
@@ -2758,7 +2881,7 @@
      * @param isCountDown True if you want the chronometer to count down to base instead of
      *                    counting up.
      */
-    public void setChronometerCountDown(int viewId, boolean isCountDown) {
+    public void setChronometerCountDown(@IdRes int viewId, boolean isCountDown) {
         setBoolean(viewId, "setCountDown", isCountDown);
     }
 
@@ -2775,7 +2898,7 @@
      * @param indeterminate True if the progress bar is indeterminate,
      *                false if not.
      */
-    public void setProgressBar(int viewId, int max, int progress,
+    public void setProgressBar(@IdRes int viewId, int max, int progress,
             boolean indeterminate) {
         setBoolean(viewId, "setIndeterminate", indeterminate);
         if (!indeterminate) {
@@ -2801,7 +2924,7 @@
      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
      * @param pendingIntent The {@link PendingIntent} to send when user clicks
      */
-    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
+    public void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent) {
         setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent));
     }
 
@@ -2813,7 +2936,7 @@
      * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked
      * @param response The {@link RemoteResponse} to send when user clicks
      */
-    public void setOnClickResponse(int viewId, @NonNull RemoteResponse response) {
+    public void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response) {
         addAction(new SetOnClickResponse(viewId, response));
     }
 
@@ -2829,7 +2952,7 @@
      * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
      *          by a child of viewId and executed when that child is clicked
      */
-    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
+    public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
         addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
     }
 
@@ -2850,7 +2973,7 @@
      * @param fillInIntent The intent which will be combined with the parent's PendingIntent
      *        in order to determine the on-click behavior of the view specified by viewId
      */
-    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
+    public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
         setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
     }
 
@@ -2874,8 +2997,8 @@
      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
      *            unchanged.
      */
-    public void setDrawableTint(int viewId, boolean targetBackground,
-            int colorFilter, @NonNull PorterDuff.Mode mode) {
+    public void setDrawableTint(@IdRes int viewId, boolean targetBackground,
+            @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
         addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
     }
 
@@ -2891,7 +3014,7 @@
      * @param colorStateList Specify a color for a
      *            {@link ColorStateList} for this drawable.
      */
-    public void setRippleDrawableColor(int viewId, ColorStateList colorStateList) {
+    public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) {
         addAction(new SetRippleDrawableColor(viewId, colorStateList));
     }
 
@@ -2902,7 +3025,7 @@
      * @param viewId The id of the view whose tint should change
      * @param tint the tint to apply, may be {@code null} to clear tint
      */
-    public void setProgressTintList(int viewId, ColorStateList tint) {
+    public void setProgressTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setProgressTintList",
                 ReflectionAction.COLOR_STATE_LIST, tint));
     }
@@ -2914,7 +3037,7 @@
      * @param viewId The id of the view whose tint should change
      * @param tint the tint to apply, may be {@code null} to clear tint
      */
-    public void setProgressBackgroundTintList(int viewId, ColorStateList tint) {
+    public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
                 ReflectionAction.COLOR_STATE_LIST, tint));
     }
@@ -2926,7 +3049,7 @@
      * @param viewId The id of the view whose tint should change
      * @param tint the tint to apply, may be {@code null} to clear tint
      */
-    public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) {
+    public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
                 ReflectionAction.COLOR_STATE_LIST, tint));
     }
@@ -2938,7 +3061,7 @@
      * @param color Sets the text color for all the states (normal, selected,
      *            focused) to be this color.
      */
-    public void setTextColor(int viewId, @ColorInt int color) {
+    public void setTextColor(@IdRes int viewId, @ColorInt int color) {
         setInt(viewId, "setTextColor", color);
     }
 
@@ -2949,7 +3072,7 @@
      * @param viewId The id of the view whose text color should change
      * @param colors the text colors to set
      */
-    public void setTextColor(int viewId, @ColorInt ColorStateList colors) {
+    public void setTextColor(@IdRes int viewId, ColorStateList colors) {
         addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
                 colors));
     }
@@ -2966,7 +3089,7 @@
      *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
      */
     @Deprecated
-    public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
+    public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) {
         setRemoteAdapter(viewId, intent);
     }
 
@@ -2978,7 +3101,7 @@
      * @param intent The intent of the service which will be
      *            providing data to the RemoteViewsAdapter
      */
-    public void setRemoteAdapter(int viewId, Intent intent) {
+    public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
     }
 
@@ -3006,7 +3129,8 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Deprecated
-    public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
+    public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list,
+            int viewTypeCount) {
         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
     }
 
@@ -3016,7 +3140,7 @@
      * @param viewId The id of the view to change
      * @param position Scroll to this adapter position
      */
-    public void setScrollPosition(int viewId, int position) {
+    public void setScrollPosition(@IdRes int viewId, int position) {
         setInt(viewId, "smoothScrollToPosition", position);
     }
 
@@ -3026,7 +3150,7 @@
      * @param viewId The id of the view to change
      * @param offset Scroll by this adapter position offset
      */
-    public void setRelativeScrollPosition(int viewId, int offset) {
+    public void setRelativeScrollPosition(@IdRes int viewId, int offset) {
         setInt(viewId, "smoothScrollByOffset", offset);
     }
 
@@ -3039,62 +3163,100 @@
      * @param right the right padding in pixels
      * @param bottom the bottom padding in pixels
      */
-    public void setViewPadding(int viewId, int left, int top, int right, int bottom) {
+    public void setViewPadding(@IdRes int viewId,
+            @Px int left, @Px int top, @Px int right, @Px int bottom) {
         addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
     }
 
     /**
-     * @hide
-     * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
+     * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}.
      * Only works if the {@link View#getLayoutParams()} supports margins.
-     * Hidden for now since we don't want to support this for all different layout margins yet.
      *
      * @param viewId The id of the view to change
-     * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
+     * @param type The margin being set e.g. {@link #MARGIN_END}
+     * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin.
+     * @hide
      */
-    public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) {
-        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN,
-                endMarginDimen));
+    public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type,
+            @DimenRes int dimen) {
+        addAction(new LayoutParamAction(viewId, type, dimen));
     }
 
     /**
-     * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
+     * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}.
      * Only works if the {@link View#getLayoutParams()} supports margins.
-     * Hidden for now since we don't want to support this for all different layout margins yet.
+     *
+     * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
+     * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
+     * display with a different density.
      *
      * @param viewId The id of the view to change
-     * @param endMargin a value in pixels for the end margin.
+     * @param type The margin being set e.g. {@link #MARGIN_END}
+     * @param value a value for the margin the given units.
+     * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
      * @hide
      */
-    public void setViewLayoutMarginEnd(int viewId, @DimenRes int endMargin) {
-        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END,
-                endMargin));
+    public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value,
+            @ComplexDimensionUnit int units) {
+        addAction(new LayoutParamAction(viewId, type, value, units));
     }
 
     /**
-     * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}.
+     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may
+     * provide the value in any dimension units.
      *
-     * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
+     * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
+     * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
+     * display with a different density.
+     *
+     * @param width Width of the view in the given units
+     * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
      * @hide
      */
-    public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) {
-        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN,
-                bottomMarginDimen));
+    public void setViewLayoutWidth(@IdRes int viewId, float width,
+            @ComplexDimensionUnit int units) {
+        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units));
     }
 
     /**
-     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}.
+     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with
+     * the result of {@link Resources#getDimensionPixelSize(int)}.
      *
-     * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed
-     *                    because they behave poorly when the density changes.
+     * @param widthDimen the dimension resource for the view's width
      * @hide
      */
-    public void setViewLayoutWidth(int viewId, int layoutWidth) {
-        if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT
-                && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) {
-            throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT");
-        }
-        mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth));
+    public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) {
+        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen));
+    }
+
+    /**
+     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may
+     * provide the value in any dimension units.
+     *
+     * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
+     * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
+     * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
+     * display with a different density.
+     *
+     * @param height height of the view in the given units
+     * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
+     * @hide
+     */
+    public void setViewLayoutHeight(@IdRes int viewId, float height,
+            @ComplexDimensionUnit int units) {
+        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units));
+    }
+
+    /**
+     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with
+     * the result of {@link Resources#getDimensionPixelSize(int)}.
+     *
+     * @param heightDimen a dimen resource to read the height from.
+     * @hide
+     */
+    public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) {
+        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen));
     }
 
     /**
@@ -3104,7 +3266,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setBoolean(int viewId, String methodName, boolean value) {
+    public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
     }
 
@@ -3115,7 +3277,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setByte(int viewId, String methodName, byte value) {
+    public void setByte(@IdRes int viewId, String methodName, byte value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
     }
 
@@ -3126,7 +3288,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setShort(int viewId, String methodName, short value) {
+    public void setShort(@IdRes int viewId, String methodName, short value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
     }
 
@@ -3137,7 +3299,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setInt(int viewId, String methodName, int value) {
+    public void setInt(@IdRes int viewId, String methodName, int value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
     }
 
@@ -3150,7 +3312,7 @@
      *
      * @hide
      */
-    public void setColorStateList(int viewId, String methodName, ColorStateList value) {
+    public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST,
                 value));
     }
@@ -3163,7 +3325,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setLong(int viewId, String methodName, long value) {
+    public void setLong(@IdRes int viewId, String methodName, long value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
     }
 
@@ -3174,7 +3336,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setFloat(int viewId, String methodName, float value) {
+    public void setFloat(@IdRes int viewId, String methodName, float value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
     }
 
@@ -3185,7 +3347,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setDouble(int viewId, String methodName, double value) {
+    public void setDouble(@IdRes int viewId, String methodName, double value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
     }
 
@@ -3196,7 +3358,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setChar(int viewId, String methodName, char value) {
+    public void setChar(@IdRes int viewId, String methodName, char value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
     }
 
@@ -3207,7 +3369,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setString(int viewId, String methodName, String value) {
+    public void setString(@IdRes int viewId, String methodName, String value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
     }
 
@@ -3218,7 +3380,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setCharSequence(int viewId, String methodName, CharSequence value) {
+    public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
     }
 
@@ -3229,7 +3391,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setUri(int viewId, String methodName, Uri value) {
+    public void setUri(@IdRes int viewId, String methodName, Uri value) {
         if (value != null) {
             // Resolve any filesystem path before sending remotely
             value = value.getCanonicalUri();
@@ -3250,7 +3412,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setBitmap(int viewId, String methodName, Bitmap value) {
+    public void setBitmap(@IdRes int viewId, String methodName, Bitmap value) {
         addAction(new BitmapReflectionAction(viewId, methodName, value));
     }
 
@@ -3261,7 +3423,7 @@
      * @param methodName The name of the method to call.
      * @param value The value to pass to the method.
      */
-    public void setBundle(int viewId, String methodName, Bundle value) {
+    public void setBundle(@IdRes int viewId, String methodName, Bundle value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
     }
 
@@ -3272,7 +3434,7 @@
      * @param methodName The name of the method to call.
      * @param value The {@link android.content.Intent} to pass the method.
      */
-    public void setIntent(int viewId, String methodName, Intent value) {
+    public void setIntent(@IdRes int viewId, String methodName, Intent value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
     }
 
@@ -3283,7 +3445,7 @@
      * @param methodName The name of the method to call.
      * @param value The {@link android.graphics.drawable.Icon} to pass the method.
      */
-    public void setIcon(int viewId, String methodName, Icon value) {
+    public void setIcon(@IdRes int viewId, String methodName, Icon value) {
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value));
     }
 
@@ -3293,7 +3455,7 @@
      * @param viewId The id of the view whose content description should change.
      * @param contentDescription The new content description for the view.
      */
-    public void setContentDescription(int viewId, CharSequence contentDescription) {
+    public void setContentDescription(@IdRes int viewId, CharSequence contentDescription) {
         setCharSequence(viewId, "setContentDescription", contentDescription);
     }
 
@@ -3303,7 +3465,7 @@
      * @param viewId The id of the view whose before view in accessibility traversal to set.
      * @param nextId The id of the next in the accessibility traversal.
      **/
-    public void setAccessibilityTraversalBefore(int viewId, int nextId) {
+    public void setAccessibilityTraversalBefore(@IdRes int viewId, @IdRes int nextId) {
         setInt(viewId, "setAccessibilityTraversalBefore", nextId);
     }
 
@@ -3313,7 +3475,7 @@
      * @param viewId The id of the view whose after view in accessibility traversal to set.
      * @param nextId The id of the next in the accessibility traversal.
      **/
-    public void setAccessibilityTraversalAfter(int viewId, int nextId) {
+    public void setAccessibilityTraversalAfter(@IdRes int viewId, @IdRes int nextId) {
         setInt(viewId, "setAccessibilityTraversalAfter", nextId);
     }
 
@@ -3323,7 +3485,7 @@
      * @param viewId The id of the view whose property to set.
      * @param labeledId The id of a view for which this view serves as a label.
      */
-    public void setLabelFor(int viewId, int labeledId) {
+    public void setLabelFor(@IdRes int viewId, @IdRes int labeledId) {
         setInt(viewId, "setLabelFor", labeledId);
     }
 
@@ -3847,7 +4009,7 @@
             }
         }
 
-        public ViewTree findViewTreeById(int id) {
+        public ViewTree findViewTreeById(@IdRes int id) {
             if (mRoot.getId() == id) {
                 return this;
             }
@@ -3869,7 +4031,7 @@
             createTree();
         }
 
-        public <T extends View> T findViewById(int id) {
+        public <T extends View> T findViewById(@IdRes int id) {
             if (mChildren == null) {
                 return mRoot.findViewById(id);
             }
@@ -4003,7 +4165,8 @@
          * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
          */
         @NonNull
-        public RemoteResponse addSharedElement(int viewId, @NonNull String sharedElementName) {
+        public RemoteResponse addSharedElement(@IdRes int viewId,
+                @NonNull String sharedElementName) {
             if (mViewIds == null) {
                 mViewIds = new IntArray();
                 mElementNames = new ArrayList<>();
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 1254647..8784399 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -92,6 +92,14 @@
     public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;
 
     /**
+     * Display area for one handed background layer, which preventing when user
+     * turning the Dark theme on, they can not clearly identify the screen has entered
+     * one handed mode.
+     * @hide
+     */
+    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;
+
+    /**
      * The last boundary of display area for system features
      */
     public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl
index a8a29b2..55d47cb 100644
--- a/core/java/android/window/ITransitionPlayer.aidl
+++ b/core/java/android/window/ITransitionPlayer.aidl
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.app.ActivityManager;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.WindowContainerTransaction;
@@ -58,6 +59,9 @@
      * @param type The {@link WindowManager#TransitionType} of the transition to start.
      * @param transitionToken An identifying token for the transition that needs to be started.
      *                        Pass this to {@link IWindowOrganizerController#startTransition}.
+     * @param triggerTask If non-null, the task containing the activity whose lifecycle change
+     *                    (start or finish) has caused this transition to occur.
      */
-    void requestStartTransition(int type, in IBinder transitionToken);
+    void requestStartTransition(int type, in IBinder transitionToken,
+            in ActivityManager.RunningTaskInfo triggerTask);
 }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index da291cf..d1d49b6 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -26,6 +26,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -153,7 +154,8 @@
     /**
      * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
      * participants to animate within. This will generally be placed at the highest-z-order
-     * shared ancestor of all participants.
+     * shared ancestor of all participants. While this is non-null, it's possible for the rootleash
+     * to be invalid if the transition is a no-op.
      */
     @NonNull
     public SurfaceControl getRootLeash() {
@@ -181,7 +183,7 @@
     @Nullable
     public Change getChange(@NonNull WindowContainerToken token) {
         for (int i = mChanges.size() - 1; i >= 0; --i) {
-            if (mChanges.get(i).mContainer == token) {
+            if (token.equals(mChanges.get(i).mContainer)) {
                 return mChanges.get(i);
             }
         }
@@ -254,6 +256,7 @@
         private final Rect mStartAbsBounds = new Rect();
         private final Rect mEndAbsBounds = new Rect();
         private final Point mEndRelOffset = new Point();
+        private ActivityManager.RunningTaskInfo mTaskInfo = null;
 
         public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
             mContainer = container;
@@ -270,6 +273,7 @@
             mStartAbsBounds.readFromParcel(in);
             mEndAbsBounds.readFromParcel(in);
             mEndRelOffset.readFromParcel(in);
+            mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
         }
 
         /** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -302,6 +306,14 @@
             mEndRelOffset.set(left, top);
         }
 
+        /**
+         * Sets the taskinfo of this container if this is a task. WARNING: this takes the
+         * reference, so don't modify it afterwards.
+         */
+        public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
+            mTaskInfo = taskInfo;
+        }
+
         /** @return the container that is changing. May be null if non-remotable (eg. activity) */
         @Nullable
         public WindowContainerToken getContainer() {
@@ -359,6 +371,12 @@
             return mLeash;
         }
 
+        /** @return the task info or null if this isn't a task */
+        @NonNull
+        public ActivityManager.RunningTaskInfo getTaskInfo() {
+            return mTaskInfo;
+        }
+
         @Override
         /** @hide */
         public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -370,6 +388,7 @@
             mStartAbsBounds.writeToParcel(dest, flags);
             mEndAbsBounds.writeToParcel(dest, flags);
             mEndRelOffset.writeToParcel(dest, flags);
+            dest.writeTypedObject(mTaskInfo, flags);
         }
 
         @NonNull
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 2dd51b4..f7fad2c 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -143,10 +143,6 @@
      */
     public void removeUid(int uid) {
         mLastTimes.delete(uid);
-
-        if (mBpfTimesAvailable) {
-            mBpfReader.removeUidsInRange(uid, uid);
-        }
     }
 
     /**
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c05e7a2..8c9da66 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -308,6 +308,11 @@
         linux_glibc: {
             srcs: [
                 "android_content_res_ApkAssets.cpp",
+                "android_database_CursorWindow.cpp",
+                "android_database_SQLiteCommon.cpp",
+                "android_database_SQLiteConnection.cpp",
+                "android_database_SQLiteGlobal.cpp",
+                "android_database_SQLiteDebug.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
@@ -331,6 +336,7 @@
             static_libs: [
                 "libinput",
                 "libbinderthreadstateutils",
+                "libsqlite",
             ],
             shared_libs: [
                 // libbinder needs to be shared since it has global state
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 4e50f87..3e513df 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -41,6 +41,10 @@
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
 extern int register_android_content_res_ApkAssets(JNIEnv* env);
+extern int register_android_database_CursorWindow(JNIEnv* env);
+extern int register_android_database_SQLiteConnection(JNIEnv* env);
+extern int register_android_database_SQLiteGlobal(JNIEnv* env);
+extern int register_android_database_SQLiteDebug(JNIEnv* env);
 extern int register_android_os_FileObserver(JNIEnv* env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
@@ -65,6 +69,11 @@
 #ifdef __linux__
         {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
         {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+        {"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
+        {"android.database.sqlite.SQLiteConnection",
+         REG_JNI(register_android_database_SQLiteConnection)},
+        {"android.database.sqlite.SQLiteGlobal", REG_JNI(register_android_database_SQLiteGlobal)},
+        {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
 #endif
         {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
         {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3156f71..efede21 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -843,7 +843,7 @@
   PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
              multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
 
-  bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
+  bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
 
   if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
       const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index 07e938d..401e003 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -25,11 +25,29 @@
 message SensorPrivacyServiceDumpProto {
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
+    // DEPRECATED
     // Is global sensor privacy enabled
     optional bool is_enabled = 1;
 
+    // DEPRECATED
     // Per sensor privacy enabled
     repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 2;
+
+    // Per user settings for sensor privacy
+    repeated SensorPrivacyUserProto user = 3;
+}
+
+message SensorPrivacyUserProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // User id
+    optional int32 user_id = 1;
+
+    // Is global sensor privacy enabled
+    optional bool is_enabled = 2;
+
+    // Per sensor privacy enabled
+    repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3;
 }
 
 message SensorPrivacyIndividualEnabledSensorProto {
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5b19a48..c326a40 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -66,6 +66,10 @@
     <!-- The height of the navigation gesture area if the gesture is starting from the bottom. -->
     <dimen name="navigation_bar_gesture_height">@dimen/navigation_bar_frame_height</dimen>
 
+    <!-- The height of the navigation larger gesture area if the gesture is starting from
+         the bottom. -->
+    <dimen name="navigation_bar_gesture_larger_height">80dp</dimen>
+
     <!-- Height of the bottom navigation / system bar in car mode. -->
     <dimen name="navigation_bar_height_car_mode">96dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fa66451..2df9684 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1702,6 +1702,7 @@
   <java-symbol type="dimen" name="navigation_bar_frame_height" />
   <java-symbol type="dimen" name="navigation_bar_frame_height_landscape" />
   <java-symbol type="dimen" name="navigation_bar_gesture_height" />
+  <java-symbol type="dimen" name="navigation_bar_gesture_larger_height" />
   <java-symbol type="dimen" name="navigation_bar_height_car_mode" />
   <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" />
   <java-symbol type="dimen" name="navigation_bar_width_car_mode" />
diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt
new file mode 100644
index 0000000..7a05d97
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TypedValueTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.util
+
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import kotlin.math.abs
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+@RunWith(AndroidJUnit4::class)
+class TypedValueTest {
+    @LargeTest
+    @Test
+    fun testFloatToComplex() {
+        fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) {
+            val complex = TypedValue.floatToComplex(value)
+            // Ensure values are accurate within .5% of the original value and within .5
+            val delta = min(abs(value) / 512f, .5f)
+            assertEquals(value, TypedValue.complexToFloat(complex), delta)
+            // If expectedRadix is provided, validate it
+            if (expectedRadix != null) {
+                val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT)
+                        and TypedValue.COMPLEX_RADIX_MASK)
+                assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix)
+            }
+        }
+
+        assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0)
+
+        assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23)
+
+        assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5
+
+        assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5
+        assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7)
+        assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7)
+        assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5
+
+        assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5
+        assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0)
+        assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0)
+        assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8
+
+        assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23)
+        assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23)
+
+        assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15)
+        assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5
+
+        // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for
+        // simplicity of the algorithm.  However, it's better not to enforce that with a test.
+        assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5
+        assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7)
+        assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7)
+        assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5
+
+        // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for
+        // simplicity of the algorithm.  However, it's better not to enforce that with a test.
+        assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5
+        assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0)
+        assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0)
+        assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5
+
+        // Test for every integer value in the range...
+        for (i: Int in -(1 shl 23) until (1 shl 23)) {
+            // ... that true integers are stored as the precise integer
+            assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0)
+            // ... that values round up when just below an integer
+            assertRoundTripEquals(i - .1f)
+            // ... that values round down when just above an integer
+            assertRoundTripEquals(i + .1f)
+        }
+    }
+
+    @SmallTest
+    @Test(expected = IllegalArgumentException::class)
+    fun testFloatToComplex_failsIfValueTooLarge() {
+        TypedValue.floatToComplex(8388607.5f) // 2^23 - .5
+    }
+
+    @SmallTest
+    @Test(expected = IllegalArgumentException::class)
+    fun testFloatToComplex_failsIfValueTooSmall() {
+        TypedValue.floatToComplex(8388608.5f) // -2^23 - .5
+    }
+
+    @LargeTest
+    @Test
+    fun testIntToComplex() {
+        // Validates every single valid value
+        for (value: Int in -(1 shl 23) until (1 shl 23)) {
+            assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value)))
+        }
+    }
+
+    @SmallTest
+    @Test(expected = IllegalArgumentException::class)
+    fun testIntToComplex_failsIfValueTooLarge() {
+        TypedValue.intToComplex(0x800000)
+    }
+
+    @SmallTest
+    @Test(expected = IllegalArgumentException::class)
+    fun testIntToComplex_failsIfValueTooSmall() {
+        TypedValue.intToComplex(-0x800001)
+    }
+
+    @SmallTest
+    @Test
+    fun testCreateComplexDimension_appliesUnits() {
+        val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
+        metrics.density = 3.25f
+
+        val height = 52 * metrics.density
+        val widthFloat = height * 16 / 9
+        val widthDimen = TypedValue.createComplexDimension(
+                widthFloat / metrics.density,
+                TypedValue.COMPLEX_UNIT_DIP
+        )
+        val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics)
+        assertEquals(widthFloat.roundToInt(), widthPx)
+    }
+}
\ No newline at end of file
diff --git a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java
index 6df1c3e..c01bb75 100644
--- a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java
+++ b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java
@@ -45,14 +45,14 @@
     private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class);
     private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
     private static final PersistableBundle PARAMS = new PersistableBundle();
-    private static final @CloseReason int CLOSE_REASON = CloseReason.UNKNOWN;
+    private static final @RangingChangeReason int REASON = RangingChangeReason.UNKNOWN;
 
     @Test
-    public void testOpenSession_StartRangingInvoked() throws RemoteException {
+    public void testOpenSession_OpenRangingInvoked() throws RemoteException {
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
         rangingManager.openSession(PARAMS, EXECUTOR, callback);
-        verify(ADAPTER, times(1)).startRanging(eq(rangingManager), eq(PARAMS));
+        verify(ADAPTER, times(1)).openRanging(eq(rangingManager), eq(PARAMS));
     }
 
     @Test
@@ -60,7 +60,7 @@
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
         SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+        when(ADAPTER.openRanging(any(), any())).thenReturn(handle);
 
         rangingManager.openSession(PARAMS, EXECUTOR, callback);
 
@@ -73,34 +73,34 @@
     }
 
     @Test
-    public void testOnRangingStarted_ValidSessionHandle() throws RemoteException {
+    public void testOnRangingOpened_ValidSessionHandle() throws RemoteException {
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
         SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+        when(ADAPTER.openRanging(any(), any())).thenReturn(handle);
 
         rangingManager.openSession(PARAMS, EXECUTOR, callback);
-        rangingManager.onRangingStarted(handle, PARAMS);
-        verify(callback, times(1)).onOpenSuccess(any(), any());
+        rangingManager.onRangingOpened(handle);
+        verify(callback, times(1)).onOpened(any());
     }
 
     @Test
-    public void testOnRangingStarted_InvalidSessionHandle() throws RemoteException {
+    public void testOnRangingOpened_InvalidSessionHandle() throws RemoteException {
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
 
-        rangingManager.onRangingStarted(new SessionHandle(2), PARAMS);
-        verify(callback, times(0)).onOpenSuccess(any(), any());
+        rangingManager.onRangingOpened(new SessionHandle(2));
+        verify(callback, times(0)).onOpened(any());
     }
 
     @Test
-    public void testOnRangingStarted_MultipleSessionsRegistered() throws RemoteException {
+    public void testOnRangingOpened_MultipleSessionsRegistered() throws RemoteException {
         SessionHandle sessionHandle1 = new SessionHandle(1);
         SessionHandle sessionHandle2 = new SessionHandle(2);
         RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
         RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
 
-        when(ADAPTER.startRanging(any(), any()))
+        when(ADAPTER.openRanging(any(), any()))
                 .thenReturn(sessionHandle1)
                 .thenReturn(sessionHandle2);
 
@@ -108,25 +108,50 @@
         rangingManager.openSession(PARAMS, EXECUTOR, callback1);
         rangingManager.openSession(PARAMS, EXECUTOR, callback2);
 
-        rangingManager.onRangingStarted(sessionHandle1, PARAMS);
-        verify(callback1, times(1)).onOpenSuccess(any(), any());
-        verify(callback2, times(0)).onOpenSuccess(any(), any());
+        rangingManager.onRangingOpened(sessionHandle1);
+        verify(callback1, times(1)).onOpened(any());
+        verify(callback2, times(0)).onOpened(any());
 
-        rangingManager.onRangingStarted(sessionHandle2, PARAMS);
-        verify(callback1, times(1)).onOpenSuccess(any(), any());
-        verify(callback2, times(1)).onOpenSuccess(any(), any());
+        rangingManager.onRangingOpened(sessionHandle2);
+        verify(callback1, times(1)).onOpened(any());
+        verify(callback2, times(1)).onOpened(any());
     }
 
     @Test
-    public void testOnRangingClosed_OnRangingClosedCalled() throws RemoteException {
+    public void testCorrectCallbackInvoked() throws RemoteException {
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
         SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
-        rangingManager.openSession(PARAMS, EXECUTOR, callback);
+        when(ADAPTER.openRanging(any(), any())).thenReturn(handle);
 
-        rangingManager.onRangingClosed(handle, CLOSE_REASON, PARAMS);
-        verify(callback, times(1)).onClosed(anyInt(), any());
+        rangingManager.openSession(PARAMS, EXECUTOR, callback);
+        rangingManager.onRangingOpened(handle);
+        verify(callback, times(1)).onOpened(any());
+
+        rangingManager.onRangingStarted(handle, PARAMS);
+        verify(callback, times(1)).onStarted(eq(PARAMS));
+
+        rangingManager.onRangingStartFailed(handle, REASON, PARAMS);
+        verify(callback, times(1)).onStartFailed(eq(REASON), eq(PARAMS));
+
+        RangingReport report = UwbTestUtils.getRangingReports(1);
+        rangingManager.onRangingResult(handle, report);
+        verify(callback, times(1)).onReportReceived(eq(report));
+
+        rangingManager.onRangingReconfigured(handle, PARAMS);
+        verify(callback, times(1)).onReconfigured(eq(PARAMS));
+
+        rangingManager.onRangingReconfigureFailed(handle, REASON, PARAMS);
+        verify(callback, times(1)).onReconfigureFailed(eq(REASON), eq(PARAMS));
+
+        rangingManager.onRangingStopped(handle);
+        verify(callback, times(1)).onStopped();
+
+        rangingManager.onRangingStopFailed(handle, REASON, PARAMS);
+        verify(callback, times(1)).onStopFailed(eq(REASON), eq(PARAMS));
+
+        rangingManager.onRangingClosed(handle, REASON, PARAMS);
+        verify(callback, times(1)).onClosed(eq(REASON), eq(PARAMS));
     }
 
     @Test
@@ -138,7 +163,7 @@
         RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
         RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
 
-        when(ADAPTER.startRanging(any(), any()))
+        when(ADAPTER.openRanging(any(), any()))
                 .thenReturn(sessionHandle1)
                 .thenReturn(sessionHandle2);
 
@@ -146,37 +171,23 @@
         rangingManager.openSession(PARAMS, EXECUTOR, callback1);
         rangingManager.openSession(PARAMS, EXECUTOR, callback2);
 
-        rangingManager.onRangingClosed(sessionHandle1, CLOSE_REASON, PARAMS);
+        rangingManager.onRangingClosed(sessionHandle1, REASON, PARAMS);
         verify(callback1, times(1)).onClosed(anyInt(), any());
         verify(callback2, times(0)).onClosed(anyInt(), any());
 
-        rangingManager.onRangingClosed(sessionHandle2, CLOSE_REASON, PARAMS);
+        rangingManager.onRangingClosed(sessionHandle2, REASON, PARAMS);
         verify(callback1, times(1)).onClosed(anyInt(), any());
         verify(callback2, times(1)).onClosed(anyInt(), any());
     }
 
     @Test
-    public void testOnRangingReport_OnReportReceived() throws RemoteException {
-        RangingManager rangingManager = new RangingManager(ADAPTER);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
-        rangingManager.openSession(PARAMS, EXECUTOR, callback);
-        rangingManager.onRangingStarted(handle, PARAMS);
-
-        RangingReport report = UwbTestUtils.getRangingReports(1);
-        rangingManager.onRangingResult(handle, report);
-        verify(callback, times(1)).onReportReceived(eq(report));
-    }
-
-    @Test
     public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException {
         SessionHandle sessionHandle1 = new SessionHandle(1);
         SessionHandle sessionHandle2 = new SessionHandle(2);
         RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
         RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
 
-        when(ADAPTER.startRanging(any(), any()))
+        when(ADAPTER.openRanging(any(), any()))
                 .thenReturn(sessionHandle1)
                 .thenReturn(sessionHandle2);
 
@@ -196,65 +207,54 @@
     }
 
     @Test
-    public void testOnClose_Reasons() throws RemoteException {
-        runOnClose_Reason(CloseReason.LOCAL_API,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API);
+    public void testReasons() throws RemoteException {
+        runReason(RangingChangeReason.LOCAL_API,
+                RangingSession.Callback.REASON_LOCAL_REQUEST);
 
-        runOnClose_Reason(CloseReason.MAX_SESSIONS_REACHED,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);
+        runReason(RangingChangeReason.MAX_SESSIONS_REACHED,
+                RangingSession.Callback.REASON_MAX_SESSIONS_REACHED);
 
-        runOnClose_Reason(CloseReason.PROTOCOL_SPECIFIC,
-                RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);
+        runReason(RangingChangeReason.PROTOCOL_SPECIFIC,
+                RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR);
 
-        runOnClose_Reason(CloseReason.REMOTE_REQUEST,
-                RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST);
+        runReason(RangingChangeReason.REMOTE_REQUEST,
+                RangingSession.Callback.REASON_REMOTE_REQUEST);
 
-        runOnClose_Reason(CloseReason.SYSTEM_POLICY,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);
+        runReason(RangingChangeReason.SYSTEM_POLICY,
+                RangingSession.Callback.REASON_SYSTEM_POLICY);
 
-        runOnClose_Reason(CloseReason.UNKNOWN,
-                RangingSession.Callback.CLOSE_REASON_UNKNOWN);
+        runReason(RangingChangeReason.BAD_PARAMETERS,
+                RangingSession.Callback.REASON_BAD_PARAMETERS);
+
+        runReason(RangingChangeReason.UNKNOWN,
+                RangingSession.Callback.REASON_UNKNOWN);
     }
 
-    private void runOnClose_Reason(@CloseReason int reasonIn,
-            @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
+    private void runReason(@RangingChangeReason int reasonIn,
+            @RangingSession.Callback.Reason int reasonOut) throws RemoteException {
         RangingManager rangingManager = new RangingManager(ADAPTER);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
         SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+        when(ADAPTER.openRanging(any(), any())).thenReturn(handle);
         rangingManager.openSession(PARAMS, EXECUTOR, callback);
 
-        rangingManager.onRangingClosed(handle, reasonIn, PARAMS);
-        verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
-    }
+        rangingManager.onRangingOpenFailed(handle, reasonIn, PARAMS);
+        verify(callback, times(1)).onOpenFailed(eq(reasonOut), eq(PARAMS));
 
-    @Test
-    public void testStartFailureReasons() throws RemoteException {
-        runOnRangingStartFailed_Reason(StartFailureReason.BAD_PARAMETERS,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS);
-
-        runOnRangingStartFailed_Reason(StartFailureReason.MAX_SESSIONS_REACHED,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);
-
-        runOnRangingStartFailed_Reason(StartFailureReason.PROTOCOL_SPECIFIC,
-                RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);
-
-        runOnRangingStartFailed_Reason(StartFailureReason.SYSTEM_POLICY,
-                RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);
-
-        runOnRangingStartFailed_Reason(StartFailureReason.UNKNOWN,
-                RangingSession.Callback.CLOSE_REASON_UNKNOWN);
-    }
-
-    private void runOnRangingStartFailed_Reason(@StartFailureReason int reasonIn,
-            @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
-        RangingManager rangingManager = new RangingManager(ADAPTER);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        SessionHandle handle = new SessionHandle(1);
-        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+        // Open a new session
         rangingManager.openSession(PARAMS, EXECUTOR, callback);
+        rangingManager.onRangingOpened(handle);
 
         rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS);
+        verify(callback, times(1)).onStartFailed(eq(reasonOut), eq(PARAMS));
+
+        rangingManager.onRangingReconfigureFailed(handle, reasonIn, PARAMS);
+        verify(callback, times(1)).onReconfigureFailed(eq(reasonOut), eq(PARAMS));
+
+        rangingManager.onRangingStopFailed(handle, reasonIn, PARAMS);
+        verify(callback, times(1)).onStopFailed(eq(reasonOut), eq(PARAMS));
+
+        rangingManager.onRangingClosed(handle, reasonIn, PARAMS);
         verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
     }
 }
diff --git a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
index 702c68e..e5eea26 100644
--- a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
+++ b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
@@ -19,9 +19,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,6 +36,8 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.concurrent.Executor;
 
@@ -43,47 +47,48 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RangingSessionTest {
-    private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class);
     private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
     private static final PersistableBundle PARAMS = new PersistableBundle();
-    private static final @RangingSession.Callback.CloseReason int CLOSE_REASON =
-            RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR;
+    private static final @RangingSession.Callback.Reason int REASON =
+            RangingSession.Callback.REASON_GENERIC_ERROR;
 
     @Test
-    public void testOnRangingStarted_OnOpenSuccessCalled() {
+    public void testOnRangingOpened_OnOpenSuccessCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
-        session.onRangingStarted(PARAMS);
+        session.onRangingOpened();
         verifyOpenState(session, true);
 
         // Verify that the onOpenSuccess callback was invoked
-        verify(callback, times(1)).onOpenSuccess(eq(session), any());
+        verify(callback, times(1)).onOpened(eq(session));
         verify(callback, times(0)).onClosed(anyInt(), any());
     }
 
     @Test
-    public void testOnRangingStarted_CannotOpenClosedSession() {
+    public void testOnRangingOpened_CannotOpenClosedSession() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
-        session.onRangingStarted(PARAMS);
+        session.onRangingOpened();
         verifyOpenState(session, true);
-        verify(callback, times(1)).onOpenSuccess(eq(session), any());
+        verify(callback, times(1)).onOpened(eq(session));
         verify(callback, times(0)).onClosed(anyInt(), any());
 
-        session.onRangingClosed(CLOSE_REASON, PARAMS);
+        session.onRangingClosed(REASON, PARAMS);
         verifyOpenState(session, false);
-        verify(callback, times(1)).onOpenSuccess(eq(session), any());
+        verify(callback, times(1)).onOpened(eq(session));
         verify(callback, times(1)).onClosed(anyInt(), any());
 
         // Now invoke the ranging started callback and ensure the session remains closed
-        session.onRangingStarted(PARAMS);
+        session.onRangingOpened();
         verifyOpenState(session, false);
-        verify(callback, times(1)).onOpenSuccess(eq(session), any());
+        verify(callback, times(1)).onOpened(eq(session));
         verify(callback, times(1)).onClosed(anyInt(), any());
     }
 
@@ -91,27 +96,30 @@
     public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
-        session.onRangingClosed(CLOSE_REASON, PARAMS);
+        session.onRangingClosed(REASON, PARAMS);
         verifyOpenState(session, false);
 
         // Verify that the onOpenSuccess callback was invoked
-        verify(callback, times(0)).onOpenSuccess(eq(session), any());
+        verify(callback, times(0)).onOpened(eq(session));
         verify(callback, times(1)).onClosed(anyInt(), any());
     }
 
-    @Test public void testOnRangingClosed_OnClosedCalled() {
+    @Test
+    public void testOnRangingClosed_OnClosedCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         session.onRangingStarted(PARAMS);
-        session.onRangingClosed(CLOSE_REASON, PARAMS);
+        session.onRangingClosed(REASON, PARAMS);
         verify(callback, times(1)).onClosed(anyInt(), any());
 
         verifyOpenState(session, false);
-        session.onRangingClosed(CLOSE_REASON, PARAMS);
+        session.onRangingClosed(REASON, PARAMS);
         verify(callback, times(2)).onClosed(anyInt(), any());
     }
 
@@ -119,7 +127,8 @@
     public void testOnRangingResult_OnReportReceivedCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
         session.onRangingStarted(PARAMS);
@@ -131,11 +140,83 @@
     }
 
     @Test
-    public void testClose() throws RemoteException {
+    public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        session.onRangingOpened();
+
+        session.start(PARAMS);
+        verify(callback, times(1)).onStarted(any());
+
+        // Calling start again should throw an illegal state
+        verifyThrowIllegalState(() -> session.start(PARAMS));
+        verify(callback, times(1)).onStarted(any());
+    }
+
+    @Test
+    public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
+        session.onRangingOpened();
+        session.start(PARAMS);
+
+        verifyNoThrowIllegalState(session::stop);
+        verify(callback, times(1)).onStopped();
+
+        // Calling stop again should throw an illegal state
+        verifyThrowIllegalState(session::stop);
+        verify(callback, times(1)).onStopped();
+    }
+
+    @Test
+    public void testReconfigure_OnlyWhenOpened() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
+
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(0)).onReconfigured(any());
+        verifyOpenState(session, false);
+
+        session.onRangingOpened();
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(1)).onReconfigured(any());
+        verifyOpenState(session, true);
+
         session.onRangingStarted(PARAMS);
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(2)).onReconfigured(any());
+        verifyOpenState(session, true);
+
+        session.onRangingStopped();
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(3)).onReconfigured(any());
+        verifyOpenState(session, true);
+
+
+        session.onRangingClosed(REASON, PARAMS);
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(3)).onReconfigured(any());
+        verifyOpenState(session, false);
+    }
+
+    @Test
+    public void testClose_NoCallbackUntilInvoked() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        session.onRangingOpened();
 
         // Calling close multiple times should invoke closeRanging until the session receives
         // the onClosed callback.
@@ -143,7 +224,7 @@
         for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
             session.close();
             verifyOpenState(session, true);
-            verify(ADAPTER, times(i)).closeRanging(handle);
+            verify(adapter, times(i)).closeRanging(handle);
             verify(callback, times(0)).onClosed(anyInt(), any());
         }
 
@@ -151,18 +232,47 @@
         // the session's close.
         final int totalCallsAfterOnRangingClosed = 2;
         for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
-            session.onRangingClosed(CLOSE_REASON, PARAMS);
+            session.onRangingClosed(REASON, PARAMS);
             verifyOpenState(session, false);
-            verify(ADAPTER, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
+            verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
             verify(callback, times(i)).onClosed(anyInt(), any());
         }
     }
 
     @Test
+    public void testClose_OnClosedCalled() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+        session.onRangingOpened();
+
+        session.close();
+        verify(callback, times(1)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testClose_CannotInteractFurther() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+        session.close();
+
+        verifyThrowIllegalState(() -> session.start(PARAMS));
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verifyThrowIllegalState(() -> session.stop());
+        verifyNoThrowIllegalState(() -> session.close());
+    }
+
+    @Test
     public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
         assertFalse(session.isOpen());
         session.onRangingStarted(PARAMS);
@@ -178,7 +288,8 @@
     public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
         assertFalse(session.isOpen());
 
@@ -191,4 +302,77 @@
     private void verifyOpenState(RangingSession session, boolean expected) {
         assertEquals(expected, session.isOpen());
     }
+
+    private void verifyThrowIllegalState(Runnable runnable) {
+        try {
+            runnable.run();
+            fail();
+        } catch (IllegalStateException e) {
+            // Pass
+        }
+    }
+
+    private void verifyNoThrowIllegalState(Runnable runnable) {
+        try {
+            runnable.run();
+        } catch (IllegalStateException e) {
+            fail();
+        }
+    }
+
+    abstract class AdapterAnswer implements Answer {
+        protected RangingSession mSession;
+
+        protected AdapterAnswer(RangingSession session) {
+            mSession = session;
+        }
+    }
+
+    class StartAnswer extends AdapterAnswer {
+        StartAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingStarted(PARAMS);
+            return null;
+        }
+    }
+
+    class ReconfigureAnswer extends AdapterAnswer {
+        ReconfigureAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingReconfigured(PARAMS);
+            return null;
+        }
+    }
+
+    class StopAnswer extends AdapterAnswer {
+        StopAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingStopped();
+            return null;
+        }
+    }
+
+    class CloseAnswer extends AdapterAnswer {
+        CloseAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingClosed(REASON, PARAMS);
+            return null;
+        }
+    }
 }
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 9f46ceb..49888fd 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -204,6 +204,26 @@
     }
 
     /**
+     * Draws a ripple
+     *
+     * @param cx
+     * @param cy
+     * @param radius
+     * @param paint
+     * @param progress
+     * @param shader
+     *
+     * @hide
+     */
+    public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
+            CanvasProperty<Float> radius, CanvasProperty<Paint> paint,
+            CanvasProperty<Float> progress, RuntimeShader shader) {
+        nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
+                radius.getNativeContainer(), paint.getNativeContainer(),
+                progress.getNativeContainer(), shader.getNativeShaderFactory());
+    }
+
+    /**
      * Draws a round rect
      *
      * @param left
@@ -260,6 +280,9 @@
     private static native void nDrawCircle(long renderer, long propCx,
             long propCy, long propRadius, long propPaint);
     @CriticalNative
+    private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius,
+            long propPaint, long propProgress, long runtimeEffect);
+    @CriticalNative
     private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
             long propRight, long propBottom, long propRx, long propRy, long propPaint);
     @CriticalNative
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index fb0983a..7f2e503 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -115,6 +115,10 @@
                 nativeShaders, colorSpace().getNativeInstance(), mIsOpaque);
     }
 
+    public long getNativeShaderFactory() {
+        return mNativeInstanceRuntimeShaderFactory;
+    }
+
     private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs,
             long[] shaderInputs, long colorSpaceHandle, boolean isOpaque);
 
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 807e5af..13f1fdd 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -42,4 +42,10 @@
     <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
      when the PIP menu is shown in center. -->
     <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
+
+    <!-- one handed background panel default color RGB -->
+    <item name="config_one_handed_background_rgb" format="float" type="dimen">0.5</item>
+
+    <!-- one handed background panel default alpha -->
+    <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 63d3118..8817f8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -55,19 +55,16 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                 taskInfo.taskId);
         mLeashByTaskId.put(taskInfo.taskId, leash);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         final Point positionInParent = taskInfo.positionInParent;
         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);
-            // TODO(shell-transitions): Eventually set everything in transition so there's no
-            //                          SF Transaction here.
-            if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
-                t.setAlpha(leash, 1f);
-                t.setMatrix(leash, 1, 0, 0, 1);
-                t.show(leash);
-            }
+            t.setAlpha(leash, 1f);
+            t.setMatrix(leash, 1, 0, 0, 1);
+            t.show(leash);
         });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index 2f2168f..aa82339 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,113 +16,32 @@
 
 package com.android.wm.shell;
 
+import android.util.Slog;
+
 import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import java.io.PrintWriter;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 /**
  * An entry point into the shell for dumping shell internal state and running adb commands.
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
-public final class ShellCommandHandler {
+public interface ShellCommandHandler {
+    /**
+     * Dumps the shell state.
+     */
+    void dump(PrintWriter pw);
 
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
-    private final Optional<Pip> mPipOptional;
-    private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutout;
-    private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<AppPairs> mAppPairsOptional;
-
-    public ShellCommandHandler(
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional) {
-        mShellTaskOrganizer = shellTaskOrganizer;
-        mLegacySplitScreenOptional = legacySplitScreenOptional;
-        mPipOptional = pipOptional;
-        mOneHandedOptional = oneHandedOptional;
-        mHideDisplayCutout = hideDisplayCutout;
-        mAppPairsOptional = appPairsOptional;
-    }
-
-    /** Dumps WM Shell internal state. */
-    @ExternalThread
-    public void dump(PrintWriter pw) {
-        mShellTaskOrganizer.dump(pw, "");
-        pw.println();
-        pw.println();
-        mPipOptional.ifPresent(pip -> pip.dump(pw));
-        mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
-        mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
-        mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
-        pw.println();
-        pw.println();
-        mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
-    }
-
-
-    /** Returns {@code true} if command was found and executed. */
-    @ExternalThread
-    public boolean handleCommand(String[] args, PrintWriter pw) {
-        if (args.length < 2) {
-            // Argument at position 0 is "WMShell".
-            return false;
-        }
-        switch (args[1]) {
-            case "pair":
-                return runPair(args, pw);
-            case "unpair":
-                return runUnpair(args, pw);
-            case "help":
-                return runHelp(pw);
-            default:
-                return false;
-        }
-    }
-
-
-    private boolean runPair(String[] args, PrintWriter pw) {
-        if (args.length < 4) {
-            // First two arguments are "WMShell" and command name.
-            pw.println("Error: two task ids should be provided as arguments");
-            return false;
-        }
-        final int taskId1 = new Integer(args[2]);
-        final int taskId2 = new Integer(args[3]);
-        mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
-        return true;
-    }
-
-    private boolean runUnpair(String[] args, PrintWriter pw) {
-        if (args.length < 3) {
-            // First two arguments are "WMShell" and command name.
-            pw.println("Error: task id should be provided as an argument");
-            return false;
-        }
-        final int taskId = new Integer(args[2]);
-        mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
-        return true;
-    }
-
-    private boolean runHelp(PrintWriter pw) {
-        pw.println("Window Manager Shell commands:");
-        pw.println("  help");
-        pw.println("      Print this help text.");
-        pw.println("  <no arguments provided>");
-        pw.println("    Dump Window Manager Shell internal state");
-        pw.println("  pair <taskId1> <taskId2>");
-        pw.println("  unpair <taskId>");
-        pw.println("    Pairs/unpairs tasks with given ids.");
-        return true;
-    }
+    /**
+     * Handles a shell command.
+     */
+    boolean handleCommand(final String[] args, PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
new file mode 100644
index 0000000..f213af7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -0,0 +1,171 @@
+/*
+ * 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.wm.shell;
+
+import android.util.Slog;
+
+import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An entry point into the shell for dumping shell internal state and running adb commands.
+ *
+ * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
+ */
+public final class ShellCommandHandlerImpl {
+    private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
+
+    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<Pip> mPipOptional;
+    private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<HideDisplayCutout> mHideDisplayCutout;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
+    private final Optional<AppPairs> mAppPairsOptional;
+    private final ShellExecutor mMainExecutor;
+    private final HandlerImpl mImpl = new HandlerImpl();
+
+    public static ShellCommandHandler create(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout,
+            Optional<AppPairs> appPairsOptional,
+            ShellExecutor mainExecutor) {
+        return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
+                pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional,
+                mainExecutor).mImpl;
+    }
+
+    private ShellCommandHandlerImpl(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout,
+            Optional<AppPairs> appPairsOptional,
+            ShellExecutor mainExecutor) {
+        mShellTaskOrganizer = shellTaskOrganizer;
+        mLegacySplitScreenOptional = legacySplitScreenOptional;
+        mPipOptional = pipOptional;
+        mOneHandedOptional = oneHandedOptional;
+        mHideDisplayCutout = hideDisplayCutout;
+        mAppPairsOptional = appPairsOptional;
+        mMainExecutor = mainExecutor;
+    }
+
+    /** Dumps WM Shell internal state. */
+    private void dump(PrintWriter pw) {
+        mShellTaskOrganizer.dump(pw, "");
+        pw.println();
+        pw.println();
+        mPipOptional.ifPresent(pip -> pip.dump(pw));
+        mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
+        mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+        mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
+        pw.println();
+        pw.println();
+        mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
+    }
+
+
+    /** Returns {@code true} if command was found and executed. */
+    private boolean handleCommand(final String[] args, PrintWriter pw) {
+        if (args.length < 2) {
+            // Argument at position 0 is "WMShell".
+            return false;
+        }
+        switch (args[1]) {
+            case "pair":
+                return runPair(args, pw);
+            case "unpair":
+                return runUnpair(args, pw);
+            case "help":
+                return runHelp(pw);
+            default:
+                return false;
+        }
+    }
+
+
+    private boolean runPair(String[] args, PrintWriter pw) {
+        if (args.length < 4) {
+            // First two arguments are "WMShell" and command name.
+            pw.println("Error: two task ids should be provided as arguments");
+            return false;
+        }
+        final int taskId1 = new Integer(args[2]);
+        final int taskId2 = new Integer(args[3]);
+        mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
+        return true;
+    }
+
+    private boolean runUnpair(String[] args, PrintWriter pw) {
+        if (args.length < 3) {
+            // First two arguments are "WMShell" and command name.
+            pw.println("Error: task id should be provided as an argument");
+            return false;
+        }
+        final int taskId = new Integer(args[2]);
+        mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
+        return true;
+    }
+
+    private boolean runHelp(PrintWriter pw) {
+        pw.println("Window Manager Shell commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  <no arguments provided>");
+        pw.println("    Dump Window Manager Shell internal state");
+        pw.println("  pair <taskId1> <taskId2>");
+        pw.println("  unpair <taskId>");
+        pw.println("    Pairs/unpairs tasks with given ids.");
+        return true;
+    }
+
+    private class HandlerImpl implements ShellCommandHandler {
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw));
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to dump the Shell in 2s", e);
+            }
+        }
+
+        @Override
+        public boolean handleCommand(String[] args, PrintWriter pw) {
+            try {
+                boolean[] result = new boolean[1];
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw);
+                });
+                return result[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to handle Shell command in 2s", e);
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
index f4c617e..d7010b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
@@ -16,61 +16,15 @@
 
 package com.android.wm.shell;
 
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-
-import java.util.Optional;
 
 /**
  * An entry point into the shell for initializing shell internal state.
  */
-public class ShellInit {
-
-    private final DisplayImeController mDisplayImeController;
-    private final DragAndDropController mDragAndDropController;
-    private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
-    private final Optional<AppPairs> mAppPairsOptional;
-    private final FullscreenTaskListener mFullscreenTaskListener;
-    private final Transitions mTransitions;
-
-    public ShellInit(DisplayImeController displayImeController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions) {
-        mDisplayImeController = displayImeController;
-        mDragAndDropController = dragAndDropController;
-        mShellTaskOrganizer = shellTaskOrganizer;
-        mLegacySplitScreenOptional = legacySplitScreenOptional;
-        mAppPairsOptional = appPairsOptional;
-        mFullscreenTaskListener = fullscreenTaskListener;
-        mTransitions = transitions;
-    }
-
-    @ExternalThread
-    public void init() {
-        // Start listening for display changes
-        mDisplayImeController.startMonitorDisplays();
-
-        mShellTaskOrganizer.addListenerForType(
-                mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
-        // Register the shell organizer
-        mShellTaskOrganizer.registerOrganizer();
-
-        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
-        // Bind the splitscreen impl to the drag drop controller
-        mDragAndDropController.setSplitScreenController(mLegacySplitScreenOptional);
-
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitions.register(mShellTaskOrganizer);
-        }
-    }
+@ExternalThread
+public interface ShellInit {
+    /**
+     * Initializes the shell state.
+     */
+    void init();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
new file mode 100644
index 0000000..0056761
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -0,0 +1,105 @@
+/*
+ * 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.wm.shell;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
+
+import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The entry point implementation into the shell for initializing shell internal state.
+ */
+public class ShellInitImpl {
+    private static final String TAG = ShellInitImpl.class.getSimpleName();
+
+    private final DisplayImeController mDisplayImeController;
+    private final DragAndDropController mDragAndDropController;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
+    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<AppPairs> mAppPairsOptional;
+    private final FullscreenTaskListener mFullscreenTaskListener;
+    private final ShellExecutor mMainExecutor;
+
+    private final InitImpl mImpl = new InitImpl();
+
+    public static ShellInit create(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<AppPairs> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            ShellExecutor mainExecutor) {
+        return new ShellInitImpl(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                appPairsOptional,
+                fullscreenTaskListener,
+                mainExecutor).mImpl;
+    }
+
+    private ShellInitImpl(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<AppPairs> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            ShellExecutor mainExecutor) {
+        mDisplayImeController = displayImeController;
+        mDragAndDropController = dragAndDropController;
+        mShellTaskOrganizer = shellTaskOrganizer;
+        mLegacySplitScreenOptional = legacySplitScreenOptional;
+        mAppPairsOptional = appPairsOptional;
+        mFullscreenTaskListener = fullscreenTaskListener;
+        mMainExecutor = mainExecutor;
+    }
+
+    private void init() {
+        // Start listening for display changes
+        mDisplayImeController.startMonitorDisplays();
+
+        mShellTaskOrganizer.addListenerForType(
+                mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        // Register the shell organizer
+        mShellTaskOrganizer.registerOrganizer();
+
+        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+
+        // Bind the splitscreen impl to the drag drop controller
+        mDragAndDropController.initialize(mLegacySplitScreenOptional);
+    }
+
+    @ExternalThread
+    private class InitImpl implements ShellInit {
+        @Override
+        public void init() {
+            try {
+                mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init());
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to initialize the Shell in 2s", e);
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d588419..8d0e965 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -29,11 +29,14 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Binder;
 import android.util.CloseGuard;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -44,7 +47,7 @@
  * View that can display a task.
  */
 public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
-        ShellTaskOrganizer.TaskListener {
+        ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
 
     /** Callback for listening task state. */
     public interface Listener {
@@ -70,6 +73,7 @@
     private final CloseGuard mGuard = new CloseGuard();
 
     private final ShellTaskOrganizer mTaskOrganizer;
+    private final Executor mShellExecutor;
 
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
@@ -78,16 +82,17 @@
     private boolean mSurfaceCreated;
     private boolean mIsInitialized;
     private Listener mListener;
-    private Executor mExecutor;
+    private Executor mListenerExecutor;
 
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRootRect = new Rect();
+    private final int[] mTmpLocation = new int[2];
 
     public TaskView(Context context, ShellTaskOrganizer organizer) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
 
         mTaskOrganizer = organizer;
-        mExecutor = organizer.getExecutor();
+        mShellExecutor = organizer.getExecutor();
         setUseAlpha();
         getHolder().addCallback(this);
         mGuard.open("release");
@@ -96,12 +101,13 @@
     /**
      * Only one listener may be set on the view, throws an exception otherwise.
      */
-    public void setListener(Listener listener) {
+    public void setListener(@NonNull Executor executor, Listener listener) {
         if (mListener != null) {
             throw new IllegalStateException(
                     "Trying to set a listener when one has already been set");
         }
         mListener = listener;
+        mListenerExecutor = executor;
     }
 
     /**
@@ -146,7 +152,9 @@
 
     private void prepareActivityOptions(ActivityOptions options) {
         final Binder launchCookie = new Binder();
-        mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
+        });
         options.setLaunchCookie(launchCookie);
         options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         options.setTaskAlwaysOnTop(true);
@@ -193,11 +201,15 @@
 
     private void performRelease() {
         getHolder().removeCallback(this);
-        mTaskOrganizer.removeListener(this);
-        resetTaskInfo();
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.removeListener(this);
+            resetTaskInfo();
+        });
         mGuard.close();
         if (mListener != null && mIsInitialized) {
-            mListener.onReleased();
+            mListenerExecutor.execute(() -> {
+                mListener.onReleased();
+            });
             mIsInitialized = false;
         }
     }
@@ -214,75 +226,71 @@
         mTaskOrganizer.applyTransaction(wct);
         // TODO(b/151449487): Only call callback once we enable synchronization
         if (mListener != null) {
-            mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+            mListenerExecutor.execute(() -> {
+                mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+            });
         }
     }
 
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash) {
-        if (mExecutor == null) return;
-        mExecutor.execute(() -> {
-            mTaskInfo = taskInfo;
-            mTaskToken = taskInfo.token;
-            mTaskLeash = leash;
+        mTaskInfo = taskInfo;
+        mTaskToken = taskInfo.token;
+        mTaskLeash = leash;
 
-            if (mSurfaceCreated) {
-                // Surface is ready, so just reparent the task to this surface control
-                mTransaction.reparent(mTaskLeash, getSurfaceControl())
-                        .show(mTaskLeash)
-                        .apply();
-            } else {
-                // The surface has already been destroyed before the task has appeared,
-                // so go ahead and hide the task entirely
-                updateTaskVisibility();
-            }
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
-            // TODO: Synchronize show with the resize
-            onLocationChanged();
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+        if (mSurfaceCreated) {
+            // Surface is ready, so just reparent the task to this surface control
+            mTransaction.reparent(mTaskLeash, getSurfaceControl())
+                    .show(mTaskLeash)
+                    .apply();
+        } else {
+            // The surface has already been destroyed before the task has appeared,
+            // so go ahead and hide the task entirely
+            updateTaskVisibility();
+        }
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
+        // TODO: Synchronize show with the resize
+        onLocationChanged();
+        setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
 
-            if (mListener != null) {
+        if (mListener != null) {
+            mListenerExecutor.execute(() -> {
                 mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
-            }
-        });
+            });
+        }
     }
 
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mExecutor == null) return;
-        mExecutor.execute(() -> {
-            if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
+        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
 
-            if (mListener != null) {
+        if (mListener != null) {
+            mListenerExecutor.execute(() -> {
                 mListener.onTaskRemovalStarted(taskInfo.taskId);
-            }
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+            });
+        }
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
 
-            // Unparent the task when this surface is destroyed
-            mTransaction.reparent(mTaskLeash, null).apply();
-            resetTaskInfo();
-        });
+        // Unparent the task when this surface is destroyed
+        mTransaction.reparent(mTaskLeash, null).apply();
+        resetTaskInfo();
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mExecutor == null) return;
-        mExecutor.execute(() -> {
-            mTaskInfo.taskDescription = taskInfo.taskDescription;
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
-        });
+        mTaskInfo.taskDescription = taskInfo.taskDescription;
+        setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
     }
 
     @Override
     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mExecutor == null) return;
-        mExecutor.execute(() -> {
-            if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
-            if (mListener != null) {
+        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
+        if (mListener != null) {
+            mListenerExecutor.execute(() -> {
                 mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
-            }
-        });
+            });
+        }
     }
 
     @Override
@@ -302,17 +310,21 @@
         mSurfaceCreated = true;
         if (mListener != null && !mIsInitialized) {
             mIsInitialized = true;
-            mListener.onInitialized();
+            mListenerExecutor.execute(() -> {
+                mListener.onInitialized();
+            });
         }
-        if (mTaskToken == null) {
-            // Nothing to update, task is not yet available
-            return;
-        }
-        // Reparent the task when this surface is created
-        mTransaction.reparent(mTaskLeash, getSurfaceControl())
-                .show(mTaskLeash)
-                .apply();
-        updateTaskVisibility();
+        mShellExecutor.execute(() -> {
+            if (mTaskToken == null) {
+                // Nothing to update, task is not yet available
+                return;
+            }
+            // Reparent the task when this surface is created
+            mTransaction.reparent(mTaskLeash, getSurfaceControl())
+                    .show(mTaskLeash)
+                    .apply();
+            updateTaskVisibility();
+        });
     }
 
     @Override
@@ -326,13 +338,47 @@
     @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
         mSurfaceCreated = false;
-        if (mTaskToken == null) {
-            // Nothing to update, task is not yet available
-            return;
-        }
+        mShellExecutor.execute(() -> {
+            if (mTaskToken == null) {
+                // Nothing to update, task is not yet available
+                return;
+            }
 
-        // Unparent the task when this surface is destroyed
-        mTransaction.reparent(mTaskLeash, null).apply();
-        updateTaskVisibility();
+            // Unparent the task when this surface is destroyed
+            mTransaction.reparent(mTaskLeash, null).apply();
+            updateTaskVisibility();
+        });
+    }
+
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+        // TODO(b/176854108): Consider to move the logic into gatherTransparentRegions since this
+        //   is dependent on the order of listener.
+        // If there are multiple TaskViews, we'll set the touchable area as the root-view, then
+        // subtract each TaskView from it.
+        if (inoutInfo.touchableRegion.isEmpty()) {
+            inoutInfo.setTouchableInsets(
+                    ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            View root = getRootView();
+            root.getLocationInWindow(mTmpLocation);
+            mTmpRootRect.set(mTmpLocation[0], mTmpLocation[1], root.getWidth(), root.getHeight());
+            inoutInfo.touchableRegion.set(mTmpRootRect);
+        }
+        getLocationInWindow(mTmpLocation);
+        mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
+                mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+        inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java
new file mode 100644
index 0000000..a29e7a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.annotation.UiContext;
+import android.content.Context;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/** Interface to create TaskView. */
+@ExternalThread
+public interface TaskViewFactory {
+    /** Creates an {@link TaskView} */
+    void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
new file mode 100644
index 0000000..a5dd79b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.annotation.UiContext;
+import android.content.Context;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/** Factory controller which can create {@link TaskView} */
+public class TaskViewFactoryController {
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final ShellExecutor mShellExecutor;
+
+    public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
+            ShellExecutor shellExecutor) {
+        mTaskOrganizer = taskOrganizer;
+        mShellExecutor = shellExecutor;
+    }
+
+    /** Creates an {@link TaskView} */
+    @ShellMainThread
+    public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
+        TaskView taskView = new TaskView(context, mTaskOrganizer);
+        executor.execute(() -> {
+            onCreate.accept(taskView);
+        });
+    }
+
+    public TaskViewFactory getTaskViewFactory() {
+        return new TaskViewFactoryImpl();
+    }
+
+    private class TaskViewFactoryImpl implements TaskViewFactory {
+        @ExternalThread
+        public void create(@UiContext Context context,
+                Executor executor, Consumer<TaskView> onCreate) {
+            mShellExecutor.execute(() -> {
+                TaskViewFactoryController.this.create(context, executor, onCreate);
+            });
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
index 54863d2..5213f6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell;
 
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -25,15 +26,17 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
-import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.ITransitionPlayer;
 import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
 import androidx.annotation.BinderThread;
@@ -59,8 +62,16 @@
     private final ShellExecutor mAnimExecutor;
     private final TransitionPlayerImpl mPlayerImpl;
 
+    /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
+    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
+
+    private static final class ActiveTransition {
+        ArrayList<Animator> mAnimations = null;
+        TransitionHandler mFirstHandler = null;
+    }
+
     /** Keeps track of currently tracked transitions and all the animations associated with each */
-    private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
+    private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>();
 
     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
@@ -75,6 +86,22 @@
         taskOrganizer.registerTransitionPlayer(mPlayerImpl);
     }
 
+    /**
+     * Adds a handler candidate.
+     * @see TransitionHandler
+     */
+    public void addHandler(@NonNull TransitionHandler handler) {
+        mHandlers.add(handler);
+    }
+
+    public ShellExecutor getMainExecutor() {
+        return mMainExecutor;
+    }
+
+    public ShellExecutor getAnimExecutor() {
+        return mAnimExecutor;
+    }
+
     // TODO(shell-transitions): real animations
     private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
             boolean show) {
@@ -93,7 +120,7 @@
             transaction.apply();
             mTransactionPool.release(transaction);
             mMainExecutor.execute(() -> {
-                mActiveTransitions.get(transition).remove(va);
+                mActiveTransitions.get(transition).mAnimations.remove(va);
                 onFinish(transition);
             });
         };
@@ -114,30 +141,23 @@
             @Override
             public void onAnimationRepeat(Animator animation) { }
         });
-        mActiveTransitions.get(transition).add(va);
+        mActiveTransitions.get(transition).mAnimations.add(va);
         mAnimExecutor.execute(va::start);
     }
 
-    private static boolean isOpeningType(@WindowManager.TransitionType int type) {
+    /** @return true if the transition was triggered by opening something vs closing something */
+    public static boolean isOpeningType(@WindowManager.TransitionType int type) {
         return type == TRANSIT_OPEN
                 || type == TRANSIT_TO_FRONT
                 || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
     }
 
-    private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
+    /**
+     * Reparents all participants into a shared parent and orders them based on: the global transit
+     * type, their transit mode, and their destination z-order.
+     */
+    private static void setupStartState(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
-                transitionToken, info);
-        // start task
-        if (!mActiveTransitions.containsKey(transitionToken)) {
-            Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
-                    + " expecting one of " + mActiveTransitions.keySet());
-        }
-        if (mActiveTransitions.get(transitionToken) != null) {
-            throw new IllegalStateException("Got a duplicate onTransitionReady call for "
-                    + transitionToken);
-        }
-        mActiveTransitions.put(transitionToken, new ArrayList<>());
         boolean isOpening = isOpeningType(info.getType());
         if (info.getRootLeash().isValid()) {
             t.show(info.getRootLeash());
@@ -148,24 +168,26 @@
             final SurfaceControl leash = change.getLeash();
             final int mode = info.getChanges().get(i).getMode();
 
-            // Don't animate anything with an animating parent
+            // Don't move anything with an animating parent
             if (change.getParent() != null) {
-                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
                     t.show(leash);
                     t.setMatrix(leash, 1, 0, 0, 1);
+                    t.setAlpha(leash, 1.f);
+                    t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
                 }
                 continue;
             }
 
             t.reparent(leash, info.getRootLeash());
-            t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x,
-                    change.getEndAbsBounds().top - info.getRootOffset().y);
+            t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
+                    change.getStartAbsBounds().top - info.getRootOffset().y);
             // Put all the OPEN/SHOW on top
             if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                 t.show(leash);
                 t.setMatrix(leash, 1, 0, 0, 1);
                 if (isOpening) {
-                    // put on top and fade in
+                    // put on top with 0 alpha
                     t.setLayer(leash, info.getChanges().size() - i);
                     if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                         // This received a transferred starting window, so make it immediately
@@ -173,47 +195,155 @@
                         t.setAlpha(leash, 1.f);
                     } else {
                         t.setAlpha(leash, 0.f);
-                        startExampleAnimation(transitionToken, leash, true /* show */);
                     }
                 } else {
-                    // put on bottom and leave it visible without fade
+                    // put on bottom and leave it visible
                     t.setLayer(leash, -i);
                     t.setAlpha(leash, 1.f);
                 }
             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
                 if (isOpening) {
-                    // put on bottom and leave visible without fade
+                    // put on bottom and leave visible
                     t.setLayer(leash, -i);
                 } else {
-                    // put on top and fade out
+                    // put on top
                     t.setLayer(leash, info.getChanges().size() - i);
-                    startExampleAnimation(transitionToken, leash, false /* show */);
                 }
-            } else {
+            } else { // CHANGE
                 t.setLayer(leash, info.getChanges().size() - i);
             }
         }
+    }
+
+    private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
+                transitionToken, info);
+        final ActiveTransition active = mActiveTransitions.get(transitionToken);
+        if (active == null) {
+            throw new IllegalStateException("Got transitionReady for non-active transition "
+                    + transitionToken + ". expecting one of " + mActiveTransitions.keySet());
+        }
+        if (active.mAnimations != null) {
+            throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+                    + transitionToken);
+        }
+        if (!info.getRootLeash().isValid()) {
+            // Invalid root-leash implies that the transition is empty/no-op, so just do
+            // housekeeping and return.
+            t.apply();
+            onFinish(transitionToken);
+            return;
+        }
+
+        setupStartState(info, t);
+
+        final Runnable finishRunnable = () -> onFinish(transitionToken);
+        // If a handler chose to uniquely run this animation, try delegating to it.
+        if (active.mFirstHandler != null && active.mFirstHandler.startAnimation(
+                transitionToken, info, t, finishRunnable)) {
+            return;
+        }
+        // Otherwise give every other handler a chance (in order)
+        for (int i = mHandlers.size() - 1; i >= 0; --i) {
+            if (mHandlers.get(i) == active.mFirstHandler) continue;
+            if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) {
+                return;
+            }
+        }
+
+        // No handler chose to perform this animation, so fall-back to the
+        // default animation handling.
+        final boolean isOpening = isOpeningType(info.getType());
+        active.mAnimations = new ArrayList<>(); // Play fade animations
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+
+            // Don't animate anything with an animating parent
+            if (change.getParent() != null) continue;
+
+            final int mode = info.getChanges().get(i).getMode();
+            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+                    // This received a transferred starting window, so don't animate
+                    continue;
+                }
+                // fade in
+                startExampleAnimation(transitionToken, change.getLeash(), true /* show */);
+            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+                // fade out
+                startExampleAnimation(transitionToken, change.getLeash(), false /* show */);
+            }
+        }
         t.apply();
         onFinish(transitionToken);
     }
 
     private void onFinish(IBinder transition) {
-        if (!mActiveTransitions.get(transition).isEmpty()) return;
+        final ActiveTransition active = mActiveTransitions.get(transition);
+        if (active.mAnimations != null && !active.mAnimations.isEmpty()) return;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animations finished, notifying core %s", transition);
         mActiveTransitions.remove(transition);
         mOrganizer.finishTransition(transition, null, null);
     }
 
-    private void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+    private void requestStartTransition(int type, @NonNull IBinder transitionToken,
+            @Nullable ActivityManager.RunningTaskInfo triggerTask) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
                 type, transitionToken);
 
         if (mActiveTransitions.containsKey(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
         }
-        IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
-        mActiveTransitions.put(transition, null);
+        final ActiveTransition active = new ActiveTransition();
+        WindowContainerTransaction wct = null;
+        for (int i = mHandlers.size() - 1; i >= 0; --i) {
+            wct = mHandlers.get(i).handleRequest(type, transitionToken, triggerTask);
+            if (wct != null) {
+                active.mFirstHandler = mHandlers.get(i);
+                break;
+            }
+        }
+        IBinder transition = mOrganizer.startTransition(type, transitionToken, wct);
+        mActiveTransitions.put(transition, active);
+    }
+
+    /** Start a new transition directly. */
+    public IBinder startTransition(@WindowManager.TransitionType int type,
+            @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
+        final ActiveTransition active = new ActiveTransition();
+        active.mFirstHandler = handler;
+        IBinder transition = mOrganizer.startTransition(type, null /* token */, wct);
+        mActiveTransitions.put(transition, active);
+        return transition;
+    }
+
+    /**
+     * Interface for something which can handle a subset of transitions.
+     */
+    public interface TransitionHandler {
+        /**
+         * Starts a transition animation. This is always called if handleRequest returned non-null
+         * for a particular transition. Otherwise, it is only called if no other handler before
+         * it handled the transition.
+         *
+         * @return true if transition was handled, false if not (falls-back to default).
+         */
+        boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback);
+
+        /**
+         * Potentially handles a startTransition request.
+         * @param type The transition type
+         * @param triggerTask The task which triggered this transition request.
+         * @return WCT to apply with transition-start or null if this handler isn't handling
+         *         the request.
+         */
+        @Nullable
+        WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type,
+                @NonNull IBinder transition,
+                @Nullable ActivityManager.RunningTaskInfo triggerTask);
     }
 
     @BinderThread
@@ -227,9 +357,10 @@
         }
 
         @Override
-        public void requestStartTransition(int i, IBinder iBinder) throws RemoteException {
+        public void requestStartTransition(int i, IBinder iBinder,
+                ActivityManager.RunningTaskInfo runningTaskInfo) throws RemoteException {
             mMainExecutor.execute(() -> {
-                Transitions.this.requestStartTransition(i, iBinder);
+                Transitions.this.requestStartTransition(i, iBinder, runningTaskInfo);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index 834de3f..abd9257 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -37,8 +37,8 @@
      */
     private final PinnedStackListenerForwarder mPinnedStackListenerForwarder;
 
-    public WindowManagerShellWrapper(ShellExecutor shellMainExecutor) {
-        mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(shellMainExecutor);
+    public WindowManagerShellWrapper(ShellExecutor mainExecutor) {
+        mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(mainExecutor);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 40b41e1..9419b9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -45,7 +45,6 @@
 import android.graphics.PointF;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -68,6 +67,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 
 import java.io.FileDescriptor;
@@ -106,7 +106,6 @@
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private final BubbleDataRepository mDataRepository;
     private BubbleLogger mLogger;
-    private final Handler mMainHandler;
     private BubbleData mBubbleData;
     private View mBubbleScrim;
     @Nullable private BubbleStackView mStackView;
@@ -138,7 +137,7 @@
     private WindowManager mWindowManager;
 
     // Used to post to main UI thread
-    private Handler mHandler = new Handler();
+    private final ShellExecutor mMainExecutor;
 
     /** LayoutParams used to add the BubbleStackView to the window manager. */
     private WindowManager.LayoutParams mWmLayoutParams;
@@ -186,15 +185,15 @@
             WindowManagerShellWrapper windowManagerShellWrapper,
             LauncherApps launcherApps,
             UiEventLogger uiEventLogger,
-            Handler mainHandler,
-            ShellTaskOrganizer organizer) {
+            ShellTaskOrganizer organizer,
+            ShellExecutor mainExecutor) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
         BubblePositioner positioner = new BubblePositioner(context, windowManager);
         BubbleData data = new BubbleData(context, logger, positioner);
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, mainHandler, organizer, positioner);
+                logger, organizer, positioner, mainExecutor);
     }
 
     /**
@@ -211,14 +210,14 @@
             WindowManagerShellWrapper windowManagerShellWrapper,
             LauncherApps launcherApps,
             BubbleLogger bubbleLogger,
-            Handler mainHandler,
             ShellTaskOrganizer organizer,
-            BubblePositioner positioner) {
+            BubblePositioner positioner,
+            ShellExecutor mainExecutor) {
         mContext = context;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mDataRepository = dataRepository;
         mLogger = bubbleLogger;
-        mMainHandler = mainHandler;
+        mMainExecutor = mainExecutor;
 
         mBubblePositioner = positioner;
         mBubbleData = data;
@@ -241,7 +240,7 @@
                 bubble.setPendingIntentCanceled();
                 return;
             }
-            mHandler.post(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT));
+            mMainExecutor.execute(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT));
         });
 
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 7f33895..cb6b543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -314,7 +314,7 @@
         mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
         mExpandedViewContainer.addView(mTaskView);
         bringChildToFront(mTaskView);
-        mTaskView.setListener(mTaskViewListener);
+        mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
         mPositioner = mController.getPositioner();
     }
 
@@ -471,13 +471,12 @@
         mIsOverflow = overflow;
 
         Intent target = new Intent(mContext, BubbleOverflowActivity.class);
+        target.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
         Bundle extras = new Bundle();
         extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mController));
         target.putExtras(extras);
-        // TODO(b/175352055) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
-                target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         mSettingsIcon.setVisibility(GONE);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 1e5e43a..0f81d7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1201,15 +1201,7 @@
 
         mTempRect.setEmpty();
         getTouchableRegion(mTempRect);
-        if (mIsExpanded && mExpandedBubble != null
-                && mExpandedBubble.getExpandedView() != null
-                && mExpandedBubble.getExpandedView().getTaskView() != null) {
-            inoutInfo.touchableRegion.set(mTempRect);
-            mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect);
-            inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE);
-        } else {
-            inoutInfo.touchableRegion.set(mTempRect);
-        }
+        inoutInfo.touchableRegion.set(mTempRect);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index cb4584c..3a7b534 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.common;
 
-import android.os.Handler;
 import android.os.RemoteException;
 import android.view.IDisplayWindowRotationCallback;
 import android.view.IDisplayWindowRotationController;
@@ -37,7 +36,7 @@
  */
 public class DisplayChangeController {
 
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final IWindowManager mWmService;
     private final IDisplayWindowRotationController mControllerImpl;
 
@@ -45,8 +44,8 @@
             new ArrayList<>();
     private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>();
 
-    public DisplayChangeController(Handler mainHandler, IWindowManager wmService) {
-        mHandler = mainHandler;
+    public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
+        mMainExecutor = mainExecutor;
         mWmService = wmService;
         mControllerImpl = new DisplayWindowRotationControllerImpl();
         try {
@@ -97,7 +96,7 @@
         @Override
         public void onRotateDisplay(int displayId, final int fromRotation,
                 final int toRotation, IDisplayWindowRotationCallback callback) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
                         callback);
             });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index a413c05..ba9ba5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -44,7 +43,7 @@
 public class DisplayController {
     private static final String TAG = "DisplayController";
 
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final Context mContext;
     private final IWindowManager mWmService;
     private final DisplayChangeController mChangeController;
@@ -61,12 +60,12 @@
         return displayManager.getDisplay(displayId);
     }
 
-    public DisplayController(Context context, Handler handler,
-            IWindowManager wmService) {
-        mHandler = handler;
+    public DisplayController(Context context, IWindowManager wmService,
+            ShellExecutor mainExecutor) {
+        mMainExecutor = mainExecutor;
         mContext = context;
         mWmService = wmService;
-        mChangeController = new DisplayChangeController(mHandler, mWmService);
+        mChangeController = new DisplayChangeController(mWmService, mainExecutor);
         mDisplayContainerListener = new DisplayWindowListenerImpl();
         try {
             mWmService.registerDisplayWindowListener(mDisplayContainerListener);
@@ -229,35 +228,35 @@
     private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub {
         @Override
         public void onDisplayAdded(int displayId) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayController.this.onDisplayAdded(displayId);
             });
         }
 
         @Override
         public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig);
             });
         }
 
         @Override
         public void onDisplayRemoved(int displayId) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayController.this.onDisplayRemoved(displayId);
             });
         }
 
         @Override
         public void onFixedRotationStarted(int displayId, int newRotation) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayController.this.onFixedRotationStarted(displayId, newRotation);
             });
         }
 
         @Override
         public void onFixedRotationFinished(int displayId) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 DisplayController.this.onFixedRotationFinished(displayId);
             });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 3fbd7ed..3944128 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -40,10 +40,12 @@
 import android.view.animation.PathInterpolator;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -64,7 +66,7 @@
     private static final int FLOATING_IME_BOTTOM_INSET = -80;
 
     protected final IWindowManager mWmService;
-    protected final Executor mExecutor;
+    protected final Executor mMainExecutor;
     private final TransactionPool mTransactionPool;
     private final DisplayController mDisplayController;
     private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
@@ -73,10 +75,10 @@
 
     public DisplayImeController(IWindowManager wmService, DisplayController displayController,
             Executor mainExecutor, TransactionPool transactionPool) {
-        mExecutor = mainExecutor;
         mWmService = wmService;
-        mTransactionPool = transactionPool;
         mDisplayController = displayController;
+        mMainExecutor = mainExecutor;
+        mTransactionPool = transactionPool;
     }
 
     /** Starts monitor displays changes and set insets controller for each displays. */
@@ -90,11 +92,7 @@
         // WM will defer IME inset handling to it in multi-window scenarious.
         PerDisplay pd = new PerDisplay(displayId,
                 mDisplayController.getDisplayLayout(displayId).rotation());
-        try {
-            mWmService.setDisplayWindowInsetsController(displayId, pd);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
-        }
+        pd.register();
         mImePerDisplay.put(displayId, pd);
     }
 
@@ -182,9 +180,11 @@
     }
 
     /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
-    public class PerDisplay extends IDisplayWindowInsetsController.Stub {
+    public class PerDisplay {
         final int mDisplayId;
         final InsetsState mInsetsState = new InsetsState();
+        protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
+                new DisplayWindowInsetsControllerImpl();
         InsetsSourceControl mImeSourceControl = null;
         int mAnimationDirection = DIRECTION_NONE;
         ValueAnimator mAnimation = null;
@@ -198,10 +198,16 @@
             mRotation = initialRotation;
         }
 
-        @BinderThread
-        @Override
-        public void insetsChanged(InsetsState insetsState) {
-            mExecutor.execute(() -> {
+        public void register() {
+            try {
+                mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
+            }
+        }
+
+        protected void insetsChanged(InsetsState insetsState) {
+            mMainExecutor.execute(() -> {
                 if (mInsetsState.equals(insetsState)) {
                     return;
                 }
@@ -220,9 +226,8 @@
             });
         }
 
-        @BinderThread
-        @Override
-        public void insetsControlChanged(InsetsState insetsState,
+        @VisibleForTesting
+        protected void insetsControlChanged(InsetsState insetsState,
                 InsetsSourceControl[] activeControls) {
             insetsChanged(insetsState);
             if (activeControls != null) {
@@ -231,7 +236,7 @@
                         continue;
                     }
                     if (activeControl.getType() == InsetsState.ITYPE_IME) {
-                        mExecutor.execute(() -> {
+                        mMainExecutor.execute(() -> {
                             final Point lastSurfacePosition = mImeSourceControl != null
                                     ? mImeSourceControl.getSurfacePosition() : null;
                             final boolean positionChanged =
@@ -271,30 +276,25 @@
             }
         }
 
-        @BinderThread
-        @Override
-        public void showInsets(int types, boolean fromIme) {
+        protected void showInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
+            mMainExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
         }
 
-        @BinderThread
-        @Override
-        public void hideInsets(int types, boolean fromIme) {
+
+        protected void hideInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
+            mMainExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
         }
 
-        @BinderThread
-        @Override
         public void topFocusedWindowChanged(String packageName) {
-            // no-op
+            // Do nothing
         }
 
         /**
@@ -457,6 +457,47 @@
                 setVisibleDirectly(true /* visible */);
             }
         }
+
+        @VisibleForTesting
+        @BinderThread
+        public class DisplayWindowInsetsControllerImpl
+                extends IDisplayWindowInsetsController.Stub {
+            @Override
+            public void topFocusedWindowChanged(String packageName) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.topFocusedWindowChanged(packageName);
+                });
+            }
+
+            @Override
+            public void insetsChanged(InsetsState insetsState) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsChanged(insetsState);
+                });
+            }
+
+            @Override
+            public void insetsControlChanged(InsetsState insetsState,
+                    InsetsSourceControl[] activeControls) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsControlChanged(insetsState, activeControls);
+                });
+            }
+
+            @Override
+            public void showInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.showInsets(types, fromIme);
+                });
+            }
+
+            @Override
+            public void hideInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.hideInsets(types, fromIme);
+                });
+            }
+        }
     }
 
     void removeImeSurface() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 22b831b..f2c57f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -51,6 +51,16 @@
     }
 
     /**
+     * Convenience method to execute the blocking call with a default timeout.
+     *
+     * @throws InterruptedException if runnable does not return in the time specified by
+     *                              {@param waitTimeout}
+     */
+    default void executeBlocking(Runnable runnable) throws InterruptedException {
+        executeBlocking(runnable, 2, TimeUnit.SECONDS);
+    }
+
+    /**
      * See {@link android.os.Handler#postDelayed(Runnable, long)}.
      */
     void executeDelayed(Runnable r, long delayMillis);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 7321dc8..33beab5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -16,16 +16,14 @@
 
 package com.android.wm.shell.common;
 
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
-import android.os.Handler;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransactionCallback;
 import android.window.WindowOrganizer;
 
-import androidx.annotation.BinderThread;
-
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
 import java.util.ArrayList;
@@ -41,7 +39,7 @@
     private static final int REPLY_TIMEOUT = 5300;
 
     private final TransactionPool mTransactionPool;
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
 
     // Sync Transactions currently don't support nesting or interleaving properly, so
     // queue up transactions to run them serially.
@@ -59,9 +57,9 @@
         }
     };
 
-    public SyncTransactionQueue(TransactionPool pool, Handler handler) {
+    public SyncTransactionQueue(TransactionPool pool, ShellExecutor mainExecutor) {
         mTransactionPool = pool;
-        mHandler = handler;
+        mMainExecutor = mainExecutor;
     }
 
     /**
@@ -152,14 +150,14 @@
             if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
             mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
             if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
-            mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
+            mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
         }
 
         @BinderThread
         @Override
         public void onTransactionReady(int id,
                 @NonNull SurfaceControl.Transaction t) {
-            mHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 synchronized (mQueue) {
                     if (mId != id) {
                         Slog.e(TAG, "Got an unexpected onTransactionReady. Expected "
@@ -167,7 +165,7 @@
                         return;
                     }
                     mInFlight = null;
-                    mHandler.removeCallbacks(mOnReplyTimeout);
+                    mMainExecutor.removeCallbacks(mOnReplyTimeout);
                     if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
                     mQueue.remove(this);
                     onTransactionReceived(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index 718f7a0..db34248 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -71,11 +71,11 @@
     private final IActivityTaskManager mActivityTaskManager;
     // NOTE: In this case we do want to use a handler since we rely on the message system to
     // efficiently dedupe sequential calls
-    private Handler mHandler;
+    private Handler mMainHandler;
 
-    public TaskStackListenerImpl(Handler handler) {
+    public TaskStackListenerImpl(Handler mainHandler) {
         mActivityTaskManager = ActivityTaskManager.getService();
-        mHandler = new Handler(handler.getLooper(), this);
+        mMainHandler = new Handler(mainHandler.getLooper(), this);
     }
 
     @VisibleForTesting
@@ -84,8 +84,8 @@
     }
 
     @VisibleForTesting
-    void setHandler(Handler handler) {
-        mHandler = handler;
+    void setHandler(Handler mainHandler) {
+        mMainHandler = mainHandler;
     }
 
     public void addListener(TaskStackListenerCallback listener) {
@@ -124,13 +124,13 @@
 
     @Override
     public void onRecentTaskListUpdated() {
-        mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget();
     }
 
     @Override
     public void onRecentTaskListFrozenChanged(boolean frozen) {
-        mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */)
-                .sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0,
+                0 /* unused */).sendToTarget();
     }
 
     @Override
@@ -147,48 +147,50 @@
         }
         mTmpListeners.clear();
 
-        mHandler.removeMessages(ON_TASK_STACK_CHANGED);
-        mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED);
+        mMainHandler.removeMessages(ON_TASK_STACK_CHANGED);
+        mMainHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED);
     }
 
     @Override
     public void onTaskProfileLocked(int taskId, int userId) {
-        mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
     }
 
     @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) {
-        mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId,
+                newDisplayId).sendToTarget();
     }
 
     @Override
     public void onTaskCreated(int taskId, ComponentName componentName) {
-        mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
     }
 
     @Override
     public void onTaskRemoved(int taskId) {
-        mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget();
     }
 
     @Override
     public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
-        mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
     }
 
     @Override
     public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
     }
 
     @Override
     public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
-        mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+        mMainHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot)
+                .sendToTarget();
     }
 
     @Override
     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
+        mMainHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
     }
 
     @Override
@@ -198,44 +200,44 @@
         args.argi1 = userId;
         args.argi2 = taskId;
         args.argi3 = stackId;
-        mHandler.removeMessages(ON_ACTIVITY_PINNED);
-        mHandler.obtainMessage(ON_ACTIVITY_PINNED, args).sendToTarget();
+        mMainHandler.removeMessages(ON_ACTIVITY_PINNED);
+        mMainHandler.obtainMessage(ON_ACTIVITY_PINNED, args).sendToTarget();
     }
 
     @Override
     public void onActivityUnpinned() {
-        mHandler.removeMessages(ON_ACTIVITY_UNPINNED);
-        mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED);
+        mMainHandler.removeMessages(ON_ACTIVITY_UNPINNED);
+        mMainHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED);
     }
 
     @Override
-    public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible,
-            boolean clearedTask, boolean wasVisible) {
+    public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+            boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
         final SomeArgs args = SomeArgs.obtain();
         args.arg1 = task;
         args.argi1 = homeTaskVisible ? 1 : 0;
         args.argi2 = clearedTask ? 1 : 0;
         args.argi3 = wasVisible ? 1 : 0;
-        mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
-        mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
+        mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
+        mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
     }
 
     @Override
     public void onActivityForcedResizable(String packageName, int taskId, int reason) {
-        mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+        mMainHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
                 .sendToTarget();
     }
 
     @Override
     public void onActivityDismissingDockedStack() {
-        mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+        mMainHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK);
     }
 
     @Override
     public void onActivityLaunchOnSecondaryDisplayFailed(
             ActivityManager.RunningTaskInfo taskInfo,
             int requestedDisplayId) {
-        mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED,
+        mMainHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED,
                 requestedDisplayId,
                 0 /* unused */,
                 taskInfo).sendToTarget();
@@ -245,25 +247,25 @@
     public void onActivityLaunchOnSecondaryDisplayRerouted(
             ActivityManager.RunningTaskInfo taskInfo,
             int requestedDisplayId) {
-        mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
+        mMainHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
                 requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget();
     }
 
     @Override
     public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
-        mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
+        mMainHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
                 requestedOrientation).sendToTarget();
     }
 
     @Override
     public void onActivityRotation(int displayId) {
-        mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
+        mMainHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
                 .sendToTarget();
     }
 
     @Override
     public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-        mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
+        mMainHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
                 0 /* unused */,
                 activityToken).sendToTarget();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 8ae6679..c8b4e10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -74,11 +74,11 @@
     public DragAndDropController(Context context, DisplayController displayController) {
         mContext = context;
         mDisplayController = displayController;
-        mDisplayController.addDisplayWindowListener(this);
     }
 
-    public void setSplitScreenController(Optional<LegacySplitScreen> splitscreen) {
+    public void initialize(Optional<LegacySplitScreen> splitscreen) {
         mLegacySplitScreen = splitscreen.orElse(null);
+        mDisplayController.addDisplayWindowListener(this);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index b92846f..a891005 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -25,6 +25,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
@@ -37,12 +38,15 @@
 
     private final Context mContext;
     private final HideDisplayCutoutOrganizer mOrganizer;
+    private final ShellExecutor mMainExecutor;
     @VisibleForTesting
     boolean mEnabled;
 
-    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer) {
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+            ShellExecutor mainExecutor) {
         mContext = context;
         mOrganizer = organizer;
+        mMainExecutor = mainExecutor;
         updateStatus();
     }
 
@@ -52,7 +56,7 @@
      */
     @Nullable
     public static HideDisplayCutoutController create(
-            Context context, DisplayController displayController, Executor executor) {
+            Context context, DisplayController displayController, ShellExecutor mainExecutor) {
         // The SystemProperty is set for devices that support this feature and is used to control
         // whether to create the HideDisplayCutout instance.
         // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk).
@@ -61,8 +65,8 @@
         }
 
         HideDisplayCutoutOrganizer organizer =
-                new HideDisplayCutoutOrganizer(context, displayController, executor);
-        return new HideDisplayCutoutController(context, organizer);
+                new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
+        return new HideDisplayCutoutController(context, organizer, mainExecutor);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 51a35d8..53dd391 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -42,6 +42,7 @@
 import com.android.internal.R;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -90,8 +91,8 @@
             };
 
     HideDisplayCutoutOrganizer(Context context, DisplayController displayController,
-            Executor executor) {
-        super(executor);
+            ShellExecutor mainExecutor) {
+        super(mainExecutor);
         mContext = context;
         mDisplayController = displayController;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
index 2c0cf59..7ce9014 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
@@ -24,7 +24,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.util.Slog;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
@@ -33,6 +32,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 
 class DividerImeController implements DisplayImeController.ImePositionProcessor {
@@ -43,7 +43,7 @@
 
     private final LegacySplitScreenTaskListener mSplits;
     private final TransactionPool mTransactionPool;
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final TaskOrganizer mTaskOrganizer;
 
     /**
@@ -94,10 +94,10 @@
     private boolean mAdjustedWhileHidden = false;
 
     DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool,
-            Handler handler, TaskOrganizer taskOrganizer) {
+            ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) {
         mSplits = splits;
         mTransactionPool = pool;
-        mHandler = handler;
+        mMainExecutor = mainExecutor;
         mTaskOrganizer = taskOrganizer;
     }
 
@@ -377,7 +377,7 @@
     /** Completely aborts/resets adjustment state */
     public void pause(int displayId) {
         if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
-        mHandler.post(() -> {
+        mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
             if (mPaused) {
                 return;
@@ -396,7 +396,7 @@
 
     public void resume(int displayId) {
         if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
-        mHandler.post(() -> {
+        mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
             if (!mPaused) {
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
index ee5c9bc..139544f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
@@ -22,12 +22,12 @@
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.widget.Toast;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
 
 import java.util.function.Consumer;
 
@@ -40,7 +40,7 @@
 
     private static final int TIMEOUT = 1000;
     private final Context mContext;
-    private final Handler mHandler = new Handler();
+    private final ShellExecutor mMainExecutor;
     private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
     private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
     private boolean mDividerDragging;
@@ -69,15 +69,17 @@
     }
 
     ForcedResizableInfoActivityController(Context context,
-            LegacySplitScreenController splitScreenController) {
+            LegacySplitScreenController splitScreenController,
+            ShellExecutor mainExecutor) {
         mContext = context;
+        mMainExecutor = mainExecutor;
         splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
     }
 
     @Override
     public void onDraggingStart() {
         mDividerDragging = true;
-        mHandler.removeCallbacks(mTimeoutRunnable);
+        mMainExecutor.removeCallbacks(mTimeoutRunnable);
     }
 
     @Override
@@ -111,7 +113,7 @@
     }
 
     private void showPending() {
-        mHandler.removeCallbacks(mTimeoutRunnable);
+        mMainExecutor.removeCallbacks(mTimeoutRunnable);
         for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
             PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
             Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
@@ -127,8 +129,8 @@
     }
 
     private void postTimeout() {
-        mHandler.removeCallbacks(mTimeoutRunnable);
-        mHandler.postDelayed(mTimeoutRunnable, TIMEOUT);
+        mMainExecutor.removeCallbacks(mTimeoutRunnable);
+        mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT);
     }
 
     private boolean debounce(String packageName) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index 6b79a36..9e1f0a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Slog;
@@ -44,10 +43,12 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.Transitions;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -79,7 +80,7 @@
     private final DividerImeController mImePositionProcessor;
     private final DividerState mDividerState = new DividerState();
     private final ForcedResizableInfoActivityController mForcedResizableController;
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final LegacySplitScreenTaskListener mSplits;
     private final SystemWindows mSystemWindows;
     final TransactionPool mTransactionPool;
@@ -111,20 +112,23 @@
 
     public LegacySplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, Handler handler, TransactionPool transactionPool,
+            DisplayImeController imeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
-            TaskStackListenerImpl taskStackListener) {
+            TaskStackListenerImpl taskStackListener, Transitions transitions,
+            ShellExecutor mainExecutor) {
         mContext = context;
         mDisplayController = displayController;
         mSystemWindows = systemWindows;
         mImeController = imeController;
-        mHandler = handler;
-        mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
+        mMainExecutor = mainExecutor;
+        mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
+                mainExecutor);
         mTransactionPool = transactionPool;
         mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
         mTaskOrganizer = shellTaskOrganizer;
-        mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, syncQueue);
-        mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler,
+        mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions,
+                syncQueue);
+        mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor,
                 shellTaskOrganizer);
         mRotationController =
                 (display, fromRotation, toRotation, wct) -> {
@@ -269,11 +273,6 @@
         }
     }
 
-    /** Posts task to handler dealing with divider. */
-    void post(Runnable task) {
-        mHandler.post(task);
-    }
-
     @Override
     public DividerView getDividerView() {
         return mView;
@@ -343,7 +342,7 @@
     }
 
     void onTaskVanished() {
-        mHandler.post(this::removeDivider);
+        removeDivider();
     }
 
     private void updateVisibility(final boolean visible) {
@@ -377,7 +376,7 @@
     @Override
     public void setMinimized(final boolean minimized) {
         if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
-        mHandler.post(() -> {
+        mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
             if (!mVisible) {
                 return;
@@ -553,8 +552,36 @@
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
+    void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+        // Set resizable directly here because buildEnterSplit already resizes home stack.
+        mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
+    }
+
+    void finishEnterSplitTransition(boolean minimized) {
+        update(mDisplayController.getDisplayContext(
+                mContext.getDisplayId()).getResources().getConfiguration());
+        if (minimized) {
+            ensureMinimizedSplit();
+        } else {
+            ensureNormalSplit();
+        }
+    }
+
     void startDismissSplit(boolean toPrimaryTask) {
+        startDismissSplit(toPrimaryTask, false /* snapped */);
+    }
+
+    void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mSplits.getSplitTransitions().dismissSplit(
+                    mSplits, mSplitLayout, !toPrimaryTask, snapped);
+        } else {
         mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
+            onDismissSplit();
+        }
+    }
+
+    void onDismissSplit() {
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 02c82de..3ed070b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -32,6 +32,7 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.TaskOrganizer;
 
 import androidx.annotation.NonNull;
 
@@ -63,11 +64,17 @@
 
     final SurfaceSession mSurfaceSession = new SurfaceSession();
 
+    private final SplitScreenTransitions mSplitTransitions;
+
     LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
                     ShellTaskOrganizer shellTaskOrganizer,
+                    Transitions transitions,
                     SyncTransactionQueue syncQueue) {
         mSplitScreenController = splitScreenController;
         mTaskOrganizer = shellTaskOrganizer;
+        mSplitTransitions = new SplitScreenTransitions(splitScreenController.mTransactionPool,
+                transitions, mSplitScreenController, this);
+        transitions.addHandler(mSplitTransitions);
         mSyncQueue = syncQueue;
     }
 
@@ -98,6 +105,14 @@
         mSplitScreenController.mTransactionPool.release(t);
     }
 
+    TaskOrganizer getTaskOrganizer() {
+        return mTaskOrganizer;
+    }
+
+    SplitScreenTransitions getSplitTransitions() {
+        return mSplitTransitions;
+    }
+
     @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (this) {
@@ -189,16 +204,18 @@
                 return;
             }
 
-            mSplitScreenController.post(() -> handleTaskInfoChanged(taskInfo));
+            handleTaskInfoChanged(taskInfo);
         }
     }
 
     private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         mLeashByTaskId.put(taskInfo.taskId, leash);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
     }
 
     private void handleChildTaskChanged(RunningTaskInfo taskInfo) {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
         updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
     }
@@ -241,14 +258,15 @@
         } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
             mSecondary = info;
         }
+        if (DEBUG) {
+            Log.d(TAG, "onTaskInfoChanged " + mPrimary + "  " + mSecondary);
+        }
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
                 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
                         && mSplitScreenController.isHomeStackResizable());
-        if (DEBUG) {
-            Log.d(TAG, "onTaskInfoChanged " + mPrimary + "  " + mSecondary);
-        }
         if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
                 && secondaryImpliedMinimize == secondaryImpliesMinimize) {
             // No relevant changes
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
new file mode 100644
index 0000000..93520c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
@@ -0,0 +1,342 @@
+/*
+ * 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.legacysplitscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.Transitions;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+import java.util.ArrayList;
+
+/** Plays transition animations for split-screen */
+public class SplitScreenTransitions implements Transitions.TransitionHandler {
+    private static final String TAG = "SplitScreenTransitions";
+
+    public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10;
+
+    private final TransactionPool mTransactionPool;
+    private final Transitions mTransitions;
+    private final LegacySplitScreenController mSplitScreen;
+    private final LegacySplitScreenTaskListener mListener;
+
+    private IBinder mPendingDismiss = null;
+    private boolean mDismissFromSnap = false;
+    private IBinder mPendingEnter = null;
+    private IBinder mAnimatingTransition = null;
+
+    /** Keeps track of currently running animations */
+    private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+    private Runnable mFinishCallback = null;
+    private SurfaceControl.Transaction mFinishTransaction;
+
+    SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+            @NonNull LegacySplitScreenController splitScreen,
+            @NonNull LegacySplitScreenTaskListener listener) {
+        mTransactionPool = pool;
+        mTransitions = transitions;
+        mSplitScreen = splitScreen;
+        mListener = listener;
+    }
+
+    @Override
+    public WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type,
+            @NonNull IBinder transition, @Nullable ActivityManager.RunningTaskInfo triggerTask) {
+        WindowContainerTransaction out = null;
+        if (mSplitScreen.isDividerVisible()) {
+            // try to handle everything while in split-screen
+            out = new WindowContainerTransaction();
+            if (triggerTask != null) {
+                final boolean shouldDismiss =
+                        // if we close the primary-docked task, then leave split-screen since there
+                        // is nothing behind it.
+                        ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK)
+                                && triggerTask.parentTaskId == mListener.mPrimary.taskId)
+                        // if a non-resizable is launched, we also need to leave split-screen.
+                        || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
+                                && !triggerTask.isResizeable);
+                // In both cases, dismiss the primary
+                if (shouldDismiss) {
+                    WindowManagerProxy.buildDismissSplit(out, mListener,
+                            mSplitScreen.getSplitLayout(), true /* dismiss */);
+                    if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) {
+                        out.reorder(triggerTask.token, true /* onTop */);
+                    }
+                    mPendingDismiss = transition;
+                }
+            }
+        } else if (triggerTask != null) {
+            // Not in split mode, so look for an open with a trigger task.
+            if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
+                    && triggerTask.configuration.windowConfiguration.getWindowingMode()
+                        == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                out = new WindowContainerTransaction();
+                mSplitScreen.prepareEnterSplitTransition(out);
+                mPendingEnter = transition;
+            }
+        }
+        return out;
+    }
+
+    // TODO(shell-transitions): real animations
+    private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+        final float end = show ? 1.f : 0.f;
+        final float start = 1.f - end;
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+        va.setDuration(500);
+        va.addUpdateListener(animation -> {
+            float fraction = animation.getAnimatedFraction();
+            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+            transaction.apply();
+        });
+        final Runnable finisher = () -> {
+            transaction.setAlpha(leash, end);
+            transaction.apply();
+            mTransactionPool.release(transaction);
+            mTransitions.getMainExecutor().execute(() -> {
+                mAnimations.remove(va);
+                onFinish();
+            });
+        };
+        va.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) { }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finisher.run();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                finisher.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) { }
+        });
+        mAnimations.add(va);
+        mTransitions.getAnimExecutor().execute(va::start);
+    }
+
+    // TODO(shell-transitions): real animations
+    private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+            @NonNull Rect startBounds, @NonNull Rect endBounds) {
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+        va.setDuration(500);
+        va.addUpdateListener(animation -> {
+            float fraction = animation.getAnimatedFraction();
+            transaction.setWindowCrop(leash,
+                    (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+                    (int) (startBounds.height() * (1.f - fraction)
+                            + endBounds.height() * fraction));
+            transaction.setPosition(leash,
+                    startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+                    startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+            transaction.apply();
+        });
+        final Runnable finisher = () -> {
+            transaction.setWindowCrop(leash, 0, 0);
+            transaction.setPosition(leash, endBounds.left, endBounds.top);
+            transaction.apply();
+            mTransactionPool.release(transaction);
+            mTransitions.getMainExecutor().execute(() -> {
+                mAnimations.remove(va);
+                onFinish();
+            });
+        };
+        va.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finisher.run();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                finisher.run();
+            }
+        });
+        mAnimations.add(va);
+        mTransitions.getAnimExecutor().execute(va::start);
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+        if (transition != mPendingDismiss && transition != mPendingEnter) {
+            // If we're not in split-mode, just abort
+            if (!mSplitScreen.isDividerVisible()) return false;
+            // Check to see if HOME is involved
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (change.getTaskInfo() == null
+                        || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue;
+                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+                    mSplitScreen.ensureMinimizedSplit();
+                } else if (change.getMode() == TRANSIT_CLOSE
+                        || change.getMode() == TRANSIT_TO_BACK) {
+                    mSplitScreen.ensureNormalSplit();
+                }
+            }
+            // Use normal animations.
+            return false;
+        }
+
+        mFinishCallback = finishCallback;
+        mFinishTransaction = mTransactionPool.acquire();
+        mAnimatingTransition = transition;
+
+        // Play fade animations
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final SurfaceControl leash = change.getLeash();
+            final int mode = info.getChanges().get(i).getMode();
+
+            if (mode == TRANSIT_CHANGE) {
+                if (change.getParent() != null) {
+                    // This is probably reparented, so we want the parent to be immediately visible
+                    final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+                    t.show(parentChange.getLeash());
+                    t.setAlpha(parentChange.getLeash(), 1.f);
+                    // and then animate this layer outside the parent (since, for example, this is
+                    // the home task animating from fullscreen to part-screen).
+                    t.reparent(leash, info.getRootLeash());
+                    t.setLayer(leash, info.getChanges().size() - i);
+                    // build the finish reparent/reposition
+                    mFinishTransaction.reparent(leash, parentChange.getLeash());
+                    mFinishTransaction.setPosition(leash,
+                            change.getEndRelOffset().x, change.getEndRelOffset().y);
+                }
+                // TODO(shell-transitions): screenshot here
+                final Rect startBounds = new Rect(change.getStartAbsBounds());
+                final boolean isHome = change.getTaskInfo() != null
+                        && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+                if (mPendingDismiss == transition && mDismissFromSnap && !isHome) {
+                    // Home is special since it doesn't move during fling. Everything else, though,
+                    // when dismissing from snap, the top/left is at 0,0.
+                    startBounds.offsetTo(0, 0);
+                }
+                final Rect endBounds = new Rect(change.getEndAbsBounds());
+                startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+                endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+                startExampleResizeAnimation(leash, startBounds, endBounds);
+            }
+            if (change.getParent() != null) {
+                continue;
+            }
+
+            if (transition == mPendingEnter
+                    && mListener.mPrimary.token.equals(change.getContainer())
+                    || mListener.mSecondary.token.equals(change.getContainer())) {
+                t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+                        change.getStartAbsBounds().height());
+                if (mListener.mPrimary.token.equals(change.getContainer())) {
+                    // Move layer to top since we want it above the oversized home task during
+                    // animation even though home task is on top in hierarchy.
+                    t.setLayer(leash, info.getChanges().size() + 1);
+                }
+            }
+            boolean isOpening = Transitions.isOpeningType(info.getType());
+            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+                // fade in
+                startExampleAnimation(leash, true /* show */);
+            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+                // fade out
+                if (transition == mPendingDismiss && mDismissFromSnap) {
+                    // Dismissing via snap-to-top/bottom means that the dismissed task is already
+                    // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+                    // and don't animate it so it doesn't pop-in when reparented.
+                    t.setAlpha(leash, 0.f);
+                } else {
+                    startExampleAnimation(leash, false /* show */);
+                }
+            }
+        }
+        if (transition == mPendingEnter) {
+            // If entering, check if we should enter into minimized or normal split
+            boolean homeIsVisible = false;
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (change.getTaskInfo() == null
+                        || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) {
+                    continue;
+                }
+                homeIsVisible = change.getMode() == TRANSIT_OPEN
+                        || change.getMode() == TRANSIT_TO_FRONT
+                        || change.getMode() == TRANSIT_CHANGE;
+                break;
+            }
+            mSplitScreen.finishEnterSplitTransition(homeIsVisible);
+        }
+        t.apply();
+        onFinish();
+        return true;
+    }
+
+    @ExternalThread
+    void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
+            boolean dismissOrMaximize, boolean snapped) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
+        mTransitions.getMainExecutor().execute(() -> {
+            mDismissFromSnap = snapped;
+            mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this);
+        });
+    }
+
+    private void onFinish() {
+        if (!mAnimations.isEmpty()) return;
+        mFinishTransaction.apply();
+        mTransactionPool.release(mFinishTransaction);
+        mFinishTransaction = null;
+        mFinishCallback.run();
+        mFinishCallback = null;
+        if (mAnimatingTransition == mPendingEnter) {
+            mPendingEnter = null;
+        }
+        if (mAnimatingTransition == mPendingDismiss) {
+            mSplitScreen.onDismissSplit();
+            mPendingDismiss = null;
+        }
+        mDismissFromSnap = false;
+        mAnimatingTransition = null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 68da35d..90a8de0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -39,6 +39,7 @@
 import android.window.WindowOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.wm.shell.Transitions;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 import java.util.ArrayList;
@@ -90,7 +91,11 @@
 
     void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles,
             LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) {
-        mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */);
+        } else {
+            mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
+        }
     }
 
     public void setResizing(final boolean resizing) {
@@ -181,6 +186,18 @@
         return isHomeResizable;
     }
 
+    /** @see #buildEnterSplit */
+    boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) {
+        // Set launchtile first so that any stack created after
+        // getAllRootTaskInfos and before reparent (even if unlikely) are placed
+        // correctly.
+        mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
+        applySyncTransaction(wct);
+        return isHomeResizable;
+    }
+
     /**
      * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
      * This assumes there is already something in the primary split since that is usually what
@@ -189,14 +206,10 @@
      *
      * @return whether the home stack is resizable
      */
-    boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) {
-        // Set launchtile first so that any stack created after
-        // getAllRootTaskInfos and before reparent (even if unlikely) are placed
-        // correctly.
-        mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
+    boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles,
+            LegacySplitDisplayLayout layout) {
         List<ActivityManager.RunningTaskInfo> rootTasks =
                 mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
         if (rootTasks.isEmpty()) {
             return false;
         }
@@ -215,48 +228,60 @@
             // Since this iterates from bottom to top, update topHomeTask for every fullscreen task
             // so it will be left with the status of the top one.
             topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
-            wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
+            outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
         }
         // Move the secondary split-forward.
-        wct.reorder(tiles.mSecondary.token, true /* onTop */);
-        boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
-        if (topHomeTask != null) {
+        outWct.reorder(tiles.mSecondary.token, true /* onTop */);
+        boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */,
+                outWct);
+        if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) {
             // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
             // is enabled, this temporarily syncs the home surface position with offset until
             // sync transaction finishes.
-            wct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
+            outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
         }
-        applySyncTransaction(wct);
         return isHomeResizable;
     }
 
-    boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
+    static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
         final int atype = ti.getActivityType();
         return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
     }
 
+    /** @see #buildDismissSplit */
+    void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
+            boolean dismissOrMaximize) {
+        // Set launch root first so that any task created after getChildContainers and
+        // before reparent (pretty unlikely) are put into fullscreen.
+        mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+        // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
+        //                 plus specific APIs to clean this up.
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
+        applySyncTransaction(wct);
+    }
+
     /**
      * Reparents all tile members back to their display and resets home task override bounds.
      * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
      *                          split (thus resulting in the top of the secondary split becoming
      *                          fullscreen. {@code false} resolves the other way.
      */
-    void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
+    static void buildDismissSplit(WindowContainerTransaction outWct,
+            LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
             boolean dismissOrMaximize) {
-        // Set launch root first so that any task created after getChildContainers and
-        // before reparent (pretty unlikely) are put into fullscreen.
-        mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
         // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
         //                 plus specific APIs to clean this up.
+        final TaskOrganizer taskOrg = tiles.getTaskOrganizer();
         List<ActivityManager.RunningTaskInfo> primaryChildren =
-                mTaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
+                taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
         List<ActivityManager.RunningTaskInfo> secondaryChildren =
-                mTaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
+                taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
         // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
         // as a result, the above will not capture any tasks; yet, we need to clean-up the
         // home task bounds.
         List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
-                mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
+                taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
         // Filter out the root split tasks
         freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token)
                 || p.token.equals(tiles.mPrimary.token));
@@ -265,11 +290,10 @@
                 && freeHomeAndRecents.isEmpty()) {
             return;
         }
-        WindowContainerTransaction wct = new WindowContainerTransaction();
         if (dismissOrMaximize) {
             // Dismissing, so move all primary split tasks first
             for (int i = primaryChildren.size() - 1; i >= 0; --i) {
-                wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                outWct.reparent(primaryChildren.get(i).token, null /* parent */,
                         true /* onTop */);
             }
             boolean homeOnTop = false;
@@ -277,16 +301,16 @@
             // order within the secondary split.
             for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
                 final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
-                wct.reparent(ti.token, null /* parent */, true /* onTop */);
+                outWct.reparent(ti.token, null /* parent */, true /* onTop */);
                 if (isHomeOrRecentTask(ti)) {
-                    wct.setBounds(ti.token, null);
-                    wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
+                    outWct.setBounds(ti.token, null);
+                    outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
                     if (i == 0) {
                         homeOnTop = true;
                     }
                 }
             }
-            if (homeOnTop) {
+            if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) {
                 // Translate/update-crop of secondary out-of-band with sync transaction -- instead
                 // play this in sync with new home-app frame because until BALST is enabled this
                 // shows up on screen before the syncTransaction returns.
@@ -304,7 +328,7 @@
                         layout.mDisplayLayout.height());
                 crop.offset(-posX, -posY);
                 sft.setWindowCrop(tiles.mSecondarySurface, crop);
-                wct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
+                outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
             }
         } else {
             // Maximize, so move non-home secondary split first
@@ -312,7 +336,7 @@
                 if (isHomeOrRecentTask(secondaryChildren.get(i))) {
                     continue;
                 }
-                wct.reparent(secondaryChildren.get(i).token, null /* parent */,
+                outWct.reparent(secondaryChildren.get(i).token, null /* parent */,
                         true /* onTop */);
             }
             // Find and place home tasks in-between. This simulates the fact that there was
@@ -320,24 +344,23 @@
             for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
                 final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
                 if (isHomeOrRecentTask(ti)) {
-                    wct.reparent(ti.token, null /* parent */, true /* onTop */);
+                    outWct.reparent(ti.token, null /* parent */, true /* onTop */);
                     // reset bounds and mode too
-                    wct.setBounds(ti.token, null);
-                    wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
+                    outWct.setBounds(ti.token, null);
+                    outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
                 }
             }
             for (int i = primaryChildren.size() - 1; i >= 0; --i) {
-                wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                outWct.reparent(primaryChildren.get(i).token, null /* parent */,
                         true /* onTop */);
             }
         }
         for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
-            wct.setBounds(freeHomeAndRecents.get(i).token, null);
-            wct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
+            outWct.setBounds(freeHomeAndRecents.get(i).token, null);
+            outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
         }
         // Reset focusable to true
-        wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
-        applySyncTransaction(wct);
+        outWct.setFocusable(tiles.mPrimary.token, true /* focusable */);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 9639096..146f231 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -203,7 +203,7 @@
             mSurfaceTransactionHelper = helper;
         }
 
-        OneHandedTransitionAnimator<T> setOneHandedAnimationCallbacks(
+        OneHandedTransitionAnimator<T> addOneHandedAnimationCallback(
                 OneHandedAnimationCallback callback) {
             mOneHandedAnimationCallbacks.add(callback);
             return this;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
new file mode 100644
index 0000000..a74f476
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -0,0 +1,201 @@
+/*
+ * 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.onehanded;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayController;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages OneHanded color background layer areas.
+ * To avoid when turning the Dark theme on, users can not clearly identify
+ * the screen has entered one handed mode.
+ */
+public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
+        implements OneHandedTransitionCallback {
+    private static final String TAG = "OneHandedBackgroundPanelOrganizer";
+
+    private final Object mLock = new Object();
+    private final SurfaceSession mSurfaceSession = new SurfaceSession();
+    private final float[] mColor;
+    private final float mAlpha;
+    private final Rect mRect;
+    private final Handler mHandler;
+    private final Point mDisplaySize = new Point();
+    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mSurfaceControlTransactionFactory;
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean mIsShowing;
+    @Nullable
+    @GuardedBy("mLock")
+    private SurfaceControl mBackgroundSurface;
+    @Nullable
+    @GuardedBy("mLock")
+    private SurfaceControl mParentLeash;
+
+    private final OneHandedAnimationCallback mOneHandedAnimationCallback =
+            new OneHandedAnimationCallback() {
+                @Override
+                public void onOneHandedAnimationStart(
+                        OneHandedAnimationController.OneHandedTransitionAnimator animator) {
+                    mHandler.post(() -> showBackgroundPanelLayer());
+                }
+            };
+
+    @Override
+    public void onStopFinished(Rect bounds) {
+        mHandler.post(() -> removeBackgroundPanelLayer());
+    }
+
+    public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController,
+            Executor executor) {
+        super(executor);
+        displayController.getDisplay(DEFAULT_DISPLAY).getRealSize(mDisplaySize);
+        final Resources res = context.getResources();
+        final float defaultRGB = res.getFloat(R.dimen.config_one_handed_background_rgb);
+        mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
+        mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
+        mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        mHandler = new Handler();
+        mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+    }
+
+    @Override
+    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+            @NonNull SurfaceControl leash) {
+        synchronized (mLock) {
+            if (mParentLeash == null) {
+                mParentLeash = leash;
+            } else {
+                throw new RuntimeException("There should be only one DisplayArea for "
+                        + "the one-handed mode background panel");
+            }
+        }
+    }
+
+    OneHandedAnimationCallback getOneHandedAnimationCallback() {
+        return mOneHandedAnimationCallback;
+    }
+
+    @Override
+    public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
+        synchronized (mLock) {
+            final List<DisplayAreaAppearedInfo> displayAreaInfos;
+            displayAreaInfos = super.registerOrganizer(displayAreaFeature);
+            for (int i = 0; i < displayAreaInfos.size(); i++) {
+                final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
+                onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
+            }
+            return displayAreaInfos;
+        }
+    }
+
+    @Override
+    public void unregisterOrganizer() {
+        synchronized (mLock) {
+            super.unregisterOrganizer();
+            mParentLeash = null;
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting
+    SurfaceControl getBackgroundSurface() {
+        synchronized (mLock) {
+            if (mParentLeash == null) {
+                return null;
+            }
+
+            if (mBackgroundSurface == null) {
+                mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
+                        .setParent(mParentLeash)
+                        .setColorLayer()
+                        .setFormat(PixelFormat.RGBA_8888)
+                        .setOpaque(false)
+                        .setName("one-handed-background-panel")
+                        .setCallsite("OneHandedBackgroundPanelOrganizer")
+                        .build();
+            }
+            return mBackgroundSurface;
+        }
+    }
+
+    @VisibleForTesting
+    void showBackgroundPanelLayer() {
+        synchronized (mLock) {
+            if (mIsShowing) {
+                return;
+            }
+
+            if (getBackgroundSurface() == null) {
+                Log.w(TAG, "mBackgroundSurface is null !");
+                return;
+            }
+
+            SurfaceControl.Transaction transaction =
+                    mSurfaceControlTransactionFactory.getTransaction();
+            transaction.setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
+                    .setColor(mBackgroundSurface, mColor)
+                    .setAlpha(mBackgroundSurface, mAlpha)
+                    .show(mBackgroundSurface)
+                    .apply();
+            transaction.close();
+            mIsShowing = true;
+        }
+    }
+
+    @VisibleForTesting
+    void removeBackgroundPanelLayer() {
+        synchronized (mLock) {
+            if (mBackgroundSurface == null) {
+                return;
+            }
+
+            SurfaceControl.Transaction transaction =
+                    mSurfaceControlTransactionFactory.getTransaction();
+            transaction.remove(mBackgroundSurface);
+            transaction.apply();
+            transaction.close();
+            mBackgroundSurface = null;
+            mIsShowing = false;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 69d8db2..00605d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -77,6 +77,7 @@
 
     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
     private final AccessibilityManager mAccessibilityManager;
+    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
 
     /**
      * Handle rotation based on OnDisplayChangingListener callback
@@ -204,17 +205,22 @@
         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler();
         OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
                 context, displayController);
+        OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
+                new OneHandedBackgroundPanelOrganizer(context, displayController, executor);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
-                context, displayController, animationController, tutorialHandler, executor);
+                context, displayController, animationController, tutorialHandler, executor,
+                oneHandedBackgroundPanelOrganizer);
         IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
-        return new OneHandedController(context, displayController, organizer, touchHandler,
-                tutorialHandler, gestureHandler, overlayManager, taskStackListener);
+        return new OneHandedController(context, displayController,
+                oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
+                gestureHandler, overlayManager, taskStackListener);
     }
 
     @VisibleForTesting
     OneHandedController(Context context,
             DisplayController displayController,
+            OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
@@ -222,6 +228,7 @@
             IOverlayManager overlayManager,
             TaskStackListenerImpl taskStackListener) {
         mContext = context;
+        mBackgroundPanelOrganizer = backgroundPanelOrganizer;
         mDisplayAreaOrganizer = displayAreaOrganizer;
         mDisplayController = displayController;
         mTouchHandler = touchHandler;
@@ -355,6 +362,7 @@
         mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mGestureHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler);
+        mDisplayAreaOrganizer.registerTransitionCallback(mBackgroundPanelOrganizer);
     }
 
     private void setupSettingObservers() {
@@ -405,9 +413,12 @@
         }
         // TODO Be aware to unregisterOrganizer() after animation finished
         mDisplayAreaOrganizer.unregisterOrganizer();
+        mBackgroundPanelOrganizer.unregisterOrganizer();
         if (mIsOneHandedEnabled) {
             mDisplayAreaOrganizer.registerOrganizer(
                     OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
+            mBackgroundPanelOrganizer.registerOrganizer(
+                    OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
         }
         mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
         mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 7fb1faa..7873318 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -86,6 +86,7 @@
             mSurfaceControlTransactionFactory;
     private OneHandedTutorialHandler mTutorialHandler;
     private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
+    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
 
     @VisibleForTesting
     OneHandedAnimationCallback mOneHandedAnimationCallback =
@@ -152,7 +153,8 @@
     public OneHandedDisplayAreaOrganizer(Context context,
             DisplayController displayController,
             OneHandedAnimationController animationController,
-            OneHandedTutorialHandler tutorialHandler, Executor executor) {
+            OneHandedTutorialHandler tutorialHandler, Executor executor,
+            OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer) {
         super(executor);
         mUpdateHandler = new Handler(OneHandedThread.get().getLooper(), mUpdateCallback);
         mAnimationController = animationController;
@@ -166,6 +168,7 @@
                         animationDurationConfig);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mTutorialHandler = tutorialHandler;
+        mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer;
     }
 
     @Override
@@ -300,8 +303,10 @@
                     mAnimationController.getAnimator(leash, fromBounds, toBounds);
             if (animator != null) {
                 animator.setTransitionDirection(direction)
-                        .setOneHandedAnimationCallbacks(mOneHandedAnimationCallback)
-                        .setOneHandedAnimationCallbacks(mTutorialHandler.getAnimationCallback())
+                        .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
+                        .addOneHandedAnimationCallback(mTutorialHandler.getAnimationCallback())
+                        .addOneHandedAnimationCallback(
+                                mBackgroundPanelOrganizer.getOneHandedAnimationCallback())
                         .setDuration(durationMs)
                         .start();
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 951a688..aa4ec17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -88,7 +88,7 @@
         mDisplayController = displayController;
         displayController.addDisplayChangingController(this);
         mNavGestureHeight = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.navigation_bar_gesture_height);
+                com.android.internal.R.dimen.navigation_bar_gesture_larger_height);
         mDragDistThreshold = context.getResources().getDimensionPixelSize(
                 R.dimen.gestures_onehanded_drag_threshold);
         final float slop = ViewConfiguration.get(context).getScaledTouchSlop();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index d59aec2..8f8ec47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -38,11 +38,11 @@
 public class PinnedStackListenerForwarder {
 
     private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl();
-    private final ShellExecutor mShellMainExecutor;
+    private final ShellExecutor mMainExecutor;
     private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>();
 
-    public PinnedStackListenerForwarder(ShellExecutor shellMainExecutor) {
-        mShellMainExecutor = shellMainExecutor;
+    public PinnedStackListenerForwarder(ShellExecutor mainExecutor) {
+        mMainExecutor = mainExecutor;
     }
 
     /** Adds a listener to receive updates from the WindowManagerService. */
@@ -94,35 +94,35 @@
     private class PinnedStackListenerImpl extends IPinnedStackListener.Stub {
         @Override
         public void onMovementBoundsChanged(boolean fromImeAdjustment) {
-            mShellMainExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 PinnedStackListenerForwarder.this.onMovementBoundsChanged(fromImeAdjustment);
             });
         }
 
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            mShellMainExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
 
         @Override
         public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
-            mShellMainExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 PinnedStackListenerForwarder.this.onActionsChanged(actions);
             });
         }
 
         @Override
         public void onActivityHidden(ComponentName componentName) {
-            mShellMainExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 PinnedStackListenerForwarder.this.onActivityHidden(componentName);
             });
         }
 
         @Override
         public void onAspectRatioChanged(float aspectRatio) {
-            mShellMainExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
             });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 33439a4..6b6bf5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -155,7 +155,7 @@
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
-            ShellExecutor shellMainExecutor) {
+            ShellExecutor mainExecutor) {
         // Initialize the Pip input consumer
         mContext = context;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -186,7 +186,7 @@
         mFloatingContentCoordinator = floatingContentCoordinator;
         mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
-                this::onAccessibilityShowMenu, this::updateMovementBounds, shellMainExecutor);
+                this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor);
 
         mPipUiEventLogger = pipUiEventLogger;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 786a1fd..8e24e0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -222,7 +222,11 @@
         }
 
         Context context = mContext;
-        final int theme = activityInfo.getThemeResource();
+        int theme = activityInfo.getThemeResource();
+        if (theme == 0) {
+            // replace with the default theme if the application didn't set
+            theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+        }
         if (DEBUG_SPLASH_SCREEN) {
             Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
                     + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 5125a39..796c8c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -31,6 +31,10 @@
 
 // Test App > Ime Activity
 const val TEST_APP_IME_ACTIVITY_LABEL = "ImeApp"
+const val TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME =
+        "com.android.wm.shell.flicker.testapp.action.OPEN_IME"
+const val TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME =
+        "com.android.wm.shell.flicker.testapp.action.CLOSE_IME"
 // Test App > Test Activity
 const val TEST_APP_FIXED_ACTIVITY_LABEL = "FixedApp"
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 6fd1df3..f8efd0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -62,8 +62,9 @@
     val ui: UiObject2?
         get() = uiDevice.findObject(appSelector)
 
-    fun launchViaIntent(stringExtras: Map<String, String> = mapOf()) {
+    fun launchViaIntent(action: String? = null, stringExtras: Map<String, String> = mapOf()) {
         val intent = openAppIntent
+        intent.action = action
         stringExtras.forEach() {
             intent.putExtra(it.key, it.value)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index c546a4d..eb20d73 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -20,6 +20,8 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
+import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME
+import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME
 import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_LABEL
 import com.android.wm.shell.flicker.testapp.Components
 import org.junit.Assert
@@ -32,15 +34,27 @@
         Components.ImeActivity()
 ) {
     fun openIME() {
-        val editText = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "plain_text_input")),
-                FIND_TIMEOUT)
-        Assert.assertNotNull("Text field not found, this usually happens when the device " +
-                "was left in an unknown state (e.g. in split screen)", editText)
-        editText.click()
+        if (!isTelevision) {
+            val editText = uiDevice.wait(
+                    Until.findObject(By.res(getPackage(), "plain_text_input")),
+                    FIND_TIMEOUT)
+            Assert.assertNotNull("Text field not found, this usually happens when the device " +
+                    "was left in an unknown state (e.g. in split screen)", editText)
+            editText.click()
+        } else {
+            // If we do the same thing as above - editText.click() - on TV, that's going to force TV
+            // into the touch mode. We really don't want that.
+            launchViaIntent(action = TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME)
+        }
     }
 
     fun closeIME() {
-        uiDevice.pressBack()
+        if (!isTelevision) {
+            uiDevice.pressBack()
+        } else {
+            // While pressing the back button should close the IME on TV as well, it may also lead
+            // to the app closing. So let's instead just ask the app to close the IME.
+            launchViaIntent(action = TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME)
+        }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 28ed343..5549330 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -53,6 +53,7 @@
         <activity android:name=".ImeActivity"
                  android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
                  android:label="ImeApp"
+                 android:launchMode="singleTop"
                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
index 8567287..59c64a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
@@ -17,10 +17,21 @@
 package com.android.wm.shell.flicker.testapp;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.view.View;
 import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
 
 public class ImeActivity extends Activity {
+    private static final String ACTION_OPEN_IME =
+            "com.android.wm.shell.flicker.testapp.action.OPEN_IME";
+    private static final String ACTION_CLOSE_IME =
+            "com.android.wm.shell.flicker.testapp.action.CLOSE_IME";
+
+    private InputMethodManager mImm;
+    private View mEditText;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -29,5 +40,27 @@
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         getWindow().setAttributes(p);
         setContentView(R.layout.activity_ime);
+
+        mEditText = findViewById(R.id.plain_text_input);
+        mImm = getSystemService(InputMethodManager.class);
+
+        handleIntent(getIntent());
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        final String action = intent.getAction();
+        if (ACTION_OPEN_IME.equals(action)) {
+            mEditText.requestFocus();
+            mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
+        } else if (ACTION_CLOSE_IME.equals(action)) {
+            mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+            mEditText.clearFocus();
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 01b5204..eb03ab0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -99,7 +99,7 @@
 
         when(mOrganizer.getExecutor()).thenReturn(mExecutor);
         mTaskView = new TaskView(mContext, mOrganizer);
-        mTaskView.setListener(mViewListener);
+        mTaskView.setListener(mExecutor, mViewListener);
     }
 
     @After
@@ -112,9 +112,9 @@
     @Test
     public void testSetPendingListener_throwsException() {
         TaskView taskView = new TaskView(mContext, mOrganizer);
-        taskView.setListener(mViewListener);
+        taskView.setListener(mExecutor, mViewListener);
         try {
-            taskView.setListener(mViewListener);
+            taskView.setListener(mExecutor, mViewListener);
         } catch (IllegalStateException e) {
             // pass
             return;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 0693052..bde04b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -71,7 +71,7 @@
 
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
-                PendingIntent.getActivity(mContext, 0, target, 0),
+                PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE),
                         Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
                 .build();
         when(mSbn.getNotification()).thenReturn(mNotif);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index 595440f..f10dc16 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -27,6 +27,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.common.ShellExecutor;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,12 +46,14 @@
     private HideDisplayCutoutController mHideDisplayCutoutController;
     @Mock
     private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+    @Mock
+    private ShellExecutor mMockMainExecutor;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mHideDisplayCutoutController = new HideDisplayCutoutController(
-                mContext, mMockDisplayAreaOrganizer);
+                mContext, mMockDisplayAreaOrganizer, mMockMainExecutor);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index e0c835b..9637570 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -50,6 +50,7 @@
 
 import com.android.internal.R;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -72,6 +73,9 @@
     private DisplayController mMockDisplayController;
     private HideDisplayCutoutOrganizer mOrganizer;
 
+    @Mock
+    private ShellExecutor mMockMainExecutor;
+
     private DisplayAreaInfo mDisplayAreaInfo;
     private SurfaceControl mLeash;
 
@@ -93,7 +97,7 @@
         when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
 
         HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(
-                mContext, mMockDisplayController, Runnable::run);
+                mContext, mMockDisplayController, mMockMainExecutor);
         mOrganizer = Mockito.spy(organizer);
         doNothing().when(mOrganizer).unregisterOrganizer();
         doNothing().when(mOrganizer).applyBoundsAndOffsets(any(), any(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
new file mode 100644
index 0000000..e9c4af1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.onehanded;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.DisplayController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
+    private DisplayAreaInfo mDisplayAreaInfo;
+    private Display mDisplay;
+    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
+    private WindowContainerToken mToken;
+    private SurfaceControl mLeash;
+    private TestableLooper mTestableLooper;
+
+    @Mock
+    IWindowContainerToken mMockRealToken;
+    @Mock
+    DisplayController mMockDisplayController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+        mToken = new WindowContainerToken(mMockRealToken);
+        mLeash = new SurfaceControl();
+        mDisplay = mContext.getDisplay();
+        when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
+        mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
+                FEATURE_ONE_HANDED_BACKGROUND_PANEL);
+
+        mBackgroundPanelOrganizer = new OneHandedBackgroundPanelOrganizer(mContext,
+                mMockDisplayController, Runnable::run);
+    }
+
+    @Test
+    public void testOnDisplayAreaAppeared() {
+        mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull();
+    }
+
+    @Test
+    public void testUnregisterOrganizer() {
+        mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+        mTestableLooper.processAllMessages();
+        mBackgroundPanelOrganizer.unregisterOrganizer();
+
+        assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNull();
+    }
+
+    @Test
+    public void testShowBackgroundLayer() {
+        mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+        mBackgroundPanelOrganizer.showBackgroundPanelLayer();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mBackgroundPanelOrganizer.mIsShowing).isTrue();
+    }
+
+    @Test
+    public void testRemoveBackgroundLayer() {
+        mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+        mBackgroundPanelOrganizer.removeBackgroundPanelLayer();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mBackgroundPanelOrganizer.mIsShowing).isFalse();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index e37e154..20184bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -54,6 +54,8 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
+    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
+    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     OneHandedTouchHandler mMockTouchHandler;
@@ -75,6 +77,7 @@
         OneHandedController oneHandedController = new OneHandedController(
                 mContext,
                 mMockDisplayController,
+                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
@@ -94,7 +97,7 @@
                 mContext);
         OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
                 mContext, mMockDisplayController, animationController, mMockTutorialHandler,
-                Runnable::run);
+                Runnable::run, mMockBackgroundOrganizer);
 
         assertThat(displayAreaOrganizer.isInOneHanded()).isFalse();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 5d742b3..3d9fad9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -80,6 +80,8 @@
     SurfaceControl mMockLeash;
     @Mock
     WindowContainerTransaction mMockWindowContainerTransaction;
+    @Mock
+    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
 
     Handler mSpyUpdateHandler;
     Handler.Callback mUpdateCallback = (msg) -> false;
@@ -103,7 +105,7 @@
                 mMockSurfaceTransactionHelper);
         when(mMockAnimator.isRunning()).thenReturn(true);
         when(mMockAnimator.setDuration(anyInt())).thenReturn(mFakeAnimator);
-        when(mMockAnimator.setOneHandedAnimationCallbacks(any())).thenReturn(mFakeAnimator);
+        when(mMockAnimator.addOneHandedAnimationCallback(any())).thenReturn(mFakeAnimator);
         when(mMockAnimator.setTransitionDirection(anyInt())).thenReturn(mFakeAnimator);
         when(mMockLeash.getWidth()).thenReturn(DISPLAY_WIDTH);
         when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT);
@@ -112,7 +114,7 @@
                 mMockDisplayController,
                 mMockAnimationController,
                 mTutorialHandler,
-                Runnable::run);
+                Runnable::run, mMockBackgroundOrganizer);
         mSpyUpdateHandler = spy(new Handler(OneHandedThread.get().getLooper(), mUpdateCallback));
         mDisplayAreaOrganizer.setUpdateHandler(mSpyUpdateHandler);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index ba8c737..b187dc9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -45,6 +45,8 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
+    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
+    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     IOverlayManager mMockOverlayManager;
@@ -59,6 +61,7 @@
         mOneHandedController = new OneHandedController(
                 getContext(),
                 mMockDisplayController,
+                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mTouchHandler,
                 mTutorialHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 4efaebf..b4cfbc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -73,7 +73,7 @@
     private PipUiEventLogger mPipUiEventLogger;
 
     @Mock
-    private ShellExecutor mShellMainExecutor;
+    private ShellExecutor mMainExecutor;
 
     private PipBoundsState mPipBoundsState;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -98,7 +98,7 @@
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
                 mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
-                mFloatingContentCoordinator, mPipUiEventLogger, mShellMainExecutor);
+                mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 07115c2..c9537af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -15,14 +15,13 @@
  */
 package unittest.src.com.android.wm.shell.startingsurface;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +71,7 @@
 
     static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
         int mAddWindowForTask = 0;
+        int mViewThemeResId;
 
         TestStartingSurfaceDrawer(Context context, ShellExecutor executor) {
             super(context, executor);
@@ -82,6 +82,7 @@
                 View view, WindowManager wm, WindowManager.LayoutParams params) {
             // listen for addView
             mAddWindowForTask = taskId;
+            mViewThemeResId = view.getContext().getThemeResId();
         }
 
         @Override
@@ -121,7 +122,7 @@
         final int taskId = 1;
         final Handler mainLoop = new Handler(Looper.getMainLooper());
         final StartingWindowInfo windowInfo =
-                createWindowInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+                createWindowInfo(taskId, android.R.style.Theme);
         mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
         waitHandlerIdle(mainLoop);
         verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
@@ -133,12 +134,24 @@
         assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
     }
 
-    private StartingWindowInfo createWindowInfo(int taskId, int windowingMode) {
+    @Test
+    public void testFallbackDefaultTheme() {
+        final int taskId = 1;
+        final Handler mainLoop = new Handler(Looper.getMainLooper());
+        final StartingWindowInfo windowInfo =
+                createWindowInfo(taskId, 0);
+        mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
+        waitHandlerIdle(mainLoop);
+        verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+        assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
+    }
+
+    private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
         StartingWindowInfo windowInfo = new StartingWindowInfo();
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
         info.packageName = "test";
-        info.theme = android.R.style.Theme;
+        info.theme = themeResId;
         final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.topActivityInfo = info;
         taskInfo.taskId = taskId;
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8330363..b54f7d8 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -100,6 +100,11 @@
                 "libz",
             ],
         },
+        linux_glibc: {
+            srcs: [
+                "CursorWindow.cpp",
+            ],
+        },
         windows: {
             enabled: true,
         },
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index cb56a51..011a0de 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -385,7 +385,7 @@
     return {};
   }
   
-  auto overlay_path = loaded_idmap->OverlayApkPath();
+  auto overlay_path = std::string(loaded_idmap->OverlayApkPath());
   auto assets = ZipAssetsProvider::Create(overlay_path);
   return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY,
                              nullptr /* override_asset */, std::move(idmap_asset),
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index bec80a7..3f06000 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -157,7 +157,8 @@
           // The target package must precede the overlay package in the apk assets paths in order
           // to take effect.
           const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
-          auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
+          auto target_package_iter = apk_assets_package_ids.find(
+              std::string(loaded_idmap->TargetApkPath()));
           if (target_package_iter == apk_assets_package_ids.end()) {
              LOG(INFO) << "failed to find target package for overlay "
                        << loaded_idmap->OverlayApkPath();
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index a613095..73e040c 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -36,13 +36,51 @@
 
 namespace android {
 
-uint32_t round_to_4_bytes(uint32_t size) {
-  return size + (4U - (size % 4U)) % 4U;
-}
+// See frameworks/base/cmds/idmap2/include/idmap2/Idmap.h for full idmap file format specification.
+struct Idmap_header {
+  // Always 0x504D4449 ('IDMP')
+  uint32_t magic;
+  uint32_t version;
 
-size_t Idmap_header::Size() const {
-  return sizeof(Idmap_header) + sizeof(uint8_t) * round_to_4_bytes(dtohl(debug_info_size));
-}
+  uint32_t target_crc32;
+  uint32_t overlay_crc32;
+
+  uint32_t fulfilled_policies;
+  uint32_t enforce_overlayable;
+
+  // overlay_path, target_path, and other string values encoded in the idmap header and read and
+  // stored in separate structures. This allows the idmap header data to be casted to this struct
+  // without having to read/store each header entry separately.
+};
+
+struct Idmap_data_header {
+  uint8_t target_package_id;
+  uint8_t overlay_package_id;
+
+  // Padding to ensure 4 byte alignment for target_entry_count
+  uint16_t p0;
+
+  uint32_t target_entry_count;
+  uint32_t target_inline_entry_count;
+  uint32_t overlay_entry_count;
+
+  uint32_t string_pool_index_offset;
+};
+
+struct Idmap_target_entry {
+  uint32_t target_id;
+  uint32_t overlay_id;
+};
+
+struct Idmap_target_entry_inline {
+  uint32_t target_id;
+  Res_value value;
+};
+
+struct Idmap_overlay_entry {
+  uint32_t overlay_id;
+  uint32_t target_id;
+};
 
 OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap)
     : data_header_(loaded_idmap->data_header_),
@@ -155,140 +193,133 @@
   return {};
 }
 
-static bool is_word_aligned(const void* data) {
-  return (reinterpret_cast<uintptr_t>(data) & 0x03U) == 0U;
+namespace {
+template <typename T>
+const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const std::string& label,
+                  size_t count = 1) {
+  if (!util::IsFourByteAligned(*in_out_data_ptr)) {
+    LOG(ERROR) << "Idmap " << label << " is not word aligned.";
+    return {};
+  }
+  if ((*in_out_size / sizeof(T)) < count) {
+    LOG(ERROR) << "Idmap too small for the number of " << label << " entries ("
+               << count << ").";
+    return nullptr;
+  }
+  auto data_ptr = *in_out_data_ptr;
+  const size_t read_size = sizeof(T) * count;
+  *in_out_data_ptr += read_size;
+  *in_out_size -= read_size;
+  return reinterpret_cast<const T*>(data_ptr);
 }
 
-static bool IsValidIdmapHeader(const StringPiece& data) {
-  if (!is_word_aligned(data.data())) {
-    LOG(ERROR) << "Idmap header is not word aligned.";
-    return false;
+std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size_t* in_out_size,
+                                           const std::string& label) {
+  const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label + " length");
+  if (len == nullptr) {
+    return {};
   }
-
-  if (data.size() < sizeof(Idmap_header)) {
-    LOG(ERROR) << "Idmap header is too small.";
-    return false;
+  const auto* data = ReadType<char>(in_out_data_ptr, in_out_size, label, *len);
+  if (data == nullptr) {
+    return {};
   }
-
-  auto header = reinterpret_cast<const Idmap_header*>(data.data());
-  if (dtohl(header->magic) != kIdmapMagic) {
-    LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
-                               dtohl(header->magic), kIdmapMagic);
-    return false;
+  // Strings are padded to the next 4 byte boundary.
+  const uint32_t padding_size = (4U - ((size_t)*in_out_data_ptr & 0x3U)) % 4U;
+  for (uint32_t i = 0; i < padding_size; i++) {
+    if (**in_out_data_ptr != 0) {
+      LOG(ERROR) << " Idmap padding of " << label << " is non-zero.";
+      return {};
+    }
+    *in_out_data_ptr += sizeof(uint8_t);
+    *in_out_size -= sizeof(uint8_t);
   }
-
-  if (dtohl(header->version) != kIdmapCurrentVersion) {
-    // We are strict about versions because files with this format are auto-generated and don't need
-    // backwards compatibility.
-    LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
-                               dtohl(header->version), kIdmapCurrentVersion);
-    return false;
-  }
-
-  return true;
+  return std::string_view(data, *len);
+}
 }
 
 LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
-                         const time_t last_mod_time,
                          const Idmap_header* header,
                          const Idmap_data_header* data_header,
                          const Idmap_target_entry* target_entries,
                          const Idmap_target_entry_inline* target_inline_entries,
                          const Idmap_overlay_entry* overlay_entries,
-                         ResStringPool* string_pool)
+                         std::unique_ptr<ResStringPool>&& string_pool,
+                         std::string_view overlay_apk_path,
+                         std::string_view target_apk_path)
      : header_(header),
        data_header_(data_header),
        target_entries_(target_entries),
        target_inline_entries_(target_inline_entries),
        overlay_entries_(overlay_entries),
-       string_pool_(string_pool),
+       string_pool_(std::move(string_pool)),
        idmap_path_(std::move(idmap_path)),
-       idmap_last_mod_time_(last_mod_time) {
-
-  size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
-                          arraysize(header_->overlay_path));
-  overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
-
-  length = strnlen(reinterpret_cast<const char*>(header_->target_path),
-                          arraysize(header_->target_path));
-  target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length);
-}
+       overlay_apk_path_(overlay_apk_path),
+       target_apk_path_(target_apk_path),
+       idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
 
 std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
                                                      const StringPiece& idmap_data) {
   ATRACE_CALL();
-  if (!IsValidIdmapHeader(idmap_data)) {
+  size_t data_size = idmap_data.size();
+  auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
+
+  // Parse the idmap header
+  auto header = ReadType<Idmap_header>(&data_ptr, &data_size, "header");
+  if (header == nullptr) {
+    return {};
+  }
+  if (dtohl(header->magic) != kIdmapMagic) {
+    LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
+                               dtohl(header->magic), kIdmapMagic);
+    return {};
+  }
+  if (dtohl(header->version) != kIdmapCurrentVersion) {
+    // We are strict about versions because files with this format are generated at runtime and
+    // don't need backwards compatibility.
+    LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
+                               dtohl(header->version), kIdmapCurrentVersion);
+    return {};
+  }
+  std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path");
+  if (!overlay_path) {
+    return {};
+  }
+  std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
+  if (!target_path) {
+    return {};
+  }
+  if (!ReadString(&data_ptr, &data_size, "target name") ||
+      !ReadString(&data_ptr, &data_size, "debug info")) {
     return {};
   }
 
-  auto header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
-  const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + header->Size();
-  size_t data_size = idmap_data.size() - header->Size();
-
-  // Currently idmap2 can only generate one data block.
-  auto data_header = reinterpret_cast<const Idmap_data_header*>(data_ptr);
-  data_ptr += sizeof(*data_header);
-  data_size -= sizeof(*data_header);
-
-  // Make sure there is enough space for the target entries declared in the header
-  const auto target_entries = reinterpret_cast<const Idmap_target_entry*>(data_ptr);
-  if (data_size / sizeof(Idmap_target_entry) <
-      static_cast<size_t>(dtohl(data_header->target_entry_count))) {
-    LOG(ERROR) << StringPrintf("Idmap too small for the number of target entries (%d)",
-                               (int)dtohl(data_header->target_entry_count));
+  // Parse the idmap data blocks. Currently idmap2 can only generate one data block.
+  auto data_header = ReadType<Idmap_data_header>(&data_ptr, &data_size, "data header");
+  if (data_header == nullptr) {
     return {};
   }
-
-  // Advance the data pointer past the target entries.
-  const size_t target_entry_size_bytes =
-      (dtohl(data_header->target_entry_count) * sizeof(Idmap_target_entry));
-  data_ptr += target_entry_size_bytes;
-  data_size -= target_entry_size_bytes;
-
-  // Make sure there is enough space for the target entries declared in the header.
-  const auto target_inline_entries = reinterpret_cast<const Idmap_target_entry_inline*>(data_ptr);
-  if (data_size / sizeof(Idmap_target_entry_inline) <
-      static_cast<size_t>(dtohl(data_header->target_inline_entry_count))) {
-    LOG(ERROR) << StringPrintf("Idmap too small for the number of target inline entries (%d)",
-                               (int)dtohl(data_header->target_inline_entry_count));
+  auto target_entries = ReadType<Idmap_target_entry>(&data_ptr, &data_size, "target",
+                                                     dtohl(data_header->target_entry_count));
+  if (target_entries == nullptr) {
     return {};
   }
-
-  // Advance the data pointer past the target entries.
-  const size_t target_inline_entry_size_bytes =
-      (dtohl(data_header->target_inline_entry_count) * sizeof(Idmap_target_entry_inline));
-  data_ptr += target_inline_entry_size_bytes;
-  data_size -= target_inline_entry_size_bytes;
-
-  // Make sure there is enough space for the overlay entries declared in the header.
-  const auto overlay_entries = reinterpret_cast<const Idmap_overlay_entry*>(data_ptr);
-  if (data_size / sizeof(Idmap_overlay_entry) <
-      static_cast<size_t>(dtohl(data_header->overlay_entry_count))) {
-    LOG(ERROR) << StringPrintf("Idmap too small for the number of overlay entries (%d)",
-                               (int)dtohl(data_header->overlay_entry_count));
+  auto target_inline_entries = ReadType<Idmap_target_entry_inline>(
+      &data_ptr, &data_size, "target inline", dtohl(data_header->target_inline_entry_count));
+  if (target_inline_entries == nullptr) {
     return {};
   }
-
-  // Advance the data pointer past the overlay entries.
-  const size_t overlay_entry_size_bytes =
-      (dtohl(data_header->overlay_entry_count) * sizeof(Idmap_overlay_entry));
-  data_ptr += overlay_entry_size_bytes;
-  data_size -= overlay_entry_size_bytes;
-
-  // Read the idmap string pool that holds the value of inline string entries.
-  uint32_t string_pool_size = dtohl(*reinterpret_cast<const uint32_t*>(data_ptr));
-  data_ptr += sizeof(uint32_t);
-  data_size -= sizeof(uint32_t);
-
-  if (data_size < string_pool_size) {
-    LOG(ERROR) << StringPrintf("Idmap too small for string pool (length %d)",
-                               (int)string_pool_size);
+  auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline",
+                                                       dtohl(data_header->overlay_entry_count));
+  if (overlay_entries == nullptr) {
     return {};
   }
-
+  std::optional<std::string_view> string_pool = ReadString(&data_ptr, &data_size, "string pool");
+  if (!string_pool) {
+    return {};
+  }
   auto idmap_string_pool = util::make_unique<ResStringPool>();
-  if (string_pool_size > 0) {
-    status_t err = idmap_string_pool->setTo(data_ptr, string_pool_size);
+  if (!string_pool->empty()) {
+    const status_t err = idmap_string_pool->setTo(string_pool->data(), string_pool->size());
     if (err != NO_ERROR) {
       LOG(ERROR) << "idmap string pool corrupt.";
       return {};
@@ -296,12 +327,10 @@
   }
 
   // Can't use make_unique because LoadedIdmap constructor is private.
-  auto loaded_idmap = std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header,
-                      data_header, target_entries, target_inline_entries, overlay_entries,
-                      idmap_string_pool.release()));
-
-  return std::move(loaded_idmap);
+  return std::unique_ptr<LoadedIdmap>(
+      new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
+                      target_inline_entries, overlay_entries, std::move(idmap_string_pool),
+                      *target_path, *overlay_path));
 }
 
 bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index fdab03b..fd9a8d13 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -31,6 +31,11 @@
 
 class LoadedIdmap;
 class IdmapResMap;
+struct Idmap_header;
+struct Idmap_data_header;
+struct Idmap_target_entry;
+struct Idmap_target_entry_inline;
+struct Idmap_overlay_entry;
 
 // A string pool for overlay apk assets. The string pool holds the strings of the overlay resources
 // table and additionally allows for loading strings from the idmap string pool. The idmap string
@@ -148,29 +153,29 @@
                                                  const StringPiece& idmap_data);
 
   // Returns the path to the IDMAP.
-  inline const std::string& IdmapPath() const {
+  std::string_view IdmapPath() const {
     return idmap_path_;
   }
 
   // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
-  inline const std::string& OverlayApkPath() const {
+  std::string_view OverlayApkPath() const {
     return overlay_apk_path_;
   }
 
   // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
-  inline const std::string& TargetApkPath() const {
+  std::string_view TargetApkPath() const {
     return target_apk_path_;
   }
 
   // Returns a mapping from target resource ids to overlay values.
-  inline const IdmapResMap GetTargetResourcesMap(
+  const IdmapResMap GetTargetResourcesMap(
       uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const {
     return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
                        target_assigned_package_id, overlay_ref_table);
   }
 
   // Returns a dynamic reference table for a loaded overlay package.
-  inline const OverlayDynamicRefTable GetOverlayDynamicRefTable(
+  const OverlayDynamicRefTable GetOverlayDynamicRefTable(
       uint8_t target_assigned_package_id) const {
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
@@ -190,22 +195,23 @@
   const Idmap_overlay_entry* overlay_entries_;
   const std::unique_ptr<ResStringPool> string_pool_;
 
-  const std::string idmap_path_;
-  std::string overlay_apk_path_;
-  std::string target_apk_path_;
-  const time_t idmap_last_mod_time_;
+  std::string idmap_path_;
+  std::string_view overlay_apk_path_;
+  std::string_view target_apk_path_;
+  time_t idmap_last_mod_time_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
 
   explicit LoadedIdmap(std::string&& idmap_path,
-                       time_t last_mod_time,
                        const Idmap_header* header,
                        const Idmap_data_header* data_header,
                        const Idmap_target_entry* target_entries,
                        const Idmap_target_entry_inline* target_inline_entries,
                        const Idmap_overlay_entry* overlay_entries,
-                       ResStringPool* string_pool);
+                       std::unique_ptr<ResStringPool>&& string_pool,
+                       std::string_view overlay_apk_path,
+                       std::string_view target_apk_path);
 
   friend OverlayStringPool;
 };
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index fb5f864..bfd564c 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -44,7 +44,7 @@
 namespace android {
 
 constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000005u;
+constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u;
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1700,56 +1700,6 @@
   return first;
 }
 
-struct Idmap_header {
-  // Always 0x504D4449 ('IDMP')
-  uint32_t magic;
-
-  uint32_t version;
-
-  uint32_t target_crc32;
-  uint32_t overlay_crc32;
-
-  uint32_t fulfilled_policies;
-  uint32_t enforce_overlayable;
-
-  uint8_t target_path[256];
-  uint8_t overlay_path[256];
-
-  uint32_t debug_info_size;
-  uint8_t debug_info[0];
-
-  size_t Size() const;
-};
-
-struct Idmap_data_header {
-  uint8_t target_package_id;
-  uint8_t overlay_package_id;
-
-  // Padding to ensure 4 byte alignment for target_entry_count
-  uint16_t p0;
-
-  uint32_t target_entry_count;
-  uint32_t target_inline_entry_count;
-  uint32_t overlay_entry_count;
-
-  uint32_t string_pool_index_offset;
-};
-
-struct Idmap_target_entry {
-  uint32_t target_id;
-  uint32_t overlay_id;
-};
-
-struct Idmap_target_entry_inline {
-  uint32_t target_id;
-  Res_value value;
-};
-
-struct Idmap_overlay_entry {
-  uint32_t overlay_id;
-  uint32_t target_id;
-};
-
 class AssetManager2;
 
 /**
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index aceeecc..c59b5b6 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -128,10 +128,14 @@
 std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
 
 template <typename T>
-bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
+inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
   return ((size_t)data.unsafe_ptr() & 0x3U) == 0;
 }
 
+inline bool IsFourByteAligned(const void* data) {
+  return ((size_t)data & 0x3U) == 0;
+}
+
 }  // namespace util
 }  // namespace android
 
diff --git a/libs/androidfw/tests/data/app/app.apk b/libs/androidfw/tests/data/app/app.apk
index c8ad86d..6703695 100644
--- a/libs/androidfw/tests/data/app/app.apk
+++ b/libs/androidfw/tests/data/app/app.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 3ab244e..723413c 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index acb74f4..815ffde 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -814,6 +814,18 @@
     mCanvas->drawDrawable(drawable.get());
 }
 
+void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
+                            uirenderer::CanvasPropertyPrimitive* y,
+                            uirenderer::CanvasPropertyPrimitive* radius,
+                            uirenderer::CanvasPropertyPaint* paint,
+                            uirenderer::CanvasPropertyPrimitive* progress,
+                            sk_sp<SkRuntimeEffect> runtimeEffect) {
+    sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable(
+            new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress,
+                                                         runtimeEffect));
+    mCanvas->drawDrawable(drawable.get());
+}
+
 void SkiaCanvas::drawPicture(const SkPicture& picture) {
     // TODO: Change to mCanvas->drawPicture()? SkCanvas::drawPicture seems to be
     // where the logic is for playback vs. ref picture. Using picture.playback here
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 584321e..fa7d373 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -147,6 +147,12 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
+    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
+                            uirenderer::CanvasPropertyPrimitive* y,
+                            uirenderer::CanvasPropertyPrimitive* radius,
+                            uirenderer::CanvasPropertyPaint* paint,
+                            uirenderer::CanvasPropertyPrimitive* progress,
+                            sk_sp<SkRuntimeEffect> runtimeEffect) override;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index f0aa777..cde50bd 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -42,6 +42,7 @@
     DrawRoundRectProperty,
     DrawDoubleRoundRect,
     DrawCircleProperty,
+    DrawRippleProperty,
     DrawCircle,
     DrawOval,
     DrawArc,
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 62c26c7..ea9fea97 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -23,6 +23,7 @@
 #include <SkVertices.h>
 #include <SkImage.h>
 #include <SkPicture.h>
+#include <SkRuntimeEffect.h>
 #include <hwui/Bitmap.h>
 #include <log/log.h>
 #include "CanvasProperty.h"
@@ -142,6 +143,42 @@
     ASSERT_DRAWABLE()
 };
 
+template<>
+struct CanvasOp<CanvasOpType::DrawRippleProperty> {
+    sp<uirenderer::CanvasPropertyPrimitive> x;
+    sp<uirenderer::CanvasPropertyPrimitive> y;
+    sp<uirenderer::CanvasPropertyPrimitive> radius;
+    sp<uirenderer::CanvasPropertyPaint> paint;
+    sp<uirenderer::CanvasPropertyPrimitive> progress;
+    sk_sp<SkRuntimeEffect> effect;
+
+    void draw(SkCanvas* canvas) const {
+        SkRuntimeShaderBuilder runtimeEffectBuilder(effect);
+
+        SkRuntimeShaderBuilder::BuilderUniform center = runtimeEffectBuilder.uniform("in_origin");
+        if (center.fVar != nullptr) {
+            center = SkV2{x->value, y->value};
+        }
+
+        SkRuntimeShaderBuilder::BuilderUniform radiusU =
+                runtimeEffectBuilder.uniform("in_maxRadius");
+        if (radiusU.fVar != nullptr) {
+            radiusU = radius->value;
+        }
+
+        SkRuntimeShaderBuilder::BuilderUniform progressU =
+                runtimeEffectBuilder.uniform("in_progress");
+        if (progressU.fVar != nullptr) {
+            progressU = progress->value;
+        }
+
+        SkPaint paintMod = paint->value;
+        paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false));
+        canvas->drawCircle(x->value, y->value, radius->value, paintMod);
+    }
+    ASSERT_DRAWABLE()
+};
+
 template <>
 struct CanvasOp<CanvasOpType::DrawColor> {
     SkColor4f color;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 11fa322..d0c996b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -31,6 +31,7 @@
 
 class SkAnimatedImage;
 class SkCanvasState;
+class SkRuntimeEffect;
 class SkVertices;
 
 namespace minikin {
@@ -133,6 +134,12 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) = 0;
+    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
+                            uirenderer::CanvasPropertyPrimitive* y,
+                            uirenderer::CanvasPropertyPrimitive* radius,
+                            uirenderer::CanvasPropertyPaint* paint,
+                            uirenderer::CanvasPropertyPrimitive* progress,
+                            sk_sp<SkRuntimeEffect> runtimeEffect) = 0;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 6889134..b3f7627 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -17,11 +17,19 @@
 #include "ImageDecoder.h"
 
 #include <hwui/Bitmap.h>
+#include <log/log.h>
 
 #include <SkAndroidCodec.h>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
+#include <SkEncodedOrigin.h>
+#include <SkFilterQuality.h>
 #include <SkPaint.h>
 
+#undef LOG_TAG
+#define LOG_TAG "ImageDecoder"
+
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
@@ -44,17 +52,29 @@
     , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType))
     , mUnpremultipliedRequired(false)
     , mOutColorSpace(getDefaultColorSpace())
-    , mSampleSize(1)
 {
     mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() }
                                     : mDecodeSize;
+    this->rewind();
 }
 
+ImageDecoder::~ImageDecoder() = default;
+
 SkAlphaType ImageDecoder::getOutAlphaType() const {
     return opaque() ? kOpaque_SkAlphaType
                     : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
 }
 
+static SkISize swapped(const SkISize& size) {
+    return SkISize { size.height(), size.width() };
+}
+
+static bool requires_matrix_scaling(bool swapWidthHeight, const SkISize& decodeSize,
+                                    const SkISize& targetSize) {
+    return (swapWidthHeight && decodeSize != swapped(targetSize))
+          || (!swapWidthHeight && decodeSize != targetSize);
+}
+
 bool ImageDecoder::setTargetSize(int width, int height) {
     if (width <= 0 || height <= 0) {
         return false;
@@ -78,17 +98,21 @@
         }
     }
 
-    SkISize targetSize = { width, height };
-    SkISize decodeSize = swapWidthHeight() ? SkISize { height, width } : targetSize;
+    const bool swap = swapWidthHeight();
+    const SkISize targetSize = { width, height };
+    SkISize decodeSize = swap ? SkISize { height, width } : targetSize;
     int sampleSize = mCodec->computeSampleSize(&decodeSize);
 
-    if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) {
-        return false;
+    if (mUnpremultipliedRequired && !opaque()) {
+        // Allow using a matrix to handle orientation, but not scaling.
+        if (requires_matrix_scaling(swap, decodeSize, targetSize)) {
+            return false;
+        }
     }
 
     mTargetSize = targetSize;
     mDecodeSize = decodeSize;
-    mSampleSize = sampleSize;
+    mOptions.fSampleSize = sampleSize;
     return true;
 }
 
@@ -137,8 +161,10 @@
 }
 
 bool ImageDecoder::setUnpremultipliedRequired(bool required) {
-    if (required && !opaque() && mDecodeSize != mTargetSize) {
-        return false;
+    if (required && !opaque()) {
+        if (requires_matrix_scaling(swapWidthHeight(), mDecodeSize, mTargetSize)) {
+            return false;
+        }
     }
     mUnpremultipliedRequired = required;
     return true;
@@ -176,51 +202,179 @@
 }
 
 bool ImageDecoder::opaque() const {
-    return mCodec->getInfo().alphaType() == kOpaque_SkAlphaType;
+    return mCurrentFrameIsOpaque;
 }
 
 bool ImageDecoder::gray() const {
     return mCodec->getInfo().colorType() == kGray_8_SkColorType;
 }
 
+bool ImageDecoder::isAnimated() {
+    return mCodec->codec()->getFrameCount() > 1;
+}
+
+int ImageDecoder::currentFrame() const {
+    return mOptions.fFrameIndex;
+}
+
+bool ImageDecoder::rewind() {
+    mOptions.fFrameIndex = 0;
+    mOptions.fPriorFrame = SkCodec::kNoFrame;
+    mCurrentFrameIsIndependent = true;
+    mCurrentFrameIsOpaque = mCodec->getInfo().isOpaque();
+    mRestoreState = RestoreState::kDoNothing;
+    mRestoreFrame = nullptr;
+
+    // TODO: Rewind the input now instead of in the next call to decode, and
+    // plumb through whether rewind succeeded.
+    return true;
+}
+
+bool ImageDecoder::advanceFrame() {
+    const int frameIndex = ++mOptions.fFrameIndex;
+    const int frameCount = mCodec->codec()->getFrameCount();
+    if (frameIndex >= frameCount) {
+        // Prevent overflow from repeated calls to advanceFrame.
+        mOptions.fFrameIndex = frameCount;
+        return false;
+    }
+
+    SkCodec::FrameInfo frameInfo;
+    if (!mCodec->codec()->getFrameInfo(frameIndex, &frameInfo)
+            || !frameInfo.fFullyReceived) {
+        // Mark the decoder as finished, requiring a rewind.
+        mOptions.fFrameIndex = frameCount;
+        return false;
+    }
+
+    mCurrentFrameIsIndependent = frameInfo.fRequiredFrame == SkCodec::kNoFrame;
+    mCurrentFrameIsOpaque = frameInfo.fAlphaType == kOpaque_SkAlphaType;
+
+    if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious) {
+        switch (mRestoreState) {
+            case RestoreState::kDoNothing:
+            case RestoreState::kNeedsRestore:
+                mRestoreState = RestoreState::kFirstRPFrame;
+                break;
+            case RestoreState::kFirstRPFrame:
+                mRestoreState = RestoreState::kRPFrame;
+                break;
+            case RestoreState::kRPFrame:
+                // Unchanged.
+                break;
+        }
+    } else { // New frame is not restore previous
+        switch (mRestoreState) {
+            case RestoreState::kFirstRPFrame:
+            case RestoreState::kRPFrame:
+                mRestoreState = RestoreState::kNeedsRestore;
+                break;
+            case RestoreState::kNeedsRestore:
+                mRestoreState = RestoreState::kDoNothing;
+                mRestoreFrame = nullptr;
+                [[fallthrough]];
+            case RestoreState::kDoNothing:
+                mOptions.fPriorFrame = frameIndex - 1;
+                break;
+        }
+    }
+
+    return true;
+}
+
+SkCodec::FrameInfo ImageDecoder::getCurrentFrameInfo() {
+    LOG_ALWAYS_FATAL_IF(finished());
+
+    auto dims = mCodec->codec()->dimensions();
+    SkCodec::FrameInfo info;
+    if (!mCodec->codec()->getFrameInfo(mOptions.fFrameIndex, &info)) {
+        // SkCodec may return false for a non-animated image. Provide defaults.
+        info.fRequiredFrame = SkCodec::kNoFrame;
+        info.fDuration = 0;
+        info.fFullyReceived = true;
+        info.fAlphaType = mCodec->codec()->getInfo().alphaType();
+        info.fHasAlphaWithinBounds = info.fAlphaType != kOpaque_SkAlphaType;
+        info.fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep;
+        info.fBlend = SkCodecAnimation::Blend::kSrc;
+        info.fFrameRect = SkIRect::MakeSize(dims);
+    }
+
+    if (auto origin = mCodec->codec()->getOrigin(); origin != kDefault_SkEncodedOrigin) {
+        if (SkEncodedOriginSwapsWidthHeight(origin)) {
+            dims = swapped(dims);
+        }
+        auto matrix = SkEncodedOriginToMatrix(origin, dims.width(), dims.height());
+        auto rect = SkRect::Make(info.fFrameRect);
+        LOG_ALWAYS_FATAL_IF(!matrix.mapRect(&rect));
+        rect.roundIn(&info.fFrameRect);
+    }
+    return info;
+}
+
+bool ImageDecoder::finished() const {
+    return mOptions.fFrameIndex >= mCodec->codec()->getFrameCount();
+}
+
 SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
+    // This was checked inside setTargetSize, but it's possible the first frame
+    // was opaque, so that method succeeded, but after calling advanceFrame, the
+    // current frame is not opaque.
+    if (mUnpremultipliedRequired && !opaque()) {
+        // Allow using a matrix to handle orientation, but not scaling.
+        if (requires_matrix_scaling(swapWidthHeight(), mDecodeSize, mTargetSize)) {
+            return SkCodec::kInvalidScale;
+        }
+    }
+
     void* decodePixels = pixels;
     size_t decodeRowBytes = rowBytes;
-    auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(),
-                                        getOutputColorSpace());
+    const auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(),
+                                              getOutputColorSpace());
+    const auto outputInfo = getOutputInfo();
+    switch (mRestoreState) {
+        case RestoreState::kFirstRPFrame:{
+            // This frame is marked kRestorePrevious. The prior frame should be in
+            // |pixels|, and it is what we'll restore after each consecutive
+            // kRestorePrevious frame. Cache it now.
+            if (!(mRestoreFrame = Bitmap::allocateHeapBitmap(outputInfo))) {
+                return SkCodec::kInternalError;
+            }
+
+            const uint8_t* srcRow = static_cast<uint8_t*>(pixels);
+                  uint8_t* dstRow = static_cast<uint8_t*>(mRestoreFrame->pixels());
+            for (int y = 0; y < outputInfo.height(); y++) {
+                memcpy(dstRow, srcRow, outputInfo.minRowBytes());
+                srcRow += rowBytes;
+                dstRow += mRestoreFrame->rowBytes();
+            }
+            break;
+        }
+        case RestoreState::kRPFrame:
+        case RestoreState::kNeedsRestore:
+            // Restore the cached frame. It's possible that the client skipped decoding a frame, so
+            // we never cached it.
+            if (mRestoreFrame) {
+                const uint8_t* srcRow = static_cast<uint8_t*>(mRestoreFrame->pixels());
+                      uint8_t* dstRow = static_cast<uint8_t*>(pixels);
+                for (int y = 0; y < outputInfo.height(); y++) {
+                    memcpy(dstRow, srcRow, outputInfo.minRowBytes());
+                    srcRow += mRestoreFrame->rowBytes();
+                    dstRow += rowBytes;
+                }
+            }
+            break;
+        case RestoreState::kDoNothing:
+            break;
+    }
+
     // Used if we need a temporary before scaling or subsetting.
     // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
     SkBitmap tmp;
     const bool scale = mDecodeSize != mTargetSize;
     const auto origin = mCodec->codec()->getOrigin();
     const bool handleOrigin = origin != kDefault_SkEncodedOrigin;
+    SkMatrix outputMatrix;
     if (scale || handleOrigin || mCropRect) {
-        if (!tmp.setInfo(decodeInfo)) {
-            return SkCodec::kInternalError;
-        }
-        if (!Bitmap::allocateHeapBitmap(&tmp)) {
-            return SkCodec::kInternalError;
-        }
-        decodePixels = tmp.getPixels();
-        decodeRowBytes = tmp.rowBytes();
-    }
-
-    SkAndroidCodec::AndroidOptions options;
-    options.fSampleSize = mSampleSize;
-    auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &options);
-
-    if (scale || handleOrigin || mCropRect) {
-        SkBitmap scaledBm;
-        if (!scaledBm.installPixels(getOutputInfo(), pixels, rowBytes)) {
-            return SkCodec::kInternalError;
-        }
-
-        SkPaint paint;
-        paint.setBlendMode(SkBlendMode::kSrc);
-        paint.setFilterQuality(kLow_SkFilterQuality);  // bilinear filtering
-
-        SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
-        SkMatrix outputMatrix;
         if (mCropRect) {
             outputMatrix.setTranslate(-mCropRect->fLeft, -mCropRect->fTop);
         }
@@ -233,13 +387,54 @@
                 std::swap(targetWidth, targetHeight);
             }
         }
-
         if (scale) {
             float scaleX = (float) targetWidth  / mDecodeSize.width();
             float scaleY = (float) targetHeight / mDecodeSize.height();
             outputMatrix.preScale(scaleX, scaleY);
         }
+        // It's possible that this portion *does* have alpha, even if the
+        // composed frame does not. In that case, the SkBitmap needs to have
+        // alpha so it blends properly.
+        if (!tmp.setInfo(decodeInfo.makeAlphaType(mUnpremultipliedRequired ? kUnpremul_SkAlphaType
+                                                                           : kPremul_SkAlphaType)))
+        {
+            return SkCodec::kInternalError;
+        }
+        if (!Bitmap::allocateHeapBitmap(&tmp)) {
+            return SkCodec::kInternalError;
+        }
+        decodePixels = tmp.getPixels();
+        decodeRowBytes = tmp.rowBytes();
 
+        if (!mCurrentFrameIsIndependent) {
+            SkMatrix inverse;
+            if (outputMatrix.invert(&inverse)) {
+                SkCanvas canvas(tmp, SkCanvas::ColorBehavior::kLegacy);
+                canvas.setMatrix(inverse);
+                SkPaint paint;
+                paint.setFilterQuality(kLow_SkFilterQuality); // bilinear
+                SkBitmap priorFrame;
+                priorFrame.installPixels(outputInfo, pixels, rowBytes);
+                canvas.drawBitmap(priorFrame, 0, 0, &paint);
+            } else {
+                ALOGE("Failed to invert matrix!");
+            }
+        }
+    }
+
+    auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+
+    if (scale || handleOrigin || mCropRect) {
+        SkBitmap scaledBm;
+        if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
+            return SkCodec::kInternalError;
+        }
+
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        paint.setFilterQuality(kLow_SkFilterQuality);  // bilinear filtering
+
+        SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
         canvas.setMatrix(outputMatrix);
         canvas.drawBitmap(tmp, 0.0f, 0.0f, &paint);
     }
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index a08e924..90261b1 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -15,6 +15,7 @@
  */
 #pragma once
 
+#include <SkAndroidCodec.h>
 #include <SkCodec.h>
 #include <SkImageInfo.h>
 #include <SkPngChunkReader.h>
@@ -24,17 +25,18 @@
 
 #include <optional>
 
-class SkAndroidCodec;
-
 namespace android {
 
-class ANDROID_API ImageDecoder {
+class Bitmap;
+
+class ANDROID_API ImageDecoder final {
 public:
     std::unique_ptr<SkAndroidCodec> mCodec;
     sk_sp<SkPngChunkReader> mPeeker;
 
     ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
                  sk_sp<SkPngChunkReader> peeker = nullptr);
+    ~ImageDecoder();
 
     bool setTargetSize(int width, int height);
     bool setCropRect(const SkIRect*);
@@ -46,25 +48,65 @@
     sk_sp<SkColorSpace> getDefaultColorSpace() const;
     void setOutColorSpace(sk_sp<SkColorSpace> cs);
 
-    // The size is the final size after scaling and cropping.
+    // The size is the final size after scaling, adjusting for the origin, and
+    // cropping.
     SkImageInfo getOutputInfo() const;
 
     int width() const;
     int height() const;
 
+    // True if the current frame is opaque.
     bool opaque() const;
 
     bool gray() const;
 
     SkCodec::Result decode(void* pixels, size_t rowBytes);
 
+    // Return true if the decoder has advanced beyond all frames.
+    bool finished() const;
+
+    bool advanceFrame();
+    bool rewind();
+
+    bool isAnimated();
+    int currentFrame() const;
+
+    SkCodec::FrameInfo getCurrentFrameInfo();
+
 private:
+    // State machine for keeping track of how to handle RestorePrevious (RP)
+    // frames in decode().
+    enum class RestoreState {
+        // Neither this frame nor the prior is RP, so there is no need to cache
+        // or restore.
+        kDoNothing,
+
+        // This is the first in a sequence of one or more RP frames. decode()
+        // needs to cache the provided pixels.
+        kFirstRPFrame,
+
+        // This is the second (or later) in a sequence of multiple RP frames.
+        // decode() needs to restore the cached frame that preceded the first RP
+        // frame in the sequence.
+        kRPFrame,
+
+        // This is the first non-RP frame after a sequence of one or more RP
+        // frames. decode() still needs to restore the cached frame. Separate
+        // from kRPFrame because if the following frame is RP the state will
+        // change to kFirstRPFrame.
+        kNeedsRestore,
+    };
+
     SkISize mTargetSize;
     SkISize mDecodeSize;
     SkColorType mOutColorType;
     bool mUnpremultipliedRequired;
     sk_sp<SkColorSpace> mOutColorSpace;
-    int mSampleSize;
+    SkAndroidCodec::AndroidOptions mOptions;
+    bool mCurrentFrameIsIndependent;
+    bool mCurrentFrameIsOpaque;
+    RestoreState mRestoreState;
+    sk_sp<Bitmap> mRestoreFrame;
     std::optional<SkIRect> mCropRect;
 
     ImageDecoder(const ImageDecoder&) = delete;
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index 7c1422d..f4877f4 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -20,8 +20,8 @@
 #include <utils/Looper.h>
 #endif
 
-#include <SkBitmap.h>
 #include <SkRegion.h>
+#include <SkRuntimeEffect.h>
 
 #include <Rect.h>
 #include <RenderNode.h>
@@ -139,6 +139,21 @@
     canvas->drawCircle(xProp, yProp, radiusProp, paintProp);
 }
 
+static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
+                                                           jlong xPropPtr, jlong yPropPtr,
+                                                           jlong radiusPropPtr, jlong paintPropPtr,
+                                                           jlong progressPropPtr, jlong effectPtr) {
+    Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
+    CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
+    CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
+    CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr);
+    CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr);
+    CanvasPropertyPrimitive* progressProp =
+            reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
+    SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(effectPtr);
+    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, sk_ref_sp(effect));
+}
+
 static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     canvas->drawWebViewFunctor(functor);
@@ -163,6 +178,7 @@
     { "nDrawCircle",              "(JJJJJ)V",   (void*) android_view_DisplayListCanvas_drawCircleProps },
     { "nDrawRoundRect",           "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
     { "nDrawWebViewFunctor",      "(JI)V",      (void*) android_view_DisplayListCanvas_drawWebViewFunctor },
+    { "nDrawRipple",              "(JJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRippleProps },
 };
 
 int register_android_view_DisplayListCanvas(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index bf19655..3142d92 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -18,6 +18,7 @@
 
 #include <SkCanvas.h>
 #include <SkDrawable.h>
+#include <SkRuntimeEffect.h>
 #include <utils/RefBase.h>
 #include "CanvasProperty.h"
 
@@ -54,6 +55,59 @@
     sp<uirenderer::CanvasPropertyPaint> mPaint;
 };
 
+class AnimatedRipple : public SkDrawable {
+public:
+    AnimatedRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
+                   uirenderer::CanvasPropertyPrimitive* radius,
+                   uirenderer::CanvasPropertyPaint* paint,
+                   uirenderer::CanvasPropertyPrimitive* progress,
+                   sk_sp<SkRuntimeEffect> runtimeEffect)
+            : mX(x)
+            , mY(y)
+            , mRadius(radius)
+            , mPaint(paint)
+            , mProgress(progress)
+            , mRuntimeEffectBuilder(std::move(runtimeEffect)) {}
+
+protected:
+    virtual SkRect onGetBounds() override {
+        const float x = mX->value;
+        const float y = mY->value;
+        const float radius = mRadius->value;
+        return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
+    }
+    virtual void onDraw(SkCanvas* canvas) override {
+        SkRuntimeShaderBuilder::BuilderUniform center = mRuntimeEffectBuilder.uniform("in_origin");
+        if (center.fVar != nullptr) {
+            center = SkV2{mX->value, mY->value};
+        }
+
+        SkRuntimeShaderBuilder::BuilderUniform radiusU =
+                mRuntimeEffectBuilder.uniform("in_maxRadius");
+        if (radiusU.fVar != nullptr) {
+            radiusU = mRadius->value;
+        }
+
+        SkRuntimeShaderBuilder::BuilderUniform progressU =
+                mRuntimeEffectBuilder.uniform("in_progress");
+        if (progressU.fVar != nullptr) {
+            progressU = mProgress->value;
+        }
+
+        SkPaint paint = mPaint->value;
+        paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false));
+        canvas->drawCircle(mX->value, mY->value, mRadius->value, paint);
+    }
+
+private:
+    sp<uirenderer::CanvasPropertyPrimitive> mX;
+    sp<uirenderer::CanvasPropertyPrimitive> mY;
+    sp<uirenderer::CanvasPropertyPrimitive> mRadius;
+    sp<uirenderer::CanvasPropertyPaint> mPaint;
+    sp<uirenderer::CanvasPropertyPrimitive> mProgress;
+    SkRuntimeShaderBuilder mRuntimeEffectBuilder;
+};
+
 class AnimatedCircle : public SkDrawable {
 public:
     AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index a436278..7faebda 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -85,6 +85,16 @@
     drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint));
 }
 
+void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
+                                     uirenderer::CanvasPropertyPrimitive* y,
+                                     uirenderer::CanvasPropertyPrimitive* radius,
+                                     uirenderer::CanvasPropertyPaint* paint,
+                                     uirenderer::CanvasPropertyPrimitive* progress,
+                                     sk_sp<SkRuntimeEffect> runtimeEffect) {
+    drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
+                                                                runtimeEffect));
+}
+
 void SkiaRecordingCanvas::enableZ(bool enableZ) {
     if (mCurrentBarrier && enableZ) {
         // Already in a re-order section, nothing to do
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 83e9349..622df43 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -66,6 +66,12 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
+    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
+                            uirenderer::CanvasPropertyPrimitive* y,
+                            uirenderer::CanvasPropertyPrimitive* radius,
+                            uirenderer::CanvasPropertyPaint* paint,
+                            uirenderer::CanvasPropertyPrimitive* progress,
+                            sk_sp<SkRuntimeEffect> runtimeEffect) override;
 
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index c50904c..afae502 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -36,7 +36,7 @@
 import android.location.Location;
 import android.location.LocationRequest;
 import android.location.LocationTime;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1d3e8eb..2dc9eb4 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -49,6 +49,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.location.provider.ProviderProperties;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -1803,7 +1804,6 @@
         }
 
         try {
-
             ProviderProperties properties = mService.getProviderProperties(provider);
             return new LocationProvider(provider, properties);
         } catch (IllegalArgumentException e) {
@@ -1931,11 +1931,35 @@
             boolean supportsSpeed, boolean supportsBearing,
             @ProviderProperties.PowerUsage int powerUsage,
             @ProviderProperties.Accuracy int accuracy) {
-        Preconditions.checkArgument(provider != null, "invalid null provider");
+        addTestProvider(provider, new ProviderProperties.Builder()
+                .setHasNetworkRequirement(requiresNetwork)
+                .setHasSatelliteRequirement(requiresSatellite)
+                .setHasCellRequirement(requiresCell)
+                .setHasMonetaryCost(hasMonetaryCost)
+                .setHasAltitudeSupport(supportsAltitude)
+                .setHasSpeedSupport(supportsSpeed)
+                .setHasBearingSupport(supportsBearing)
+                .setPowerUsage(powerUsage)
+                .setAccuracy(accuracy)
+                .build());
+    }
 
-        ProviderProperties properties = new ProviderProperties(requiresNetwork,
-                requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
-                supportsBearing, powerUsage, accuracy);
+    /**
+     * Creates a test location provider and adds it to the set of active providers. This provider
+     * will replace any provider with the same name that exists prior to this call.
+     *
+     * @param provider the provider name
+     *
+     * @throws IllegalArgumentException if provider is null
+     * @throws IllegalArgumentException if properties is null
+     * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
+     * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
+     * allowed} for your app.
+     */
+    public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) {
+        Preconditions.checkArgument(provider != null, "invalid null provider");
+        Preconditions.checkArgument(properties != null, "invalid null properties");
+
         try {
             mService.addTestProvider(provider, properties, mContext.getOpPackageName(),
                     mContext.getFeatureId());
diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java
index 6d2bfed..60b251e 100644
--- a/location/java/android/location/LocationProvider.java
+++ b/location/java/android/location/LocationProvider.java
@@ -17,6 +17,7 @@
 package android.location;
 
 import android.annotation.Nullable;
+import android.location.provider.ProviderProperties;
 
 /**
  * Information about the properties of a location provider.
@@ -200,10 +201,8 @@
     }
 
     /**
-     * Returns the power requirement for this provider.
-     *
-     * @return the power requirement for this provider, as one of the
-     * constants ProviderProperties.POWER_USAGE_*.
+     * Returns the power requirement for this provider, one of the ProviderProperties.POWER_USAGE_*
+     * constants.
      */
     public int getPowerRequirement() {
         if (mProperties == null) {
@@ -214,11 +213,8 @@
     }
 
     /**
-     * Returns a constant describing horizontal accuracy of this provider.
-     * If the provider returns finer grain or exact location,
-     * {@link ProviderProperties#ACCURACY_FINE} is returned, otherwise if the
-     * location is only approximate then {@link ProviderProperties#ACCURACY_COARSE}
-     * is returned.
+     * Returns the rough accuracy of this provider, one of the ProviderProperties.ACCURACY_*
+     * constants.
      */
     public int getAccuracy() {
         if (mProperties == null) {
diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/android/location/provider/ILocationProvider.aidl
similarity index 66%
rename from location/java/com/android/internal/location/ILocationProvider.aidl
rename to location/java/android/location/provider/ILocationProvider.aidl
index dac08ff..f9995d5 100644
--- a/location/java/com/android/internal/location/ILocationProvider.aidl
+++ b/location/java/android/location/provider/ILocationProvider.aidl
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.location;
+package android.location.provider;
 
 import android.os.Bundle;
-import android.os.WorkSource;
 
-import com.android.internal.location.ILocationProviderManager;
-import com.android.internal.location.ProviderRequest;
+import android.location.provider.ILocationProviderManager;
+import android.location.provider.ProviderRequest;
 
 /**
  * Binder interface for services that implement location providers. Do not implement this directly,
@@ -29,14 +28,8 @@
  */
 interface ILocationProvider {
 
-    @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}")
     oneway void setLocationProviderManager(in ILocationProviderManager manager);
-
-    @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}")
-    oneway void setRequest(in ProviderRequest request, in WorkSource ws);
-
+    oneway void setRequest(in ProviderRequest request);
     oneway void flush();
-
-    @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}")
     oneway void sendExtraCommand(String command, in Bundle extras);
 }
diff --git a/location/java/com/android/internal/location/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
similarity index 92%
rename from location/java/com/android/internal/location/ILocationProviderManager.aidl
rename to location/java/android/location/provider/ILocationProviderManager.aidl
index a5b22b2..e3f51d9 100644
--- a/location/java/com/android/internal/location/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.location;
+package android.location.provider;
 
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
 
 /**
  * Binder interface for manager of all location providers.
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
new file mode 100644
index 0000000..1306ea2
--- /dev/null
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2010 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.location.provider;
+
+import static android.location.Location.EXTRA_NO_GPS_LOCATION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+import android.location.LocationResult;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Base class for location providers outside the system server.
+ *
+ * Location providers should be wrapped in a non-exported service which returns the result of
+ * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The
+ * service should not be exported so that components other than the system server cannot bind to it.
+ * Alternatively, the service may be guarded by a permission that only system server can obtain. The
+ * service may specify metadata on its capabilities:
+ *
+ * <ul>
+ *     <li>
+ *         "serviceVersion": An integer version code to help tie break if multiple services are
+ *         capable of implementing the same location provider. All else equal, the service with the
+ *         highest version code will be chosen. Assumed to be 0 if not specified.
+ *     </li>
+ *     <li>
+ *         "serviceIsMultiuser": A boolean property, indicating if the service wishes to take
+ *         responsibility for handling changes to the current user on the device. If true, the
+ *         service will always be bound from the system user. If false, the service will always be
+ *         bound from the current user. If the current user changes, the old binding will be
+ *         released, and a new binding established under the new user. Assumed to be false if not
+ *         specified.
+ *     </li>
+ * </ul>
+ *
+ * <p>The service should have an intent filter in place for the location provider it wishes to
+ * implements. Defaults for some providers are specified as constants in this class.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class LocationProviderBase {
+
+    /**
+     * Callback to be invoked when a flush operation is complete and all flushed locations have been
+     * reported.
+     */
+    public interface OnFlushCompleteCallback {
+
+        /**
+         * Should be invoked once the flush is complete.
+         */
+        void onFlushComplete();
+    }
+
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * {@link android.location.LocationManager#NETWORK_PROVIDER}.
+     */
+    @SuppressLint("ActionValue")
+    public static final String ACTION_NETWORK_PROVIDER =
+            "com.android.location.service.v3.NetworkLocationProvider";
+
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * {@link android.location.LocationManager#FUSED_PROVIDER}.
+     */
+    @SuppressLint("ActionValue")
+    public static final String ACTION_FUSED_PROVIDER =
+            "com.android.location.service.FusedLocationProvider";
+
+    private final String mTag;
+    private final @Nullable String mPackageName;
+    private final @Nullable String mAttributionTag;
+    private final IBinder mBinder;
+
+    // write locked on mBinder, read lock is optional depending on atomicity requirements
+    private @Nullable volatile ILocationProviderManager mManager;
+    private volatile ProviderProperties mProperties;
+    private volatile boolean mAllowed;
+
+    public LocationProviderBase(@NonNull Context context, @NonNull String tag,
+            @NonNull ProviderProperties properties) {
+        mTag = tag;
+        mPackageName = context.getPackageName();
+        mAttributionTag = context.getAttributionTag();
+        mBinder = new Service();
+
+        mManager = null;
+        mProperties = Objects.requireNonNull(properties);
+        mAllowed = true;
+    }
+
+    /**
+     * Returns the IBinder instance that should be returned from the
+     * {@link android.app.Service#onBind(Intent)} method of the wrapping service.
+     */
+    public final @Nullable IBinder getBinder() {
+        return mBinder;
+    }
+
+    /**
+     * Sets whether this provider is currently allowed or not. Note that this is specific to the
+     * provider only, and is unrelated to global location settings. This is a hint to the location
+     * manager that this provider will be unable to fulfill incoming requests. Setting a provider
+     * as not allowed will result in the provider being disabled. Setting a provider as allowed
+     * means that the provider may be in either the enabled or disabled state.
+     *
+     * <p>Some guidelines: providers should set their own allowed/disallowed status based only on
+     * state "owned" by that provider. For instance, providers should not take into account the
+     * state of the location master setting when setting themselves allowed or disallowed, as this
+     * state is not owned by a particular provider. If a provider requires some additional user
+     * consent that is particular to the provider, this should be use to set the allowed/disallowed
+     * state. If the provider proxies to another provider, the child provider's allowed/disallowed
+     * state should be taken into account in the parent's allowed state. For most providers, it is
+     * expected that they will be always allowed.
+     */
+    public void setAllowed(boolean allowed) {
+        synchronized (mBinder) {
+            if (mAllowed == allowed) {
+                return;
+            }
+
+            mAllowed = allowed;
+        }
+
+        ILocationProviderManager manager = mManager;
+        if (manager != null) {
+            try {
+                manager.onSetAllowed(mAllowed);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (RuntimeException e) {
+                Log.w(mTag, e);
+            }
+        }
+    }
+
+    /**
+     * Returns true if this provider is allowed. Providers start as allowed on construction.
+     */
+    public boolean isAllowed() {
+        return mAllowed;
+    }
+
+    /**
+     * Sets the provider properties that may be queried by clients. Generally speaking, providers
+     * should try to avoid changing their properties after construction.
+     */
+    public void setProperties(@NonNull ProviderProperties properties) {
+        synchronized (mBinder) {
+            mProperties = Objects.requireNonNull(properties);
+        }
+
+        ILocationProviderManager manager = mManager;
+        if (manager != null) {
+            try {
+                manager.onSetProperties(mProperties);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (RuntimeException e) {
+                Log.w(mTag, e);
+            }
+        }
+    }
+
+    /**
+     * Returns the currently set properties of the provider.
+     */
+    public @NonNull ProviderProperties getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * Reports a new location from this provider.
+     */
+    public void reportLocation(@NonNull Location location) {
+        reportLocation(LocationResult.create(location));
+    }
+
+    /**
+     * Reports a new location result from this provider.
+     *
+     * <p>May only be used from Android S onwards.
+     */
+    public void reportLocation(@NonNull LocationResult locationResult) {
+        ILocationProviderManager manager = mManager;
+        if (manager != null) {
+            locationResult = locationResult.map(location -> {
+                // remove deprecated extras to save on serialization costs
+                Bundle extras = location.getExtras();
+                if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
+                        || extras.containsKey("coarseLocation"))) {
+                    location = new Location(location);
+                    extras = location.getExtras();
+                    extras.remove(EXTRA_NO_GPS_LOCATION);
+                    extras.remove("coarseLocation");
+                    if (extras.isEmpty()) {
+                        location.setExtras(null);
+                    }
+                }
+                return location;
+            });
+
+            try {
+                manager.onReportLocation(locationResult);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (RuntimeException e) {
+                Log.w(mTag, e);
+            }
+        }
+    }
+
+    /**
+     * Set the current {@link ProviderRequest} for this provider. Each call to this method overrides
+     * any prior ProviderRequests. The provider should immediately attempt to provide locations (or
+     * not provide locations) according to the parameters of the provider request.
+     */
+    public abstract void onSetRequest(@NonNull ProviderRequest request);
+
+    /**
+     * Requests a flush of any pending batched locations. The callback must always be invoked once
+     * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been
+     * invoked with any flushed locations. The callback may be invoked immediately if no locations
+     * are flushed.
+     */
+    public abstract void onFlush(@NonNull OnFlushCompleteCallback callback);
+
+    /**
+     * Implements optional custom commands.
+     */
+    public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras);
+
+    private final class Service extends ILocationProvider.Stub {
+
+        Service() {}
+
+        @Override
+        public void setLocationProviderManager(ILocationProviderManager manager) {
+            synchronized (mBinder) {
+                try {
+                    manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                } catch (RuntimeException e) {
+                    Log.w(mTag, e);
+                }
+
+                mManager = manager;
+            }
+        }
+
+        @Override
+        public void setRequest(ProviderRequest request) {
+            onSetRequest(request);
+        }
+
+        @Override
+        public void flush() {
+            onFlush(this::onFlushComplete);
+        }
+
+        private void onFlushComplete() {
+            ILocationProviderManager manager = mManager;
+            if (manager != null) {
+                try {
+                    manager.onFlushComplete();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                } catch (RuntimeException e) {
+                    Log.w(mTag, e);
+                }
+            }
+        }
+
+        @Override
+        public void sendExtraCommand(String command, Bundle extras) {
+            onSendExtraCommand(command, extras);
+        }
+    }
+}
diff --git a/location/java/android/location/ProviderProperties.aidl b/location/java/android/location/provider/ProviderProperties.aidl
similarity index 94%
rename from location/java/android/location/ProviderProperties.aidl
rename to location/java/android/location/provider/ProviderProperties.aidl
index 8b1d79f..fd5a614 100644
--- a/location/java/android/location/ProviderProperties.aidl
+++ b/location/java/android/location/provider/ProviderProperties.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.location;
+package android.location.provider;
 
 parcelable ProviderProperties;
diff --git a/location/java/android/location/ProviderProperties.java b/location/java/android/location/provider/ProviderProperties.java
similarity index 65%
rename from location/java/android/location/ProviderProperties.java
rename to location/java/android/location/provider/ProviderProperties.java
index 8fa8c98..7934012 100644
--- a/location/java/android/location/ProviderProperties.java
+++ b/location/java/android/location/provider/ProviderProperties.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.location;
+package android.location.provider;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -78,10 +78,7 @@
     private final @PowerUsage int mPowerUsage;
     private final @Accuracy int mAccuracy;
 
-    /**
-     * @hide
-     */
-    public ProviderProperties(boolean hasNetworkRequirement, boolean hasSatelliteRequirement,
+    private ProviderProperties(boolean hasNetworkRequirement, boolean hasSatelliteRequirement,
             boolean hasCellRequirement, boolean hasMonetaryCost, boolean hasAltitudeSupport,
             boolean hasSpeedSupport, boolean hasBearingSupport,
             @PowerUsage int powerUsage, @Accuracy int accuracy) {
@@ -92,10 +89,8 @@
         mHasAltitudeSupport = hasAltitudeSupport;
         mHasSpeedSupport = hasSpeedSupport;
         mHasBearingSupport = hasBearingSupport;
-        mPowerUsage = Preconditions.checkArgumentInRange(powerUsage, POWER_USAGE_LOW,
-                POWER_USAGE_HIGH, "powerUsage");
-        mAccuracy = Preconditions.checkArgumentInRange(accuracy, ACCURACY_FINE,
-                ACCURACY_COARSE, "locationAccuracy");
+        mPowerUsage = powerUsage;
+        mAccuracy = accuracy;
     }
 
     /**
@@ -233,7 +228,7 @@
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder("ProviderProperties[");
-        b.append("power=").append(powerToString(mPowerUsage)).append(", ");
+        b.append("powerUsage=").append(powerToString(mPowerUsage)).append(", ");
         b.append("accuracy=").append(accuracyToString(mAccuracy));
         if (mHasNetworkRequirement || mHasSatelliteRequirement || mHasCellRequirement) {
             b.append(", requires=");
@@ -292,4 +287,128 @@
                 throw new AssertionError();
         }
     }
+
+    /**
+     * Builder for ProviderProperties.
+     */
+    public static final class Builder {
+
+        private boolean mHasNetworkRequirement;
+        private boolean mHasSatelliteRequirement;
+        private boolean mHasCellRequirement;
+        private boolean mHasMonetaryCost;
+        private boolean mHasAltitudeSupport;
+        private boolean mHasSpeedSupport;
+        private boolean mHasBearingSupport;
+        private @PowerUsage int mPowerUsage;
+        private @Accuracy int mAccuracy;
+
+        public Builder() {
+            mHasNetworkRequirement = false;
+            mHasSatelliteRequirement = false;
+            mHasCellRequirement = false;
+            mHasMonetaryCost = false;
+            mHasAltitudeSupport = false;
+            mHasSpeedSupport = false;
+            mHasBearingSupport = false;
+            mPowerUsage = POWER_USAGE_HIGH;
+            mAccuracy = ACCURACY_COARSE;
+        }
+
+        public Builder(@NonNull ProviderProperties providerProperties) {
+            mHasNetworkRequirement = providerProperties.mHasNetworkRequirement;
+            mHasSatelliteRequirement = providerProperties.mHasSatelliteRequirement;
+            mHasCellRequirement = providerProperties.mHasCellRequirement;
+            mHasMonetaryCost = providerProperties.mHasMonetaryCost;
+            mHasAltitudeSupport = providerProperties.mHasAltitudeSupport;
+            mHasSpeedSupport = providerProperties.mHasSpeedSupport;
+            mHasBearingSupport = providerProperties.mHasBearingSupport;
+            mPowerUsage = providerProperties.mPowerUsage;
+            mAccuracy = providerProperties.mAccuracy;
+        }
+
+        /**
+         * Sets whether a provider requires network access. False by default.
+         */
+        public @NonNull Builder setHasNetworkRequirement(boolean requiresNetwork) {
+            mHasNetworkRequirement = requiresNetwork;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider requires satellite access. False by default.
+         */
+        public @NonNull Builder setHasSatelliteRequirement(boolean requiresSatellite) {
+            mHasSatelliteRequirement = requiresSatellite;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider requires cell tower access. False by default.
+         */
+        public @NonNull Builder setHasCellRequirement(boolean requiresCell) {
+            mHasCellRequirement = requiresCell;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider has a monetary cost. False by default.
+         */
+        public @NonNull Builder setHasMonetaryCost(boolean monetaryCost) {
+            mHasMonetaryCost = monetaryCost;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider can provide altitude information. False by default.
+         */
+        public @NonNull Builder setHasAltitudeSupport(boolean supportsAltitude) {
+            mHasAltitudeSupport = supportsAltitude;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider can provide speed information. False by default.
+         */
+        public @NonNull Builder setHasSpeedSupport(boolean supportsSpeed) {
+            mHasSpeedSupport = supportsSpeed;
+            return this;
+        }
+
+        /**
+         * Sets whether a provider can provide bearing information. False by default.
+         */
+        public @NonNull Builder setHasBearingSupport(boolean supportsBearing) {
+            mHasBearingSupport = supportsBearing;
+            return this;
+        }
+
+        /**
+         * Sets a very rough bucket of provider power usage. {@link #POWER_USAGE_HIGH} by default.
+         */
+        public @NonNull Builder setPowerUsage(@PowerUsage int powerUsage) {
+            mPowerUsage = Preconditions.checkArgumentInRange(powerUsage, POWER_USAGE_LOW,
+                    POWER_USAGE_HIGH, "powerUsage");
+            return this;
+        }
+
+        /**
+         * Sets a very rough bucket of provider location accuracy. {@link #ACCURACY_COARSE} by
+         * default.
+         */
+        public @NonNull Builder setAccuracy(@Accuracy int accuracy) {
+            mAccuracy = Preconditions.checkArgumentInRange(accuracy, ACCURACY_FINE,
+                    ACCURACY_COARSE, "accuracy");
+            return this;
+        }
+
+        /**
+         * Builds a new ProviderProperties.
+         */
+        public @NonNull ProviderProperties build() {
+            return new ProviderProperties(mHasNetworkRequirement, mHasSatelliteRequirement,
+                    mHasCellRequirement, mHasMonetaryCost, mHasAltitudeSupport, mHasSpeedSupport,
+                    mHasBearingSupport, mPowerUsage, mAccuracy);
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderRequest.aidl b/location/java/android/location/provider/ProviderRequest.aidl
similarity index 93%
rename from location/java/com/android/internal/location/ProviderRequest.aidl
rename to location/java/android/location/provider/ProviderRequest.aidl
index 4e1ea95..b98f301 100644
--- a/location/java/com/android/internal/location/ProviderRequest.aidl
+++ b/location/java/android/location/provider/ProviderRequest.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.location;
+package android.location.provider;
 
 parcelable ProviderRequest;
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/android/location/provider/ProviderRequest.java
similarity index 75%
rename from location/java/com/android/internal/location/ProviderRequest.java
rename to location/java/android/location/provider/ProviderRequest.java
index 545ea52..e543b04 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/android/location/provider/ProviderRequest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.location;
+package android.location.provider;
 
 import static android.location.LocationRequest.QUALITY_BALANCED_POWER_ACCURACY;
 import static android.location.LocationRequest.QUALITY_HIGH_ACCURACY;
@@ -22,10 +22,9 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
 import android.location.LocationRequest;
 import android.location.LocationRequest.Quality;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.WorkSource;
@@ -33,34 +32,25 @@
 
 import com.android.internal.util.Preconditions;
 
-import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
 
 /**
  * Location provider request.
  * @hide
  */
+@SystemApi
 public final class ProviderRequest implements Parcelable {
 
     public static final long INTERVAL_DISABLED = Long.MAX_VALUE;
 
-    public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(
+    public static final @NonNull ProviderRequest EMPTY_REQUEST = new ProviderRequest(
             INTERVAL_DISABLED, QUALITY_BALANCED_POWER_ACCURACY, 0, false, false, new WorkSource());
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
-            + "ProviderRequest}")
-    public final boolean reportLocation;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
-            + "ProviderRequest}")
-    public final long interval;
+    private final long mIntervalMillis;
     private final @Quality int mQuality;
     private final long mMaxUpdateDelayMillis;
     private final boolean mLowPower;
     private final boolean mLocationSettingsIgnored;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
-            + "ProviderRequest}")
-    public final List<LocationRequest> locationRequests;
     private final WorkSource mWorkSource;
 
     private ProviderRequest(
@@ -70,22 +60,11 @@
             boolean lowPower,
             boolean locationSettingsIgnored,
             @NonNull WorkSource workSource) {
-        reportLocation = intervalMillis != INTERVAL_DISABLED;
-        interval = intervalMillis;
+        mIntervalMillis = intervalMillis;
         mQuality = quality;
         mMaxUpdateDelayMillis = maxUpdateDelayMillis;
         mLowPower = lowPower;
         mLocationSettingsIgnored = locationSettingsIgnored;
-        if (intervalMillis != INTERVAL_DISABLED) {
-            locationRequests = Collections.singletonList(new LocationRequest.Builder(intervalMillis)
-                    .setQuality(quality)
-                    .setLowPower(lowPower)
-                    .setLocationSettingsIgnored(locationSettingsIgnored)
-                    .setWorkSource(workSource)
-                    .build());
-        } else {
-            locationRequests = Collections.emptyList();
-        }
         mWorkSource = Objects.requireNonNull(workSource);
     }
 
@@ -94,7 +73,7 @@
      * request is inactive and does not require any locations to be reported.
      */
     public boolean isActive() {
-        return interval != INTERVAL_DISABLED;
+        return mIntervalMillis != INTERVAL_DISABLED;
     }
 
     /**
@@ -102,7 +81,7 @@
      * {@link #INTERVAL_DISABLED} for an inactive request.
      */
     public @IntRange(from = 0) long getIntervalMillis() {
-        return interval;
+        return mIntervalMillis;
     }
 
     /**
@@ -148,29 +127,28 @@
         return mWorkSource;
     }
 
-    public static final Parcelable.Creator<ProviderRequest> CREATOR =
-            new Parcelable.Creator<ProviderRequest>() {
-                @Override
-                public ProviderRequest createFromParcel(Parcel in) {
-                    long intervalMillis = in.readLong();
-                    if (intervalMillis == INTERVAL_DISABLED) {
-                        return EMPTY_REQUEST;
-                    } else {
-                        return new ProviderRequest(
-                                intervalMillis,
-                                /* quality= */ in.readInt(),
-                                /* maxUpdateDelayMillis= */ in.readLong(),
-                                /* lowPower= */ in.readBoolean(),
-                                /* locationSettingsIgnored= */ in.readBoolean(),
-                                /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
-                    }
-                }
+    public static final @NonNull Creator<ProviderRequest> CREATOR = new Creator<ProviderRequest>() {
+        @Override
+        public ProviderRequest createFromParcel(Parcel in) {
+            long intervalMillis = in.readLong();
+            if (intervalMillis == INTERVAL_DISABLED) {
+                return EMPTY_REQUEST;
+            } else {
+                return new ProviderRequest(
+                        intervalMillis,
+                        /* quality= */ in.readInt(),
+                        /* maxUpdateDelayMillis= */ in.readLong(),
+                        /* lowPower= */ in.readBoolean(),
+                        /* locationSettingsIgnored= */ in.readBoolean(),
+                        /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
+            }
+        }
 
-                @Override
-                public ProviderRequest[] newArray(int size) {
-                    return new ProviderRequest[size];
-                }
-            };
+        @Override
+        public ProviderRequest[] newArray(int size) {
+            return new ProviderRequest[size];
+        }
+    };
 
     @Override
     public int describeContents() {
@@ -178,9 +156,9 @@
     }
 
     @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeLong(interval);
-        if (interval != INTERVAL_DISABLED) {
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeLong(mIntervalMillis);
+        if (mIntervalMillis != INTERVAL_DISABLED) {
             parcel.writeInt(mQuality);
             parcel.writeLong(mMaxUpdateDelayMillis);
             parcel.writeBoolean(mLowPower);
@@ -199,10 +177,10 @@
         }
 
         ProviderRequest that = (ProviderRequest) o;
-        if (interval == INTERVAL_DISABLED) {
-            return that.interval == INTERVAL_DISABLED;
+        if (mIntervalMillis == INTERVAL_DISABLED) {
+            return that.mIntervalMillis == INTERVAL_DISABLED;
         } else {
-            return interval == that.interval
+            return mIntervalMillis == that.mIntervalMillis
                     && mQuality == that.mQuality
                     && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
                     && mLowPower == that.mLowPower
@@ -213,16 +191,16 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(interval, mQuality, mWorkSource);
+        return Objects.hash(mIntervalMillis, mQuality, mWorkSource);
     }
 
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
         s.append("ProviderRequest[");
-        if (interval != INTERVAL_DISABLED) {
+        if (mIntervalMillis != INTERVAL_DISABLED) {
             s.append("@");
-            TimeUtils.formatDuration(interval, s);
+            TimeUtils.formatDuration(mIntervalMillis, s);
             if (mQuality != QUALITY_BALANCED_POWER_ACCURACY) {
                 if (mQuality == QUALITY_HIGH_ACCURACY) {
                     s.append(", HIGH_ACCURACY");
@@ -230,7 +208,7 @@
                     s.append(", LOW_POWER");
                 }
             }
-            if (mMaxUpdateDelayMillis / 2 > interval) {
+            if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) {
                 s.append(", maxUpdateDelay=");
                 TimeUtils.formatDuration(mMaxUpdateDelayMillis, s);
             }
@@ -253,7 +231,7 @@
     /**
      * A Builder for {@link ProviderRequest}s.
      */
-    public static class Builder {
+    public static final class Builder {
         private long mIntervalMillis = INTERVAL_DISABLED;
         private int mQuality = QUALITY_BALANCED_POWER_ACCURACY;
         private long mMaxUpdateDelayMillis = 0;
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index 4e13487..338d7cc 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -6,33 +6,33 @@
     method @Deprecated public android.os.IBinder getBinder();
   }
 
-  public abstract class LocationProviderBase {
+  @Deprecated public abstract class LocationProviderBase {
     ctor @Deprecated public LocationProviderBase(String, com.android.location.provider.ProviderPropertiesUnbundled);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public LocationProviderBase(android.content.Context, String, com.android.location.provider.ProviderPropertiesUnbundled);
-    method public android.os.IBinder getBinder();
-    method @RequiresApi(android.os.Build.VERSION_CODES.R) public boolean isAllowed();
+    ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public LocationProviderBase(android.content.Context, String, com.android.location.provider.ProviderPropertiesUnbundled);
+    method @Deprecated public android.os.IBinder getBinder();
+    method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public boolean isAllowed();
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public boolean isEnabled();
     method @Deprecated protected void onDisable();
     method @Deprecated protected void onDump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method @Deprecated protected void onEnable();
-    method protected void onFlush(com.android.location.provider.LocationProviderBase.OnFlushCompleteCallback);
+    method @Deprecated protected void onFlush(com.android.location.provider.LocationProviderBase.OnFlushCompleteCallback);
     method @Deprecated protected int onGetStatus(android.os.Bundle);
     method @Deprecated protected long onGetStatusUpdateTime();
-    method protected void onInit();
-    method protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle);
-    method protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource);
-    method public void reportLocation(android.location.Location);
-    method public void reportLocation(android.location.LocationResult);
+    method @Deprecated protected void onInit();
+    method @Deprecated protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle);
+    method @Deprecated protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource);
+    method @Deprecated public void reportLocation(android.location.Location);
+    method @Deprecated public void reportLocation(android.location.LocationResult);
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setAdditionalProviderPackages(java.util.List<java.lang.String>);
-    method @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean);
+    method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean);
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setEnabled(boolean);
-    method @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setProperties(com.android.location.provider.ProviderPropertiesUnbundled);
+    method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setProperties(com.android.location.provider.ProviderPropertiesUnbundled);
     field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
-    field public static final String FUSED_PROVIDER = "fused";
+    field @Deprecated public static final String FUSED_PROVIDER = "fused";
   }
 
-  protected static interface LocationProviderBase.OnFlushCompleteCallback {
-    method public void onFlushComplete();
+  @Deprecated protected static interface LocationProviderBase.OnFlushCompleteCallback {
+    method @Deprecated public void onFlushComplete();
   }
 
   @Deprecated public final class LocationRequestUnbundled {
diff --git a/location/lib/api/system-current.txt b/location/lib/api/system-current.txt
index d802177..7046abd 100644
--- a/location/lib/api/system-current.txt
+++ b/location/lib/api/system-current.txt
@@ -1 +1,62 @@
 // Signature format: 2.0
+package com.android.location.provider {
+
+  @Deprecated public final class FusedLocationHardware {
+    method @Deprecated public void flushBatchedLocations();
+    method @Deprecated public int getSupportedBatchSize();
+    method @Deprecated public int getVersion();
+    method @Deprecated public void injectDeviceContext(int);
+    method @Deprecated public void injectDiagnosticData(String);
+    method @Deprecated public void registerSink(com.android.location.provider.FusedLocationHardwareSink, android.os.Looper);
+    method @Deprecated public void requestBatchOfLocations(int);
+    method @Deprecated public void startBatching(int, com.android.location.provider.GmsFusedBatchOptions);
+    method @Deprecated public void stopBatching(int);
+    method @Deprecated public boolean supportsDeviceContextInjection();
+    method @Deprecated public boolean supportsDiagnosticDataInjection();
+    method @Deprecated public void unregisterSink(com.android.location.provider.FusedLocationHardwareSink);
+    method @Deprecated public void updateBatchingOptions(int, com.android.location.provider.GmsFusedBatchOptions);
+  }
+
+  @Deprecated public class FusedLocationHardwareSink {
+    ctor @Deprecated public FusedLocationHardwareSink();
+    method @Deprecated public void onCapabilities(int);
+    method @Deprecated public void onDiagnosticDataAvailable(String);
+    method @Deprecated public void onLocationAvailable(android.location.Location[]);
+    method @Deprecated public void onStatusChanged(int);
+  }
+
+  @Deprecated public class GmsFusedBatchOptions {
+    ctor @Deprecated public GmsFusedBatchOptions();
+    method @Deprecated public int getFlags();
+    method @Deprecated public double getMaxPowerAllocationInMW();
+    method @Deprecated public long getPeriodInNS();
+    method @Deprecated public float getSmallestDisplacementMeters();
+    method @Deprecated public int getSourcesToUse();
+    method @Deprecated public boolean isFlagSet(int);
+    method @Deprecated public boolean isSourceToUseSet(int);
+    method @Deprecated public void resetFlag(int);
+    method @Deprecated public void resetSourceToUse(int);
+    method @Deprecated public void setFlag(int);
+    method @Deprecated public void setMaxPowerAllocationInMW(double);
+    method @Deprecated public void setPeriodInNS(long);
+    method @Deprecated public void setSmallestDisplacementMeters(float);
+    method @Deprecated public void setSourceToUse(int);
+  }
+
+  @Deprecated public static final class GmsFusedBatchOptions.BatchFlags {
+    ctor @Deprecated public GmsFusedBatchOptions.BatchFlags();
+    field @Deprecated public static int CALLBACK_ON_LOCATION_FIX;
+    field @Deprecated public static int WAKEUP_ON_FIFO_FULL;
+  }
+
+  @Deprecated public static final class GmsFusedBatchOptions.SourceTechnologies {
+    ctor @Deprecated public GmsFusedBatchOptions.SourceTechnologies();
+    field @Deprecated public static int BLUETOOTH;
+    field @Deprecated public static int CELL;
+    field @Deprecated public static int GNSS;
+    field @Deprecated public static int SENSORS;
+    field @Deprecated public static int WIFI;
+  }
+
+}
+
diff --git a/location/lib/java/com/android/location/provider/FusedLocationHardware.java b/location/lib/java/com/android/location/provider/FusedLocationHardware.java
new file mode 100644
index 0000000..3d32386
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/FusedLocationHardware.java
@@ -0,0 +1,80 @@
+/*
+ * 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.location.provider;
+
+import android.annotation.SystemApi;
+import android.os.Looper;
+
+/**
+ * Class that exposes IFusedLocationHardware functionality to unbundled services.
+ *
+ * @deprecated This class may no longer be used from Android P and onwards.
+ * @hide
+ */
+@Deprecated
+@SystemApi
+public final class FusedLocationHardware {
+
+    private FusedLocationHardware() {}
+
+    /*
+     * Methods to provide a Facade for IFusedLocationHardware
+     */
+    public void registerSink(FusedLocationHardwareSink sink, Looper looper) {}
+
+    public void unregisterSink(FusedLocationHardwareSink sink) {}
+
+    public int getSupportedBatchSize() {
+        return 0;
+    }
+
+    public void startBatching(int id, GmsFusedBatchOptions batchOptions) {}
+
+    public void stopBatching(int id) {}
+
+    public void updateBatchingOptions(int id, GmsFusedBatchOptions batchOptions) {}
+
+    public void requestBatchOfLocations(int batchSizeRequest) {}
+
+    public void flushBatchedLocations() {}
+
+    public boolean supportsDiagnosticDataInjection() {
+        return false;
+    }
+
+    public void injectDiagnosticData(String data) {}
+
+    public boolean supportsDeviceContextInjection() {
+        return false;
+    }
+
+    public void injectDeviceContext(int deviceEnabledContext) {}
+
+    /**
+     * Returns the version of the FLP HAL.
+     *
+     * <p>Version 1 is the initial release.
+     * <p>Version 2 adds the ability to use {@link #flushBatchedLocations},
+     * {@link FusedLocationHardwareSink#onCapabilities}, and
+     * {@link FusedLocationHardwareSink#onStatusChanged}.
+     *
+     * <p>This method is only available on API 23 or later.  Older APIs have version 1.
+     */
+    public int getVersion() {
+        return 1;
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java b/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java
new file mode 100644
index 0000000..30bb1b3
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.provider;
+
+import android.annotation.SystemApi;
+import android.location.Location;
+
+/**
+ * Base class for sinks to interact with FusedLocationHardware.
+ *
+ * <p>Default implementations allow new methods to be added without crashing
+ * clients compiled against an old library version.
+ *
+ * @deprecated This class may no longer be used from Android P and onwards.
+ * @hide
+ */
+@Deprecated
+@SystemApi
+public class FusedLocationHardwareSink {
+    /**
+     * Called when one or more locations are available from the FLP
+     * HAL.
+     */
+    public void onLocationAvailable(Location[] locations) {
+        // default do nothing
+    }
+
+    /**
+     * Called when diagnostic data is available from the FLP HAL.
+     */
+    public void onDiagnosticDataAvailable(String data) {
+        // default do nothing
+    }
+
+    /**
+     * Called when capabilities are available from the FLP HAL.
+     * Should be called once right after initialization.
+     *
+     * @param capabilities A bitmask of capabilities defined in
+     *                     fused_location.h.
+     */
+    public void onCapabilities(int capabilities) {
+        // default do nothing
+    }
+
+    /**
+     * Called when the status changes in the underlying FLP HAL
+     * implementation (the ability to compute location).  This
+     * callback will only be made on version 2 or later
+     * (see {@link FusedLocationHardware#getVersion()}).
+     *
+     * @param status One of FLP_STATUS_LOCATION_AVAILABLE or
+     *               FLP_STATUS_LOCATION_UNAVAILABLE as defined in
+     *               fused_location.h.
+     */
+    public void onStatusChanged(int status) {
+        // default do nothing
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java
new file mode 100644
index 0000000..3647377
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java
@@ -0,0 +1,89 @@
+/*
+ * 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.location.provider;
+
+import android.annotation.SystemApi;
+
+/**
+ * Class that exposes FusedBatchOptions to the GmsCore.
+ *
+ * @deprecated This class may no longer be used from Android P and onwards.
+ * @hide
+ */
+@Deprecated
+@SystemApi
+public class GmsFusedBatchOptions {
+
+    public void setMaxPowerAllocationInMW(double value) {}
+
+    public double getMaxPowerAllocationInMW() {
+        return 0;
+    }
+
+    public void setPeriodInNS(long value) {}
+
+    public long getPeriodInNS() {
+        return 0;
+    }
+
+    public void setSmallestDisplacementMeters(float value) {}
+
+    public float getSmallestDisplacementMeters() {
+        return 0;
+    }
+
+    public void setSourceToUse(int source) {}
+
+    public void resetSourceToUse(int source) {}
+
+    public boolean isSourceToUseSet(int source) {
+        return false;
+    }
+
+    public int getSourcesToUse() {
+        return 0;
+    }
+
+    public void setFlag(int flag) {}
+
+    public void resetFlag(int flag) {}
+
+    public boolean isFlagSet(int flag) {
+        return false;
+    }
+
+    public int getFlags() {
+        return 0;
+    }
+
+    /**
+     * Definition of enum flag sets needed by this class.
+     * Such values need to be kept in sync with the ones in fused_location.h
+     */
+    public static final class SourceTechnologies {
+        public static int GNSS = 1 << 0;
+        public static int WIFI = 1 << 1;
+        public static int SENSORS = 1 << 2;
+        public static int CELL = 1 << 3;
+        public static int BLUETOOTH = 1 << 4;
+    }
+
+    public static final class BatchFlags {
+        public static int WAKEUP_ON_FIFO_FULL = 1 << 0;
+        public static int CALLBACK_ON_LOCATION_FIX = 1 << 1;
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 47e4256..b545a83 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -23,7 +23,10 @@
 import android.location.LocationManager;
 import android.location.LocationProvider;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ILocationProvider;
+import android.location.provider.ILocationProviderManager;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -34,10 +37,6 @@
 
 import androidx.annotation.RequiresApi;
 
-import com.android.internal.location.ILocationProvider;
-import com.android.internal.location.ILocationProviderManager;
-import com.android.internal.location.ProviderRequest;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
@@ -58,7 +57,11 @@
  * <p>IMPORTANT: This class is effectively a public API for unbundled
  * applications, and must remain API stable. See README.txt in the root
  * of this package for more information.
+ *
+ * @deprecated This class is not part of the standard API surface - use
+ * {@link android.location.provider.LocationProviderBase} instead.
  */
+@Deprecated
 public abstract class LocationProviderBase {
 
     /**
@@ -386,8 +389,8 @@
         }
 
         @Override
-        public void setRequest(ProviderRequest request, WorkSource ws) {
-            onSetRequest(new ProviderRequestUnbundled(request), ws);
+        public void setRequest(ProviderRequest request) {
+            onSetRequest(new ProviderRequestUnbundled(request), request.getWorkSource());
         }
 
         @Override
diff --git a/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java
index 9d8ccdf..89ca282 100644
--- a/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java
+++ b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java
@@ -17,7 +17,7 @@
 package com.android.location.provider;
 
 import android.annotation.NonNull;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
 
 import java.util.Objects;
 
@@ -35,10 +35,18 @@
     public static @NonNull ProviderPropertiesUnbundled create(boolean requiresNetwork,
             boolean requiresSatellite, boolean requiresCell, boolean hasMonetaryCost,
             boolean supportsAltitude, boolean supportsSpeed, boolean supportsBearing,
-            int powerRequirement, int accuracy) {
-        return new ProviderPropertiesUnbundled(new ProviderProperties(requiresNetwork,
-                requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
-                supportsBearing, powerRequirement, accuracy));
+            int powerUsage, int accuracy) {
+        return new ProviderPropertiesUnbundled(new ProviderProperties.Builder()
+                .setHasNetworkRequirement(requiresNetwork)
+                .setHasSatelliteRequirement(requiresSatellite)
+                .setHasCellRequirement(requiresCell)
+                .setHasMonetaryCost(requiresCell)
+                .setHasAltitudeSupport(requiresCell)
+                .setHasSpeedSupport(requiresCell)
+                .setHasBearingSupport(requiresCell)
+                .setPowerUsage(powerUsage)
+                .setAccuracy(accuracy)
+                .build());
     }
 
     private final ProviderProperties mProperties;
diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
index b464fca..28317fe 100644
--- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
@@ -18,13 +18,12 @@
 
 import android.annotation.NonNull;
 import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
 import android.os.Build;
 import android.os.WorkSource;
 
 import androidx.annotation.RequiresApi;
 
-import com.android.internal.location.ProviderRequest;
-
 import java.util.Collections;
 import java.util.List;
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 36f7bed..6cf99e2 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -43,8 +43,8 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAddress;
 
@@ -55,7 +55,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -99,6 +98,7 @@
 
         RouteInfo mDefaultAudioVideo;
         RouteInfo mBluetoothA2dpRoute;
+        boolean mIsBluetoothA2dpOn;
 
         RouteInfo mSelectedRoute;
 
@@ -113,11 +113,16 @@
         IMediaRouterClient mClient;
         MediaRouterClientState mClientState;
 
-        Map<Integer, Integer> mStreamVolume = new ArrayMap<>();
+        SparseIntArray mStreamVolume = new SparseIntArray();
 
         final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
             @Override
             public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
+                try {
+                    mIsBluetoothA2dpOn = mAudioService.isBluetoothA2dpOn();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error querying Bluetooth A2DP state", e);
+                }
                 mHandler.post(new Runnable() {
                     @Override public void run() {
                         updateAudioRoutes(newRoutes);
@@ -267,23 +272,23 @@
         }
 
         int getStreamVolume(int streamType) {
-            if (!mStreamVolume.containsKey(streamType)) {
+            int idx = mStreamVolume.indexOfKey(streamType);
+            if (idx < 0) {
+                int volume = 0;
                 try {
-                    mStreamVolume.put(streamType, mAudioService.getStreamVolume(streamType));
+                    volume = mAudioService.getStreamVolume(streamType);
+                    mStreamVolume.put(streamType, volume);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error getting local stream volume", e);
+                } finally {
+                    return volume;
                 }
             }
-            return mStreamVolume.get(streamType);
+            return mStreamVolume.valueAt(idx);
         }
 
         boolean isBluetoothA2dpOn() {
-            try {
-                return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
-                return false;
-            }
+            return mBluetoothA2dpRoute != null && mIsBluetoothA2dpOn;
         }
 
         void updateDiscoveryRequest() {
@@ -1444,12 +1449,8 @@
                 selectedRoute == sStatic.mDefaultAudioVideo) {
             dispatchRouteVolumeChanged(selectedRoute);
         } else if (sStatic.mBluetoothA2dpRoute != null) {
-            try {
-                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
-                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
-            }
+            dispatchRouteVolumeChanged(sStatic.mIsBluetoothA2dpOn
+                    ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
         } else {
             dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
         }
diff --git a/media/java/android/media/musicrecognition/OWNERS b/media/java/android/media/musicrecognition/OWNERS
new file mode 100644
index 0000000..58f5d40
--- /dev/null
+++ b/media/java/android/media/musicrecognition/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 830636
+
+joannechung@google.com
+oni@google.com
+volnov@google.com
+
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index f22bcd8..1fd132d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -547,6 +547,7 @@
      * @param keyEvent the key event to send
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
         dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, /*needWakeLock=*/false);
     }
@@ -558,6 +559,7 @@
      * @param needWakeLock true if a wake lock should be held while sending the key
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
         dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, needWakeLock);
     }
@@ -630,6 +632,7 @@
      * @param musicOnly true if key event should only be sent to music stream
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int streamType,
             boolean musicOnly) {
         dispatchVolumeKeyEventInternal(keyEvent, streamType, musicOnly, /*asSystemService=*/false);
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index 4aeebe4..0f61907 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -61,6 +61,37 @@
     }
 }
 
+const char* AImageDecoder_resultToString(int result) {
+    switch (result) {
+        case        ANDROID_IMAGE_DECODER_SUCCESS:
+            return "ANDROID_IMAGE_DECODER_SUCCESS";
+        case        ANDROID_IMAGE_DECODER_INCOMPLETE:
+            return "ANDROID_IMAGE_DECODER_INCOMPLETE";
+        case        ANDROID_IMAGE_DECODER_ERROR:
+            return "ANDROID_IMAGE_DECODER_ERROR";
+        case        ANDROID_IMAGE_DECODER_INVALID_CONVERSION:
+            return "ANDROID_IMAGE_DECODER_INVALID_CONVERSION";
+        case        ANDROID_IMAGE_DECODER_INVALID_SCALE:
+            return "ANDROID_IMAGE_DECODER_INVALID_SCALE";
+        case        ANDROID_IMAGE_DECODER_BAD_PARAMETER:
+            return "ANDROID_IMAGE_DECODER_BAD_PARAMETER";
+        case        ANDROID_IMAGE_DECODER_INVALID_INPUT:
+            return "ANDROID_IMAGE_DECODER_INVALID_INPUT";
+        case        ANDROID_IMAGE_DECODER_SEEK_ERROR:
+            return "ANDROID_IMAGE_DECODER_SEEK_ERROR";
+        case        ANDROID_IMAGE_DECODER_INTERNAL_ERROR:
+            return "ANDROID_IMAGE_DECODER_INTERNAL_ERROR";
+        case        ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT:
+            return "ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT";
+        case        ANDROID_IMAGE_DECODER_FINISHED:
+            return "ANDROID_IMAGE_DECODER_FINISHED";
+        case        ANDROID_IMAGE_DECODER_INVALID_STATE:
+            return "ANDROID_IMAGE_DECODER_INVALID_STATE";
+        default:
+            return nullptr;
+    }
+}
+
 static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
     SkCodec::Result result;
     auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
@@ -173,7 +204,13 @@
             || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
-    return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+
+    auto* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->currentFrame() != 0) {
+        return ANDROID_IMAGE_DECODER_INVALID_STATE;
+    }
+
+    return imageDecoder->setOutColorType(getColorType((AndroidBitmapFormat) format))
             ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
 }
 
@@ -185,6 +222,10 @@
     }
 
     ImageDecoder* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->currentFrame() != 0) {
+        return ANDROID_IMAGE_DECODER_INVALID_STATE;
+    }
+
     imageDecoder->setOutColorSpace(std::move(cs));
     return ANDROID_IMAGE_DECODER_SUCCESS;
 }
@@ -279,7 +320,12 @@
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
-    return toDecoder(decoder)->setUnpremultipliedRequired(required)
+    auto* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->currentFrame() != 0) {
+        return ANDROID_IMAGE_DECODER_INVALID_STATE;
+    }
+
+    return imageDecoder->setUnpremultipliedRequired(required)
             ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
 }
 
@@ -288,7 +334,12 @@
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
-    return toDecoder(decoder)->setTargetSize(width, height)
+    auto* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->currentFrame() != 0) {
+        return ANDROID_IMAGE_DECODER_INVALID_STATE;
+    }
+
+    return imageDecoder->setTargetSize(width, height)
             ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
 }
 
@@ -309,10 +360,15 @@
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
+    auto* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->currentFrame() != 0) {
+        return ANDROID_IMAGE_DECODER_INVALID_STATE;
+    }
+
     SkIRect cropIRect;
     cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
     SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
-    return toDecoder(decoder)->setCropRect(cropPtr)
+    return imageDecoder->setCropRect(cropPtr)
             ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
 }
 
@@ -341,6 +397,10 @@
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
+    if (imageDecoder->finished()) {
+        return ANDROID_IMAGE_DECODER_FINISHED;
+    }
+
     return ResultToErrorCode(imageDecoder->decode(pixels, stride));
 }
 
@@ -352,7 +412,7 @@
     if (!decoder) return false;
 
     ImageDecoder* imageDecoder = toDecoder(decoder);
-    return imageDecoder->mCodec->codec()->getFrameCount() > 1;
+    return imageDecoder->isAnimated();
 }
 
 int32_t AImageDecoder_getRepeatCount(AImageDecoder* decoder) {
@@ -369,3 +429,109 @@
     }
     return count;
 }
+
+int AImageDecoder_advanceFrame(AImageDecoder* decoder) {
+    if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+    if (!imageDecoder->isAnimated()) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    if (imageDecoder->advanceFrame()) {
+        return ANDROID_IMAGE_DECODER_SUCCESS;
+    }
+
+    if (imageDecoder->finished()) {
+        return ANDROID_IMAGE_DECODER_FINISHED;
+    }
+
+    return ANDROID_IMAGE_DECODER_INCOMPLETE;
+}
+
+int AImageDecoder_rewind(AImageDecoder* decoder) {
+    if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+    if (!imageDecoder->isAnimated()) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return imageDecoder->rewind() ? ANDROID_IMAGE_DECODER_SUCCESS
+                                  : ANDROID_IMAGE_DECODER_SEEK_ERROR;
+}
+
+AImageDecoderFrameInfo* AImageDecoderFrameInfo_create() {
+    return reinterpret_cast<AImageDecoderFrameInfo*>(new SkCodec::FrameInfo);
+}
+
+static SkCodec::FrameInfo* toFrameInfo(AImageDecoderFrameInfo* info) {
+    return reinterpret_cast<SkCodec::FrameInfo*>(info);
+}
+
+static const SkCodec::FrameInfo* toFrameInfo(const AImageDecoderFrameInfo* info) {
+    return reinterpret_cast<const SkCodec::FrameInfo*>(info);
+}
+
+void AImageDecoderFrameInfo_delete(AImageDecoderFrameInfo* info) {
+    delete toFrameInfo(info);
+}
+
+int AImageDecoder_getFrameInfo(AImageDecoder* decoder,
+        AImageDecoderFrameInfo* info) {
+    if (!decoder || !info) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    auto* imageDecoder = toDecoder(decoder);
+    if (imageDecoder->finished()) {
+        return ANDROID_IMAGE_DECODER_FINISHED;
+    }
+
+    *toFrameInfo(info) = imageDecoder->getCurrentFrameInfo();
+    return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int64_t AImageDecoderFrameInfo_getDuration(const AImageDecoderFrameInfo* info) {
+    if (!info) return 0;
+
+    return toFrameInfo(info)->fDuration * 1'000'000;
+}
+
+ARect AImageDecoderFrameInfo_getFrameRect(const AImageDecoderFrameInfo* info) {
+    if (!info) {
+        return { 0, 0, 0, 0};
+    }
+
+    const SkIRect& r = toFrameInfo(info)->fFrameRect;
+    return { r.left(), r.top(), r.right(), r.bottom() };
+}
+
+bool AImageDecoderFrameInfo_hasAlphaWithinBounds(const AImageDecoderFrameInfo* info) {
+    if (!info) return false;
+
+    return toFrameInfo(info)->fHasAlphaWithinBounds;
+}
+
+int32_t AImageDecoderFrameInfo_getDisposeOp(const AImageDecoderFrameInfo* info) {
+    if (!info) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+    static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kKeep)
+                  == ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE);
+    static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kRestoreBGColor)
+                  == ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND);
+    static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kRestorePrevious)
+                  == ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS);
+    return static_cast<int>(toFrameInfo(info)->fDisposalMethod);
+}
+
+int32_t AImageDecoderFrameInfo_getBlendOp(const AImageDecoderFrameInfo* info) {
+    if (!info) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+    switch (toFrameInfo(info)->fBlend) {
+        case SkCodecAnimation::Blend::kSrc:
+            return ANDROID_IMAGE_DECODER_BLEND_OP_SRC;
+        case SkCodecAnimation::Blend::kSrcOver:
+            return ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER;
+    }
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index a184ab9..d8c3cef 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,5 +1,6 @@
 LIBJNIGRAPHICS {
   global:
+    AImageDecoder_resultToString; # introduced=31
     AImageDecoder_createFromAAsset; # introduced=30
     AImageDecoder_createFromFd; # introduced=30
     AImageDecoder_createFromBuffer; # introduced=30
@@ -15,12 +16,22 @@
     AImageDecoder_setCrop; # introduced=30
     AImageDecoder_isAnimated; # introduced=31
     AImageDecoder_getRepeatCount; # introduced=31
+    AImageDecoder_advanceFrame; # introduced=31
+    AImageDecoder_rewind; # introduced=31
+    AImageDecoder_getFrameInfo; # introduced = 31
     AImageDecoderHeaderInfo_getWidth; # introduced=30
     AImageDecoderHeaderInfo_getHeight; # introduced=30
     AImageDecoderHeaderInfo_getMimeType; # introduced=30
     AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30
     AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30
     AImageDecoderHeaderInfo_getDataSpace; # introduced=30
+    AImageDecoderFrameInfo_create; # introduced = 31
+    AImageDecoderFrameInfo_delete; # introduced = 31
+    AImageDecoderFrameInfo_getDuration; # introduced = 31
+    AImageDecoderFrameInfo_getFrameRect; # introduced = 31
+    AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced = 31
+    AImageDecoderFrameInfo_getDisposeOp; # introduced = 31
+    AImageDecoderFrameInfo_getBlendOp; # introduced = 31
     AndroidBitmap_getInfo;
     AndroidBitmap_getDataSpace;
     AndroidBitmap_lockPixels;
diff --git a/native/webview/TEST_MAPPING b/native/webview/TEST_MAPPING
new file mode 100644
index 0000000..bd25200
--- /dev/null
+++ b/native/webview/TEST_MAPPING
@@ -0,0 +1,36 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWebkitTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsHostsideWebViewTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "GtsWebViewTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.test.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "GtsWebViewHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.test.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml
index bad0497..12dc170 100644
--- a/packages/FusedLocation/AndroidManifest.xml
+++ b/packages/FusedLocation/AndroidManifest.xml
@@ -40,7 +40,7 @@
              LocationManagerService will bind to the service with the highest
              version. -->
         <service android:name="com.android.location.fused.FusedLocationService"
-                 android:exported="true"
+                 android:exported="false"
                  android:permission="android.permission.WRITE_SECURE_SETTINGS">
            <intent-filter>
                <action android:name="com.android.location.service.FusedLocationProvider" />
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 6827d6e..cb55c72 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -20,6 +20,8 @@
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.QUALITY_LOW_POWER;
+import static android.location.provider.ProviderProperties.ACCURACY_FINE;
+import static android.location.provider.ProviderProperties.POWER_USAGE_LOW;
 
 import static com.android.location.provider.ProviderRequestUnbundled.INTERVAL_DISABLED;
 
@@ -28,113 +30,57 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.location.LocationRequest;
-import android.os.WorkSource;
+import android.location.provider.LocationProviderBase;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
+import android.os.Bundle;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.location.ProviderRequest;
-import com.android.location.provider.LocationProviderBase;
-import com.android.location.provider.ProviderPropertiesUnbundled;
-import com.android.location.provider.ProviderRequestUnbundled;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /** Basic fused location provider implementation. */
 public class FusedLocationProvider extends LocationProviderBase {
 
     private static final String TAG = "FusedLocationProvider";
 
-    private static final ProviderPropertiesUnbundled PROPERTIES =
-            ProviderPropertiesUnbundled.create(
-                    /* requiresNetwork = */ false,
-                    /* requiresSatellite = */ false,
-                    /* requiresCell = */ false,
-                    /* hasMonetaryCost = */ false,
-                    /* supportsAltitude = */ true,
-                    /* supportsSpeed = */ true,
-                    /* supportsBearing = */ true,
-                    Criteria.POWER_LOW,
-                    Criteria.ACCURACY_FINE
-            );
+    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
+                .setHasAltitudeSupport(true)
+                .setHasSpeedSupport(true)
+                .setHasBearingSupport(true)
+                .setPowerUsage(POWER_USAGE_LOW)
+                .setAccuracy(ACCURACY_FINE)
+                .build();
 
     private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds
 
-    final Object mLock = new Object();
+    private final Object mLock = new Object();
 
     private final Context mContext;
     private final LocationManager mLocationManager;
-    private final LocationListener mGpsListener;
-    private final LocationListener mNetworkListener;
+    private final ChildLocationListener mGpsListener;
+    private final ChildLocationListener mNetworkListener;
     private final BroadcastReceiver mUserChangeReceiver;
 
     @GuardedBy("mLock")
-    ProviderRequestUnbundled mRequest;
-    @GuardedBy("mLock")
-    private WorkSource mWorkSource;
-    @GuardedBy("mLock")
-    private long mGpsInterval;
-    @GuardedBy("mLock")
-    private long mNetworkInterval;
+    private ProviderRequest mRequest;
 
     @GuardedBy("mLock")
-    @Nullable private Location mFusedLocation;
-    @GuardedBy("mLock")
-    @Nullable Location mGpsLocation;
-    @GuardedBy("mLock")
-    @Nullable Location mNetworkLocation;
+    private @Nullable Location mFusedLocation;
 
     public FusedLocationProvider(Context context) {
         super(context, TAG, PROPERTIES);
         mContext = context;
-        mLocationManager = context.getSystemService(LocationManager.class);
+        mLocationManager = Objects.requireNonNull(context.getSystemService(LocationManager.class));
 
-        mGpsListener = new LocationListener() {
-            @Override
-            public void onLocationChanged(Location location) {
-                synchronized (mLock) {
-                    mGpsLocation = location;
-                    reportBestLocationLocked();
-                }
-            }
-
-            @Override
-            public void onProviderDisabled(String provider) {
-                synchronized (mLock) {
-                    // if satisfying a bypass request, don't clear anything
-                    if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
-                        return;
-                    }
-
-                    mGpsLocation = null;
-                }
-            }
-        };
-
-        mNetworkListener = new LocationListener() {
-            @Override
-            public void onLocationChanged(Location location) {
-                synchronized (mLock) {
-                    mNetworkLocation = location;
-                    reportBestLocationLocked();
-                }
-            }
-
-            @Override
-            public void onProviderDisabled(String provider) {
-                synchronized (mLock) {
-                    // if satisfying a bypass request, don't clear anything
-                    if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
-                        return;
-                    }
-
-                    mNetworkLocation = null;
-                }
-            }
-        };
+        mGpsListener = new ChildLocationListener(GPS_PROVIDER);
+        mNetworkListener = new ChildLocationListener(NETWORK_PROVIDER);
 
         mUserChangeReceiver = new BroadcastReceiver() {
             @Override
@@ -147,10 +93,7 @@
             }
         };
 
-        mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
-        mWorkSource = new WorkSource();
-        mGpsInterval = INTERVAL_DISABLED;
-        mNetworkInterval = INTERVAL_DISABLED;
+        mRequest = ProviderRequest.EMPTY_REQUEST;
     }
 
     void start() {
@@ -161,57 +104,53 @@
         mContext.unregisterReceiver(mUserChangeReceiver);
 
         synchronized (mLock) {
-            mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
+            mRequest = ProviderRequest.EMPTY_REQUEST;
             updateRequirementsLocked();
         }
     }
 
     @Override
-    public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) {
+    public void onSetRequest(ProviderRequest request) {
         synchronized (mLock) {
             mRequest = request;
-            mWorkSource = workSource;
             updateRequirementsLocked();
         }
     }
 
-    @GuardedBy("mLock")
-    private void updateRequirementsLocked() {
-        long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getInterval()
-                : INTERVAL_DISABLED;
-        long networkInterval = mRequest.getInterval();
+    @Override
+    public void onFlush(OnFlushCompleteCallback callback) {
+        OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() {
+            private int mFlushCount = 2;
 
-        if (gpsInterval != mGpsInterval) {
-            resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener);
-            mGpsInterval = gpsInterval;
-        }
-        if (networkInterval != mNetworkInterval) {
-            resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval,
-                    mNetworkListener);
-            mNetworkInterval = networkInterval;
-        }
+            @Override
+            public void onFlushComplete() {
+                if (--mFlushCount == 0) {
+                    callback.onFlushComplete();
+                }
+            }
+        };
+
+        mGpsListener.flush(wrapper);
+        mNetworkListener.flush(wrapper);
     }
 
+    @Override
+    public void onSendExtraCommand(String command, @Nullable Bundle extras) {}
+
     @GuardedBy("mLock")
-    private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval,
-            LocationListener listener) {
-        if (oldInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) {
-            mLocationManager.removeUpdates(listener);
-        }
-        if (newInterval != INTERVAL_DISABLED) {
-            LocationRequest request = new LocationRequest.Builder(newInterval)
-                    .setQuality(mRequest.getQuality())
-                    .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored())
-                    .setWorkSource(mWorkSource)
-                    .build();
-            mLocationManager.requestLocationUpdates(provider, request, mContext.getMainExecutor(),
-                    listener);
-        }
+    private void updateRequirementsLocked() {
+        long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis()
+                : INTERVAL_DISABLED;
+        long networkInterval = mRequest.getIntervalMillis();
+
+        mGpsListener.resetProviderRequest(gpsInterval);
+        mNetworkListener.resetProviderRequest(networkInterval);
     }
 
     @GuardedBy("mLock")
     void reportBestLocationLocked() {
-        Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation);
+        Location bestLocation = chooseBestLocation(mGpsListener.getLocation(),
+                mNetworkListener.getLocation());
         if (bestLocation == mFusedLocation) {
             return;
         }
@@ -228,25 +167,25 @@
         // clear cached locations when the user changes to prevent leaking user information
         synchronized (mLock) {
             mFusedLocation = null;
-            mGpsLocation = null;
-            mNetworkLocation = null;
+            mGpsListener.clearLocation();
+            mNetworkListener.clearLocation();
         }
     }
 
     void dump(PrintWriter writer) {
         synchronized (mLock) {
             writer.println("request: " + mRequest);
-            if (mGpsInterval != INTERVAL_DISABLED) {
-                writer.println("  gps interval: " + mGpsInterval);
+            if (mGpsListener.getInterval() != INTERVAL_DISABLED) {
+                writer.println("  gps interval: " + mGpsListener.getInterval());
             }
-            if (mNetworkInterval != INTERVAL_DISABLED) {
-                writer.println("  network interval: " + mNetworkInterval);
+            if (mNetworkListener.getInterval() != INTERVAL_DISABLED) {
+                writer.println("  network interval: " + mNetworkListener.getInterval());
             }
-            if (mGpsLocation != null) {
-                writer.println("  last gps location: " + mGpsLocation);
+            if (mGpsListener.getLocation() != null) {
+                writer.println("  last gps location: " + mGpsListener.getLocation());
             }
-            if (mNetworkLocation != null) {
-                writer.println("  last network location: " + mNetworkLocation);
+            if (mNetworkListener.getLocation() != null) {
+                writer.println("  last network location: " + mNetworkListener.getLocation());
             }
         }
     }
@@ -279,4 +218,104 @@
         }
         return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB;
     }
+
+    private class ChildLocationListener implements LocationListener {
+
+        private final String mProvider;
+        private final SparseArray<OnFlushCompleteCallback> mPendingFlushes;
+
+        @GuardedBy("mLock")
+        private int mNextFlushCode = 0;
+        @GuardedBy("mLock")
+        private @Nullable Location mLocation = null;
+        @GuardedBy("mLock")
+        private long mInterval = INTERVAL_DISABLED;
+
+        ChildLocationListener(String provider) {
+            mProvider = provider;
+            mPendingFlushes = new SparseArray<>();
+        }
+
+        @Nullable Location getLocation() {
+            synchronized (mLock) {
+                return mLocation;
+            }
+        }
+
+        long getInterval() {
+            synchronized (mLock) {
+                return mInterval;
+            }
+        }
+
+        void clearLocation() {
+            synchronized (mLock) {
+                mLocation = null;
+            }
+        }
+
+        private void resetProviderRequest(long newInterval) {
+            synchronized (mLock) {
+                if (newInterval == mInterval) {
+                    return;
+                }
+
+                if (mInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) {
+                    mLocationManager.removeUpdates(this);
+                }
+
+                mInterval = newInterval;
+
+                if (mInterval != INTERVAL_DISABLED) {
+                    LocationRequest request = new LocationRequest.Builder(mInterval)
+                            .setMaxUpdateDelayMillis(mRequest.getMaxUpdateDelayMillis())
+                            .setQuality(mRequest.getQuality())
+                            .setLowPower(mRequest.isLowPower())
+                            .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored())
+                            .setWorkSource(mRequest.getWorkSource())
+                            .build();
+                    mLocationManager.requestLocationUpdates(mProvider, request,
+                            mContext.getMainExecutor(), this);
+                }
+            }
+        }
+
+        void flush(OnFlushCompleteCallback callback) {
+            synchronized (mLock) {
+                int requestCode = mNextFlushCode++;
+                mPendingFlushes.put(requestCode, callback);
+                mLocationManager.requestFlush(mProvider, this, requestCode);
+            }
+        }
+
+        @Override
+        public void onLocationChanged(Location location) {
+            synchronized (mLock) {
+                mLocation = location;
+                reportBestLocationLocked();
+            }
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+            synchronized (mLock) {
+                // if satisfying a bypass request, don't clear anything
+                if (mRequest.isActive() && mRequest.isLocationSettingsIgnored()) {
+                    return;
+                }
+
+                mLocation = null;
+            }
+        }
+
+        @Override
+        public void onFlushComplete(int requestCode) {
+            synchronized (mLock) {
+                OnFlushCompleteCallback callback = mPendingFlushes.removeReturnOld(requestCode);
+                if (callback != null) {
+                    callback.onFlushComplete();
+                }
+            }
+        }
+    }
 }
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 2bda530..d472311 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -27,18 +27,18 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ILocationProvider;
+import android.location.provider.ILocationProviderManager;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
-import android.os.WorkSource;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.location.ILocationProvider;
-import com.android.internal.location.ILocationProviderManager;
-import com.android.internal.location.ProviderRequest;
 import com.android.location.fused.FusedLocationProvider;
 
 import org.junit.After;
@@ -71,7 +71,7 @@
         long seed = System.currentTimeMillis();
         Log.i(TAG, "location seed: " + seed);
 
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = ApplicationProvider.getApplicationContext();
         mRandom = new Random(seed);
         mLocationManager = context.getSystemService(LocationManager.class);
 
@@ -120,8 +120,7 @@
         mProvider.setRequest(
                         new ProviderRequest.Builder()
                                 .setIntervalMillis(1000)
-                                .build(),
-                new WorkSource());
+                                .build());
 
         Location location = createLocation(NETWORK_PROVIDER, mRandom);
         mLocationManager.setTestProviderLocation(NETWORK_PROVIDER, location);
@@ -135,8 +134,7 @@
                 new ProviderRequest.Builder()
                         .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
                         .setIntervalMillis(1000)
-                        .build(),
-                new WorkSource());
+                        .build());
 
         Location location = createLocation(GPS_PROVIDER, mRandom);
         mLocationManager.setTestProviderLocation(GPS_PROVIDER, location);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 1f3ee6d..b0c3169 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -347,17 +347,19 @@
         if (!wasSetUp) {
             return;
         }
-
-        // load dummy layout with OK button disabled until we override this layout in
-        // startInstallConfirm
-        bindUi();
-        checkIfAllowedAndInitiateInstall();
     }
 
     @Override
     protected void onResume() {
         super.onResume();
 
+        if (mAppSnippet != null) {
+            // load dummy layout with OK button disabled until we override this layout in
+            // startInstallConfirm
+            bindUi();
+            checkIfAllowedAndInitiateInstall();
+        }
+
         if (mOk != null) {
             mOk.setEnabled(mEnableOk);
         }
diff --git a/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml b/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml
new file mode 100644
index 0000000..ed9d85e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml
@@ -0,0 +1,30 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="38dp"
+        android:height="24dp"
+        android:viewportWidth="38.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9.45,14.48h1.8c-0.05,0.98 -0.24,1.82 -0.6,2.53c-0.35,0.7 -0.85,1.24 -1.51,1.62c-0.66,0.38 -1.48,0.57 -2.47,0.57c-0.71,0 -1.35,-0.14 -1.92,-0.42c-0.57,-0.28 -1.06,-0.68 -1.47,-1.2c-0.4,-0.53 -0.71,-1.16 -0.93,-1.89c-0.21,-0.74 -0.32,-1.56 -0.32,-2.48v-2.63c0,-0.91 0.11,-1.74 0.32,-2.47c0.22,-0.74 0.54,-1.36 0.95,-1.88C3.71,5.69 4.21,5.29 4.8,5.01c0.6,-0.28 1.28,-0.42 2.03,-0.42c0.92,0 1.71,0.19 2.34,0.56c0.64,0.36 1.14,0.9 1.48,1.61c0.35,0.7 0.55,1.57 0.59,2.59h-1.8C9.41,8.59 9.29,7.98 9.1,7.52C8.91,7.04 8.63,6.69 8.26,6.47C7.9,6.24 7.42,6.13 6.84,6.13c-0.52,0 -0.97,0.1 -1.36,0.31C5.1,6.65 4.79,6.95 4.54,7.34C4.3,7.72 4.12,8.19 3.99,8.74c-0.12,0.54 -0.18,1.15 -0.18,1.82v2.65c0,0.62 0.05,1.21 0.15,1.75c0.1,0.54 0.27,1.02 0.49,1.43c0.23,0.4 0.52,0.72 0.89,0.95c0.36,0.23 0.81,0.34 1.33,0.34c0.66,0 1.18,-0.11 1.56,-0.32c0.38,-0.21 0.67,-0.56 0.85,-1.03C9.27,15.85 9.39,15.23 9.45,14.48z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M24.87,4.78l-1.74,9.72l-0.2,1.64l-0.29,-1.44l-2.21,-9.92l-0.2,0l-1.06,0l-0.22,0l-2.28,9.92l-0.28,1.4l-0.18,-1.59l-1.78,-9.73l-1.79,0l2.83,14.22l0.34,0l0.92,0l0.35,0l2.48,-10.36l0.14,-0.84l0.15,0.84l2.36,10.36l0.36,0l0.92,0l0.34,0l2.82,-14.22z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M35.72,6.32l0,-1.54l-5.61,0l-0.34,0l-1.45,0l0,14.22l1.79,0l0,-6.28l4.8,0l0,-1.54l-4.8,0l0,-4.86z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index c2b26bc..d76a8a1 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gas"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 65ce6e0..85b5bc2 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 3ea96c7..f260559 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - التحسين لسلامة البطارية"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - التحسين للحفاظ على سلامة البطارية"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
@@ -557,7 +557,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string>
     <string name="user_nickname" msgid="262624187455825083">"اللقب"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index a259687..32be72d 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 2cbd681..88e99ce 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 4d8e348..4a820ea 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Napuniće se za <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizuje se radi stanja baterije"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizuje se radi boljeg stanja baterije"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 4d6f3ac..6a16246 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index ef0412b..07ba66a 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизиране за състоян. на батерията"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизиране с цел състоянието на батерията"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 5b95399..99ee1d6 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 00d8087..eedad34 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 98d502d..209b2d6 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimitzant per a l\'estat de la bateria"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està optimitzant per preservar l\'estat de la bateria"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Àlies"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index a9a5f48..b3141e4 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Host"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 738ed3c..a7fa91f 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index e274d63..361b932 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alias"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 66244eb..a3b3d26 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για ολοκλήρωση της φόρτισης"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Βελτιστοποίηση κατάστασης μπαταρίας"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Βελτιστοποίηση για τη διατήρηση της καλής κατάστασης της μπαταρίας"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index f5a0e2c..fe7515f 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 6f11e80..75a6678 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index f5a0e2c..fe7515f 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index f5a0e2c..fe7515f 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index bfca1095..aed0800 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎Creating new user…‎‏‎‎‏‎"</string>
     <string name="user_nickname" msgid="262624187455825083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎Nickname‎‏‎‎‏‎"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎Add guest‎‏‎‎‏‎"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎Remove guest‎‏‎‎‏‎"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎End guest session‎‏‎‎‏‎"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎Guest‎‏‎‎‏‎"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎Take a photo‎‏‎‎‏‎"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎Choose an image‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 44ae8c2..f9260a8 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 236394c..bc17623 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apodo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index c8fd94f..c73f136 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 226ffba..2f51ac0 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Optimizatzen bateria egoera onean mantentzeko"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Optimizatzen, bateria egoera onean mantentzeko"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 917a637..038b815 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string>
     <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index b103802..33b6142 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 91d14d4..01492c2 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la charge complète"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimisation pour la santé de la pile"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimisation pour préserver la pile"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 2518fe1..04ed5fe 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 486afe3..b59395c 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimizando para manter a batería en bo estado"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimizando a preservación da batería"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alcume"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 2bebe36..601ffde 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 824ab25..974d00c 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string>
     <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index cd21a5f..f792428 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index c3b859c..2bf5325 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Becenév"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 041ad0e..fea1e12 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լիցքավորումը"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Օպտիմալացվում է"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Օպտիմալացվում է մարտկոցի պահպանման համար"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 332d5b3..7298cea 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index f401364..bf6c031 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index ef5e2ff..2ed92e4 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index db2ffe5..bf4c35e 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string>
     <string name="user_nickname" msgid="262624187455825083">"כינוי"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 9e9db3d..f467c60 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index ec39008..493839f 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
     <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 881a13c..83d859b 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батареяның жұмыс істеу қабілеті оңтайландырылуда"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарея жұмысын оңтайландыру"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 14f7788..db571d6 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើត​អ្នកប្រើប្រាស់ថ្មី…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះ​ហៅក្រៅ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូល​ភ្ញៀវ"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"លុប​​​ភ្ញៀវ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើស​រូបភាព"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index a279d22..2e8b9f1 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಇದೆ"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯ ಆರೋಗ್ಯಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯ ಸುಸ್ಥಿತಿಗಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index de69488..0294e9d 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string>
     <string name="user_nickname" msgid="262624187455825083">"닉네임"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e70460f..0c73cf6 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index c63cec8..fbe814a 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index d040edf..b3d8e17 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 74c0dd5..3c69e64 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 739bceb..b909359 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Прекар"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index b7de429..8f83ee1 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്‌ടിക്കുന്നു…"</string>
     <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index c7a2edd..8168b79 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Цэнэглэх хүртэл үлдсэн <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейн чанарыг оновчилж байна"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейн барилтыг оновчилж байна"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Хоч"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index d29cb59..f47d8e0 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string>
     <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index c593e4b..fb2826f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index aa9d3d6..d98d442 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string>
     <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9494c39..944c48e 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> til batteriet er fulladet"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimaliserer batteritilstanden"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – optimaliserer batteritilstanden"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index e31fb4e..9a0d2ed 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string>
     <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 0b75fdd..a8e4fb5 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index cfde61f..306ef44 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 1221862..e231a26 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਸਥਿਤੀ ਲਈ ਅਨੁਕੂਲ ਬਣਾਇਆ ਜਾ ਰਿਹਾ"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਸਥਿਤੀ ਲਈ ਅਨੁਕੂਲ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 102835c..a62e9cc 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Do naładowania <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optymalizuję, by utrzymać baterię w dobrym stanie"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optymalizuję, aby utrzymać baterię w dobrym stanie"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 99019a6..fe922c0 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 238e54b..5d1f880 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 99019a6..fe922c0 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index df95dbc..765f83a 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 68826d7..98b3203 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 736445d..087f1403 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ආරෝපණය වන තෙක් <xliff:g id="TIME">%1$s</xliff:g> ඇත"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරි සෞඛ්‍යය සඳහා ප්‍රශස්ත කරමින්"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරි ආයු කාලය වැඩි දියුණු කරමින්"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්‍ර ආරෝපණය"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string>
     <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 265ec8f..858f017 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 2e6fc0e..2386b13 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Še <xliff:g id="TIME">%1$s</xliff:g> do polne napolnjenosti"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizacija za ohran. zmog. baterije"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizacija za ohranjanje zmogljivosti baterije"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 821e897b..8d53b25 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 207ca74..0b64a70 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Напуниће се за <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизује се ради стања батерије"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизује се ради бољег стања батерије"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
@@ -554,7 +554,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Надимак"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 7afd344..58eda86 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index b08da89..889163b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -492,8 +492,8 @@
     <string name="status_unavailable" msgid="5279036186589861608">"Hamna"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Imechagua anwani ya MAC kwa nasibu"</string>
     <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">Imeunganisha vifaa %1$d</item>
-      <item quantity="one">Imeunganisha kifaa %1$d</item>
+      <item quantity="other">Vifaa %1$d vimeunganishwa</item>
+      <item quantity="one">Kifaa %1$d kimeunganishwa</item>
     </plurals>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Muda zaidi."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Muda kidogo."</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 5fdf8bd..e0de778 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string>
     <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 38f08b2..53f4abf 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీ స్థితిని ఆప్టిమైజ్ చేయడం కోసం"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీ జీవితకాలాన్ని పెంచడం కోసం ఆప్టిమైజ్ చేస్తోంది"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్‌ను క్రియేట్ చేస్తోంది…"</string>
     <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్‌ను ఎంచుకోండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index a895138..5f93563 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -284,7 +284,7 @@
     <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
     <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"ลดการเปลืองแบตเตอรี่และเพิ่มประสิทธิภาพเครือข่าย"</string>
     <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"เมื่อเปิดใช้โหมดนี้ ที่อยู่ MAC ของอุปกรณ์นี้อาจเปลี่ยนทุกครั้งที่เชื่อมต่อกับเครือข่ายที่มีการเปิดใช้การสุ่ม MAC"</string>
-    <string name="wifi_metered_label" msgid="8737187690304098638">"แบบจำกัดปริมาณอินเทอร์เน็ต"</string>
+    <string name="wifi_metered_label" msgid="8737187690304098638">"แบบจำกัดปริมาณ"</string>
     <string name="wifi_unmetered_label" msgid="6174142840934095093">"ไม่มีการวัดปริมาณอินเทอร์เน็ต"</string>
     <string name="select_logd_size_title" msgid="1604578195914595173">"ขนาดบัฟเฟอร์ของตัวบันทึก"</string>
     <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"เลือกขนาด Logger ต่อบัฟเฟอร์ไฟล์บันทึก"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index b451d89..9bbfb1d 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index e1ff56b..9dd32b6 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 6806ce6..99cc958 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – стан акумулятора оптимізується"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимізація для збереження заряду акумулятора"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
@@ -555,7 +555,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 5a8b6dc..515cf70 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string>
     <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 7530016..1830046 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> ichida toʻladi"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻladi"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Batareya quvvati muvozanatlanmoqda"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Batareya uchun optimizatsiya"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nik"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 643fdb9..2d56e45 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 81d8106..6b64d27 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string>
     <string name="user_nickname" msgid="262624187455825083">"昵称"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"访客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 39a12aa..4ab580e 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"還需 <xliff:g id="TIME">%1$s</xliff:g>才能充滿電"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在優化電池狀態"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 優化電池效能"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充電"</string>
@@ -553,7 +553,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
+    <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index cc35ed3..46695db 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -450,7 +450,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g>後充飽電"</string>
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string>
-    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 針對電池狀態進行最佳化調整"</string>
+    <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 最佳化調整電池狀態"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index bcc6369..f4ff6ec 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -553,7 +553,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
+    <!-- no translation found for guest_exit_guest (4754204715192830850) -->
+    <skip />
     <string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0ce8dd8..792fb52 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1447,6 +1447,9 @@
     <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
     <string name="data_connection_5g_plus" translatable="false">5G+</string>
 
+    <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_carrier_wifi">CWF</string>
+
     <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
     <string name="cell_data_off_content_description">Mobile data off</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index a921053..02d1c2e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -23,12 +23,12 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.provider.Settings;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.Log;
 
 /**
@@ -58,35 +58,18 @@
     private boolean mWifiRestartInProgress = false;
     private boolean mTelephonyRestartInProgress = false;
     private RecoveryStatusCallback mCurrentRecoveryCallback = null;
-    private final BroadcastReceiver mWifiMonitor = new BroadcastReceiver() {
+    private final SubsystemRestartTrackingCallback mWifiSubsystemRestartTrackingCallback =
+            new SubsystemRestartTrackingCallback() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!mWifiRestartInProgress || mCurrentRecoveryCallback == null) {
-                stopTrackingWifiRestart();
-            }
+        public void onSubsystemRestarting() {
+            // going to do nothing on this - already assuming that subsystem is restarting
+        }
 
-            // TODO: harden this code to avoid race condition. What if WiFi toggled just before
-            // recovery triggered. Either use new broadcasts from framework or detect more state
-            // changes.
-            boolean recoveryDone = false;
-            if (TextUtils.equals(intent.getAction(), WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-                if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                        WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED) {
-                    recoveryDone = true;
-                }
-            } else if (TextUtils.equals(intent.getAction(),
-                    WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
-                if (intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
-                        WifiManager.WIFI_AP_STATE_FAILED) == WifiManager.WIFI_AP_STATE_ENABLED) {
-                    recoveryDone = true;
-                }
-            }
-
-            if (recoveryDone) {
-                mWifiRestartInProgress = false;
-                stopTrackingWifiRestart();
-                checkIfAllSubsystemsRestartsAreDone();
-            }
+        @Override
+        public void onSubsystemRestarted() {
+            mWifiRestartInProgress = false;
+            stopTrackingWifiRestart();
+            checkIfAllSubsystemsRestartsAreDone();
         }
     };
     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@@ -208,13 +191,13 @@
     }
 
     private void startTrackingWifiRestart() {
-        IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(mWifiMonitor, filter, null, mHandler);
+        mWifiManager.registerSubsystemRestartTrackingCallback(new HandlerExecutor(mHandler),
+                mWifiSubsystemRestartTrackingCallback);
     }
 
     private void stopTrackingWifiRestart() {
-        mContext.unregisterReceiver(mWifiMonitor);
+        mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+                mWifiSubsystemRestartTrackingCallback);
     }
 
     private void startTrackingTelephonyRestart() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
similarity index 97%
rename from packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
rename to packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
index 61af5a7..647fd2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
@@ -19,7 +19,7 @@
 /**
  * Class to access MediaOutput constants.
  */
-public class MediaOutputSliceConstants {
+public class MediaOutputConstants {
 
     /**
      * Key for the Remote Media slice.
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index c2613a5..0cb9906 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -43,6 +43,7 @@
     public static final int ICON_1X = R.drawable.ic_1x_mobiledata;
     public static final int ICON_5G = R.drawable.ic_5g_mobiledata;
     public static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
+    public static final int ICON_CWF = R.drawable.ic_carrier_wifi;
 
     public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
             "CARRIER_NETWORK_CHANGE",
@@ -276,6 +277,20 @@
             0,
             false);
 
+    public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup(
+            "CWF",
+            /* sbIcons= */ null,
+            /* qsIcons= */ null,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+            /* sbNullState= */ 0,
+            /* qsNullState= */ 0,
+            /* sbDiscState= */ 0,
+            /* qsDiscState= */ 0,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+            R.string.data_connection_carrier_wifi,
+            TelephonyIcons.ICON_CWF,
+            /* isWide= */ true);
+
     // When adding a new MobileIconGround, check if the dataContentDescription has to be filtered
     // in QSCarrier#hasValidTypeContentDescription
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index cbb5105..4614694 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
+import android.util.FeatureFlagUtils;
 
 import com.android.settingslib.R;
 
@@ -103,11 +104,14 @@
     private Network mDefaultNetwork = null;
     private NetworkCapabilities mDefaultNetworkCapabilities = null;
     private final Runnable mCallback;
+    private final boolean mProviderModel;
 
     private WifiInfo mWifiInfo;
     public boolean enabled;
     public boolean isCaptivePortal;
     public boolean isDefaultNetwork;
+    public boolean isCarrierMerged;
+    public int subId;
     public int state;
     public boolean connected;
     public String ssid;
@@ -124,6 +128,8 @@
         mNetworkScoreManager = networkScoreManager;
         mConnectivityManager = connectivityManager;
         mCallback = callback;
+        mProviderModel = FeatureFlagUtils.isEnabled(
+                mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
 
     public void setListening(boolean listening) {
@@ -193,6 +199,10 @@
             } else {
                 ssid = getValidSsid(mWifiInfo);
             }
+            if (mProviderModel) {
+                isCarrierMerged = mWifiInfo.isCarrierMerged();
+                subId = mWifiInfo.getSubscriptionId();
+            }
             updateRssi(mWifiInfo.getRssi());
             maybeRequestNetworkScore();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 323449a..6cc863a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,6 +1,7 @@
 package com.android.keyguard;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
@@ -107,7 +108,10 @@
     /**
      * Boolean value indicating if notifications are visible on lock screen.
      */
-    private boolean mHasVisibleNotifications;
+    private boolean mHasVisibleNotifications = true;
+
+    private AnimatorSet mClockInAnim = null;
+    private AnimatorSet mClockOutAnim = null;
 
     /**
      * If the Keyguard Slice has a header (big center-aligned text.)
@@ -318,12 +322,15 @@
     private void animateClockChange(boolean useLargeClock) {
         if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) return;
 
+        if (mClockInAnim != null) mClockInAnim.cancel();
+        if (mClockOutAnim != null) mClockOutAnim.cancel();
+
         View in, out;
         int direction = 1;
         if (useLargeClock) {
             out = mNewLockscreenClockFrame;
             in = mNewLockscreenLargeClockFrame;
-            addView(in);
+            if (indexOfChild(in) == -1) addView(in);
             direction = -1;
         } else {
             in = mNewLockscreenClockFrame;
@@ -333,25 +340,35 @@
             removeView(out);
         }
 
-        AnimatorSet outAnim = new AnimatorSet();
-        outAnim.setDuration(CLOCK_OUT_MILLIS);
-        outAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-        outAnim.playTogether(
+        mClockOutAnim = new AnimatorSet();
+        mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
+        mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+        mClockOutAnim.playTogether(
                 ObjectAnimator.ofFloat(out, View.ALPHA, 0f),
                 ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0,
                         direction * -mClockSwitchYAmount));
+        mClockOutAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                mClockOutAnim = null;
+            }
+        });
 
         in.setAlpha(0);
         in.setVisibility(View.VISIBLE);
-        AnimatorSet inAnim = new AnimatorSet();
-        inAnim.setDuration(CLOCK_IN_MILLIS);
-        inAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        inAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
+        mClockInAnim = new AnimatorSet();
+        mClockInAnim.setDuration(CLOCK_IN_MILLIS);
+        mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
                 ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0));
-        inAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
+        mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
+        mClockInAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                mClockInAnim = null;
+            }
+        });
 
-        inAnim.start();
-        outAnim.start();
+        mClockInAnim.start();
+        mClockOutAnim.start();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 926062b..f9505de 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,9 +28,9 @@
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index a1bde58..943a54e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -22,9 +22,9 @@
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 4f85192..5dd2f06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -46,7 +46,7 @@
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputSliceConstants;
+import com.android.settingslib.media.MediaOutputConstants;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
@@ -449,8 +449,8 @@
         mCallback.dismissDialog();
         final ActivityStarter.OnDismissAction postKeyguardAction = () -> {
             mContext.sendBroadcast(new Intent()
-                    .setAction(MediaOutputSliceConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING)
-                    .setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME));
+                    .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING)
+                    .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME));
             mShadeController.animateCollapsePanels();
             return true;
         };
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 0ce0c02..bd3f5a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -20,7 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.text.TextUtils
-import com.android.settingslib.media.MediaOutputSliceConstants
+import com.android.settingslib.media.MediaOutputConstants
 import javax.inject.Inject
 
 /**
@@ -30,10 +30,10 @@
     private val mediaOutputDialogFactory: MediaOutputDialogFactory
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
-        if (TextUtils.equals(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
+        if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
                         intent.action)) {
             mediaOutputDialogFactory.create(
-                    intent.getStringExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME), false)
+                    intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME), false)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 6d9d587..54e30af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -91,11 +91,11 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEvents;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
new file mode 100644
index 0000000..143121a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -0,0 +1,113 @@
+/*
+ * 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.systemui.screenshot;
+
+import static android.graphics.ColorSpace.Named.SRGB;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.media.Image;
+
+/**
+ * Holds a hardware image, coordinates and render node to draw the tile. The tile manages clipping
+ * and dimensions. The tile must be drawn translated to the correct target position:
+ * <pre>
+ *     ImageTile tile = getTile();
+ *     canvas.save();
+ *     canvas.translate(tile.getLeft(), tile.getTop());
+ *     canvas.drawRenderNode(tile.getDisplayList());
+ *     canvas.restore();
+ * </pre>
+ */
+class ImageTile implements AutoCloseable {
+    private final Image mImage;
+    private final Rect mLocation;
+    private RenderNode mNode;
+
+    private static final ColorSpace COLOR_SPACE = ColorSpace.get(SRGB);
+
+    /**
+     * Create an image tile from the given image.
+     *
+     * @param image an image containing a hardware buffer
+     * @param location the captured area represented by image tile (virtual coordinates)
+     */
+    ImageTile(Image image, Rect location) {
+        mImage = requireNonNull(image, "image");
+        mLocation = location;
+
+        requireNonNull(mImage.getHardwareBuffer(), "image must be a hardware image");
+    }
+
+    RenderNode getDisplayList() {
+        if (mNode == null) {
+            mNode = new RenderNode("Tile{" + Integer.toHexString(mImage.hashCode()) + "}");
+        }
+        if (mNode.hasDisplayList()) {
+            return mNode;
+        }
+        final int w = Math.min(mImage.getWidth(), mLocation.width());
+        final int h = Math.min(mImage.getHeight(), mLocation.height());
+        mNode.setPosition(0, 0, w, h);
+
+        RecordingCanvas canvas = mNode.beginRecording(w, h);
+        Rect rect = new Rect(0, 0, w, h);
+        canvas.save();
+        canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
+        canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
+                0, 0, null);
+        canvas.restore();
+        mNode.endRecording();
+        return mNode;
+    }
+
+    Rect getLocation() {
+        return mLocation;
+    }
+
+    int getLeft() {
+        return mLocation.left;
+    }
+
+    int getTop() {
+        return mLocation.top;
+    }
+
+    int getRight() {
+        return mLocation.right;
+    }
+
+    int getBottom() {
+        return mLocation.bottom;
+    }
+
+    @Override
+    public void close() {
+        mImage.close();
+        mNode.discardDisplayList();
+    }
+
+    @Override
+    public String toString() {
+        return "{location=" + mLocation + ", source=" + mImage
+                + ", buffer=" + mImage.getHardwareBuffer() + "}";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
new file mode 100644
index 0000000..8ff66f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.screenshot;
+
+import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.UiThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Owns a series of partial screen captures (tiles).
+ * <p>
+ * To display on-screen, use {@link #getDrawable()}.
+ */
+@UiThread
+class ImageTileSet {
+
+    private static final String TAG = "ImageTileSet";
+
+    interface OnBoundsChangedListener {
+        /**
+         * Reports an update to the bounding box that contains all active tiles. These are virtual
+         * (capture) coordinates which can be either negative or positive.
+         */
+        void onBoundsChanged(int left, int top, int right, int bottom);
+    }
+
+    interface OnContentChangedListener {
+        /**
+         * Mark as dirty and rebuild display list.
+         */
+        void onContentChanged();
+    }
+
+    private final List<ImageTile> mTiles = new ArrayList<>();
+    private final Rect mBounds = new Rect();
+
+    private OnContentChangedListener mOnContentChangedListener;
+    private OnBoundsChangedListener mOnBoundsChangedListener;
+
+    void setOnBoundsChangedListener(OnBoundsChangedListener listener) {
+        mOnBoundsChangedListener = listener;
+    }
+
+    void setOnContentChangedListener(OnContentChangedListener listener) {
+        mOnContentChangedListener = listener;
+    }
+
+    void addTile(ImageTile tile) {
+        final Rect newBounds = new Rect(mBounds);
+        final Rect newRect = tile.getLocation();
+        mTiles.add(tile);
+        newBounds.union(newRect);
+        if (!newBounds.equals(mBounds)) {
+            mBounds.set(newBounds);
+            if (mOnBoundsChangedListener != null) {
+                mOnBoundsChangedListener.onBoundsChanged(
+                        newBounds.left, newBounds.top, newBounds.right, newBounds.bottom);
+            }
+        }
+        if (mOnContentChangedListener != null) {
+            mOnContentChangedListener.onContentChanged();
+        }
+    }
+
+    /**
+     * Returns a drawable to paint the combined contents of the tiles. Drawable dimensions are
+     * zero-based and map directly to {@link #getLeft()}, {@link #getTop()}, {@link #getRight()},
+     * and {@link #getBottom()} which are dimensions relative to the capture start position
+     * (positive or negative).
+     *
+     * @return a drawable to display the image content
+     */
+    Drawable getDrawable() {
+        return new TiledImageDrawable(this);
+    }
+
+    boolean  isEmpty() {
+        return mTiles.isEmpty();
+    }
+
+    int size() {
+        return mTiles.size();
+    }
+
+    ImageTile get(int i) {
+        return mTiles.get(i);
+    }
+
+    Bitmap toBitmap() {
+        if (mTiles.isEmpty()) {
+            return null;
+        }
+        final RenderNode output = new RenderNode("Bitmap Export");
+        output.setPosition(0, 0, getWidth(), getHeight());
+        RecordingCanvas canvas = output.beginRecording();
+        canvas.translate(-getLeft(), -getTop());
+        for (ImageTile tile : mTiles) {
+            canvas.save();
+            canvas.translate(tile.getLeft(), tile.getTop());
+            canvas.drawRenderNode(tile.getDisplayList());
+            canvas.restore();
+        }
+        output.endRecording();
+        return HardwareRenderer.createHardwareBitmap(output, getWidth(), getHeight());
+    }
+
+    int getLeft() {
+        return mBounds.left;
+    }
+
+    int getTop() {
+        return mBounds.top;
+    }
+
+    int getRight() {
+        return mBounds.right;
+    }
+
+    int getBottom() {
+        return mBounds.bottom;
+    }
+
+    int getWidth() {
+        return mBounds.width();
+    }
+
+    int getHeight() {
+        return mBounds.height();
+    }
+
+    void clear() {
+        mBounds.set(0, 0, 0, 0);
+        mTiles.forEach(ImageTile::close);
+        mTiles.clear();
+        if (mOnBoundsChangedListener != null) {
+            mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0);
+        }
+        if (mOnContentChangedListener != null) {
+            mOnContentChangedListener.onContentChanged();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index e159992..bb07012 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
 
+import static java.lang.Math.min;
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.UiContext;
@@ -44,15 +45,20 @@
 import javax.inject.Inject;
 
 /**
- * High level interface to scroll capture API.
+ * High(er) level interface to scroll capture API.
  */
 public class ScrollCaptureClient {
+    private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024);
+    private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed
+    private static final int MAX_PAGES = 5;
+    private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE;
 
     @VisibleForTesting
     static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
 
     private static final String TAG = LogConfig.logTag(ScrollCaptureClient.class);
 
+
     /**
      * A connection to a remote window. Starts a capture session.
      */
@@ -60,13 +66,10 @@
         /**
          * Session start should be deferred until UI is active because of resource allocation and
          * potential visible side effects in the target window.
-         *
-         * @param maxBuffers the maximum number of buffers (tiles) that may be in use at one
-         *                   time, tiles are not cached anywhere so set this to a large enough
-         *                   number to retain offscreen content until it is no longer needed
+
          * @param sessionConsumer listener to receive the session once active
          */
-        void start(int maxBuffers, Consumer<Session> sessionConsumer);
+        void start(Consumer<Session> sessionConsumer);
 
         /**
          * Close the connection.
@@ -100,26 +103,33 @@
      */
     interface Session {
         /**
-         * Request the given horizontal strip. Values are y-coordinates in captured space, relative
-         * to start position.
+         * Request an image tile at the given position, from top, to top + {@link #getTileHeight()},
+         * and from left 0, to {@link #getPageWidth()}
          *
-         * @param contentRect the area to capture, in content rect space, relative to scroll-bounds
+         * @param top the top (y) position of the tile to capture, in content rect space
          * @param consumer listener to be informed of the result
          */
-        void requestTile(Rect contentRect, Consumer<CaptureResult> consumer);
+        void requestTile(int top, Consumer<CaptureResult> consumer);
 
         /**
-         * End the capture session, return the target app to original state. The returned
-         * stage must be waited for to complete to allow the target app a chance to restore to
-         * original state before becoming visible.
+         * Returns the maximum number of tiles which may be requested and retained without
+         * being {@link Image#close() closed}.
          *
-         * @return a stage presenting the session shutdown
+         * @return the maximum number of open tiles allowed
+         */
+        int getMaxTiles();
+
+        int getTileHeight();
+
+        int getPageHeight();
+
+        int getPageWidth();
+
+        /**
+         * End the capture session, return the target app to original state. The listener
+         * will be called when the target app is ready to before visible and interactive.
          */
         void end(Runnable listener);
-
-        int getMaxTileHeight();
-
-        int getMaxTileWidth();
     }
 
     private final IWindowManager mWindowManagerService;
@@ -131,6 +141,12 @@
         mWindowManagerService = windowManagerService;
     }
 
+    /**
+     * Set the window token for the screenshot window/ This is required to avoid targeting our
+     * window or any above it.
+     *
+     * @param token the windowToken of the screenshot window
+     */
     public void setHostWindowToken(IBinder token) {
         mHostWindowToken = token;
     }
@@ -176,6 +192,8 @@
 
         private ImageReader mReader;
         private Rect mScrollBounds;
+        private int mTileHeight;
+        private int mTileWidth;
         private Rect mRequestRect;
         private boolean mStarted;
 
@@ -197,6 +215,15 @@
             mScrollBounds = scrollBounds;
             mConnectionConsumer.accept(this);
             mConnectionConsumer = null;
+
+            int pxPerPage = mScrollBounds.width() * mScrollBounds.height();
+            int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE));
+            mTileWidth = mScrollBounds.width();
+            mTileHeight = pxPerTile  / mScrollBounds.width();
+            if (DEBUG_SCROLL) {
+                Log.d(TAG, "scrollBounds: " + mScrollBounds);
+                Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight);
+            }
         }
 
         @Override
@@ -257,24 +284,19 @@
 
         // ScrollCaptureController.Connection
 
-        // -> Error handling: BiConsumer<Session, Throwable> ?
         @Override
-        public void start(int maxBufferCount, Consumer<Session> sessionConsumer) {
+        public void start(Consumer<Session> sessionConsumer) {
             if (DEBUG_SCROLL) {
-                Log.d(TAG, "start(maxBufferCount=" + maxBufferCount
-                        + ", sessionConsumer=" + sessionConsumer + ")");
+                Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")");
             }
-            mReader = ImageReader.newInstance(mScrollBounds.width(), mScrollBounds.height(),
-                    PixelFormat.RGBA_8888, maxBufferCount, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+            mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
+                    MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
             mSessionConsumer = sessionConsumer;
             try {
                 mConnection.startCapture(mReader.getSurface());
                 mStarted = true;
             } catch (RemoteException e) {
-                Log.w(TAG, "should not be happening :-(");
-                // ?
-                //mSessionListener.onError(e);
-                //mSessionListener = null;
+                Log.w(TAG, "Failed to start", e);
             }
         }
 
@@ -307,27 +329,36 @@
         }
 
         @Override
-        public int getMaxTileHeight() {
+        public int getPageHeight() {
             return mScrollBounds.height();
         }
 
         @Override
-        public int getMaxTileWidth() {
+        public int getPageWidth() {
             return mScrollBounds.width();
         }
 
         @Override
-        public void requestTile(Rect contentRect, Consumer<CaptureResult> consumer) {
+        public int getTileHeight() {
+            return mTileHeight;
+        }
+
+        @Override
+        public int getMaxTiles() {
+            return MAX_IMAGE_COUNT;
+        }
+
+        @Override
+        public void requestTile(int top, Consumer<CaptureResult> consumer) {
             if (DEBUG_SCROLL) {
-                Log.d(TAG, "requestTile(contentRect=" + contentRect + "consumer=" + consumer + ")");
+                Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")");
             }
-            mRequestRect = new Rect(contentRect);
+            mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight);
             mResultConsumer = consumer;
             try {
                 mConnection.requestImage(mRequestRect);
             } catch (RemoteException e) {
                 Log.e(TAG, "Caught remote exception from requestImage", e);
-                // ?
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 27c74ac..c75efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,16 +16,9 @@
 
 package com.android.systemui.screenshot;
 
-import static android.graphics.ColorSpace.Named.SRGB;
-
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorSpace;
-import android.graphics.Picture;
-import android.graphics.Rect;
-import android.media.Image;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.util.Log;
@@ -46,6 +39,8 @@
 public class ScrollCaptureController {
     private static final String TAG = "ScrollCaptureController";
 
+    private static final boolean USE_TILED_IMAGE = false;
+
     public static final int MAX_PAGES = 5;
     public static final int MAX_HEIGHT = 12000;
 
@@ -55,7 +50,7 @@
     private final Executor mUiExecutor;
     private final Executor mBgExecutor;
     private final ImageExporter mImageExporter;
-    private Picture mPicture;
+    private final ImageTileSet mImageTileSet;
 
     public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
             Executor bgExecutor, ImageExporter exporter) {
@@ -64,6 +59,7 @@
         mUiExecutor = uiExecutor;
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
+        mImageTileSet = new ImageTileSet();
     }
 
     /**
@@ -72,50 +68,50 @@
      * @param after action to take after the flow is complete
      */
     public void run(final Runnable after) {
-        mConnection.start(MAX_PAGES, (session) -> startCapture(session, after));
+        mConnection.start((session) -> startCapture(session, after));
     }
 
     private void startCapture(Session session, final Runnable onDismiss) {
-        Rect requestRect = new Rect(0, 0,
-                session.getMaxTileWidth(), session.getMaxTileHeight());
         Consumer<ScrollCaptureClient.CaptureResult> consumer =
                 new Consumer<ScrollCaptureClient.CaptureResult>() {
 
                     int mFrameCount = 0;
+                    int mTop = 0;
 
                     @Override
                     public void accept(ScrollCaptureClient.CaptureResult result) {
                         mFrameCount++;
+
                         boolean emptyFrame = result.captured.height() == 0;
                         if (!emptyFrame) {
-                            mPicture = stackBelow(mPicture, result.image, result.captured.width(),
-                                    result.captured.height());
+                            mImageTileSet.addTile(new ImageTile(result.image, result.captured));
                         }
+
                         if (emptyFrame || mFrameCount >= MAX_PAGES
-                                || requestRect.bottom > MAX_HEIGHT) {
-                            if (mPicture != null) {
-                                exportToFile(mPicture, session, onDismiss);
+                                || mTop + session.getTileHeight() > MAX_HEIGHT) {
+                            if (!mImageTileSet.isEmpty()) {
+                                exportToFile(mImageTileSet.toBitmap(), session, onDismiss);
+                                mImageTileSet.clear();
                             } else {
                                 session.end(onDismiss);
                             }
                             return;
                         }
-                        requestRect.offset(0, session.getMaxTileHeight());
-                        session.requestTile(requestRect, /* consumer */ this);
+                        mTop += result.captured.height();
+                        session.requestTile(mTop, /* consumer */ this);
                     }
                 };
 
         // fire it up!
-        session.requestTile(requestRect, consumer);
+        session.requestTile(0, consumer);
     };
 
-    void exportToFile(Picture picture, Session session, Runnable afterEnd) {
+    void exportToFile(Bitmap bitmap, Session session, Runnable afterEnd) {
         mImageExporter.setFormat(Bitmap.CompressFormat.PNG);
         mImageExporter.setQuality(6);
         ListenableFuture<Uri> future =
-                mImageExporter.export(mBgExecutor, Bitmap.createBitmap(picture));
+                mImageExporter.export(mBgExecutor, bitmap);
         future.addListener(() -> {
-            picture.close(); // release resources
             try {
                 launchViewer(future.get());
             } catch (InterruptedException | ExecutionException e) {
@@ -126,41 +122,6 @@
         }, mUiExecutor);
     }
 
-    /**
-     * Combine the top {@link Picture} with an {@link Image} by appending the image directly
-     * below, creating a result that is the combined height of both.
-     * <p>
-     * Note: no pixel data is transferred here, only a record of drawing commands. Backing
-     * hardware buffers must not be modified/recycled until the picture is
-     * {@link Picture#close closed}.
-     *
-     * @param top the existing picture
-     * @param below the image to append below
-     * @param cropWidth the width of the pixel data to use from the image
-     * @param cropHeight the height of the pixel data to use from the image
-     *
-     * @return a new Picture which draws the previous picture with the image below it
-     */
-    private static Picture stackBelow(Picture top, Image below, int cropWidth, int cropHeight) {
-        int width = cropWidth;
-        int height = cropHeight;
-        if (top != null) {
-            height += top.getHeight();
-            width = Math.max(width, top.getWidth());
-        }
-        Picture combined = new Picture();
-        Canvas canvas = combined.beginRecording(width, height);
-        int y = 0;
-        if (top != null) {
-            canvas.drawPicture(top, new Rect(0, 0, top.getWidth(), top.getHeight()));
-            y += top.getHeight();
-        }
-        canvas.drawBitmap(Bitmap.wrapHardwareBuffer(
-                below.getHardwareBuffer(), ColorSpace.get(SRGB)), 0, y, null);
-        combined.endRecording();
-        return combined;
-    }
-
     void launchViewer(Uri uri) {
         Intent editIntent = new Intent(Intent.ACTION_VIEW);
         editIntent.setType("image/png");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
new file mode 100644
index 0000000..72f489b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.screenshot;
+
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+/**
+ * Draws a set of hardware image tiles from a display list. The tiles exist in virtual coordinate
+ * space that may extend into positive or negative values. The origin is the upper-left-most corner
+ * of bounding box, which is drawn at 0,0 to the drawable (output) bounds.
+ */
+public class TiledImageDrawable extends Drawable {
+
+    private static final String TAG = "TiledImageDrawable";
+
+    private final ImageTileSet mTiles;
+    private RenderNode mNode;
+
+    public TiledImageDrawable(ImageTileSet tiles) {
+        mTiles = tiles;
+        mTiles.setOnContentChangedListener(this::onContentChanged);
+    }
+
+    private void onContentChanged() {
+        if (mNode != null && mNode.hasDisplayList()) {
+            mNode.discardDisplayList();
+        }
+        invalidateSelf();
+    }
+
+    private void rebuildDisplayListIfNeeded() {
+        if (mNode != null && mNode.hasDisplayList()) {
+            return;
+        }
+        if (mNode == null) {
+            mNode = new RenderNode("TiledImageDrawable");
+        }
+        mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight());
+        Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight());
+        // Align content (virtual) top/left with 0,0, within the render node
+        canvas.translate(-mTiles.getLeft(), -mTiles.getTop());
+        for (int i = 0; i < mTiles.size(); i++) {
+            ImageTile tile = mTiles.get(i);
+            canvas.save();
+            canvas.translate(tile.getLeft(), tile.getTop());
+            canvas.drawRenderNode(tile.getDisplayList());
+            canvas.restore();
+        }
+        mNode.endRecording();
+    }
+
+    /**
+     * Draws the tiled image to the canvas, with the top/left (virtual) coordinate aligned to 0,0
+     * placed at left/top of the drawable's bounds.
+     */
+    @Override
+    public void draw(Canvas canvas) {
+        rebuildDisplayListIfNeeded();
+        if (canvas.isHardwareAccelerated()) {
+            Rect bounds = getBounds();
+            canvas.save();
+            canvas.clipRect(bounds);
+            canvas.translate(bounds.left, bounds.top);
+            canvas.drawRenderNode(mNode);
+            canvas.restore();
+        } else {
+            Log.d(TAG, "Canvas is not hardware accelerated. Skipping draw!");
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mTiles.getWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mTiles.getHeight();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mNode.setAlpha(alpha / 255f)) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        throw new IllegalArgumentException("not implemented");
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index dbee0ee..9ef304d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -20,6 +20,8 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -456,12 +458,14 @@
             if (target == null) {
                 return;
             }
-            Integer value = (Integer) target.getTag(iconVisible
+            final Integer data = (Integer) target.getTag(iconVisible
                     ? com.android.internal.R.id.tag_margin_end_when_icon_visible
                     : com.android.internal.R.id.tag_margin_end_when_icon_gone);
-            if (value == null) {
+            if (data == null) {
                 return;
             }
+            final DisplayMetrics metrics = target.getResources().getDisplayMetrics();
+            final int value = TypedValue.complexToDimensionPixelOffset(data, metrics);
             if (target instanceof NotificationHeaderView) {
                 ((NotificationHeaderView) target).setTopLineExtraMarginEnd(value);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 291aefe..e419966 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -359,7 +359,7 @@
 
         // broadcasts
         IntentFilter filter = new IntentFilter();
-        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
index 9db109d..66e8082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import com.android.settingslib.AccessibilityContentDescriptions;
+import com.android.settingslib.SignalIcon.IconGroup;
 import com.android.systemui.R;
 
 public class WifiIcons {
@@ -40,6 +42,7 @@
             WIFI_NO_INTERNET_ICONS,
             WIFI_FULL_ICONS
     };
+
     static final int[][] WIFI_SIGNAL_STRENGTH = QS_WIFI_SIGNAL_STRENGTH;
 
     public static final int QS_WIFI_DISABLED = com.android.internal.R.drawable.ic_wifi_signal_0;
@@ -47,4 +50,16 @@
     static final int WIFI_NO_NETWORK = QS_WIFI_NO_NETWORK;
 
     static final int WIFI_LEVEL_COUNT = WIFI_SIGNAL_STRENGTH[0].length;
+
+    public static final IconGroup UNMERGED_WIFI = new IconGroup(
+            "Wi-Fi Icons",
+            WifiIcons.WIFI_SIGNAL_STRENGTH,
+            WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
+            AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
+            WifiIcons.WIFI_NO_NETWORK,
+            WifiIcons.QS_WIFI_NO_NETWORK,
+            WifiIcons.WIFI_NO_NETWORK,
+            WifiIcons.QS_WIFI_NO_NETWORK,
+            AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+    );
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 6d109ac..4954286 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -25,12 +25,15 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
+import android.text.Html;
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.SignalIcon.IconGroup;
+import com.android.settingslib.SignalIcon.MobileIconGroup;
 import com.android.settingslib.SignalIcon.State;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.wifi.WifiStatusTracker;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
@@ -42,6 +45,9 @@
         SignalController<WifiSignalController.WifiState, IconGroup> {
     private final boolean mHasMobileDataFeature;
     private final WifiStatusTracker mWifiTracker;
+    private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI;
+    private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI;
+    private final WifiManager mWifiManager;
 
     public WifiSignalController(Context context, boolean hasMobileDataFeature,
             CallbackHandler callbackHandler, NetworkControllerImpl networkController,
@@ -49,6 +55,7 @@
             NetworkScoreManager networkScoreManager) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
                 callbackHandler, networkController);
+        mWifiManager = wifiManager;
         mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
                 connectivityManager, this::handleStatusUpdated);
         mWifiTracker.setListening(true);
@@ -57,18 +64,7 @@
             wifiManager.registerTrafficStateCallback(context.getMainExecutor(),
                     new WifiTrafficStateCallback());
         }
-        // WiFi only has one state.
-        mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
-                "Wi-Fi Icons",
-                WifiIcons.WIFI_SIGNAL_STRENGTH,
-                WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
-                AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
-                WifiIcons.WIFI_NO_NETWORK,
-                WifiIcons.QS_WIFI_NO_NETWORK,
-                WifiIcons.WIFI_NO_NETWORK,
-                WifiIcons.QS_WIFI_NO_NETWORK,
-                AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-                );
+        mCurrentState.iconGroup = mLastState.iconGroup = mUnmergedWifiIconGroup;
     }
 
     @Override
@@ -82,6 +78,14 @@
 
     @Override
     public void notifyListeners(SignalCallback callback) {
+        if (mCurrentState.isCarrierMerged) {
+            notifyListenersForCarrierWifi(callback);
+        } else {
+            notifyListenersForNonCarrierWifi(callback);
+        }
+    }
+
+    private void notifyListenersForNonCarrierWifi(SignalCallback callback) {
         // only show wifi in the cluster if connected or if wifi-only
         boolean visibleWhenEnabled = mContext.getResources().getBoolean(
                 R.bool.config_showWifiIndicatorWhenEnabled);
@@ -104,14 +108,49 @@
                 wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
     }
 
-    private void copyWifiStates() {
-        mCurrentState.enabled = mWifiTracker.enabled;
-        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
-        mCurrentState.connected = mWifiTracker.connected;
-        mCurrentState.ssid = mWifiTracker.ssid;
-        mCurrentState.rssi = mWifiTracker.rssi;
-        mCurrentState.level = mWifiTracker.level;
-        mCurrentState.statusLabel = mWifiTracker.statusLabel;
+    private void notifyListenersForCarrierWifi(SignalCallback callback) {
+        MobileIconGroup icons = mCarrierMergedWifiIconGroup;
+        String contentDescription = getTextIfExists(getContentDescription()).toString();
+        CharSequence dataContentDescriptionHtml = getTextIfExists(icons.dataContentDescription);
+
+        CharSequence dataContentDescription = Html.fromHtml(
+                dataContentDescriptionHtml.toString(), 0).toString();
+        if (mCurrentState.inetCondition == 0) {
+            dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
+        }
+        boolean qsVisible = mCurrentState.enabled
+                && (mCurrentState.connected && mCurrentState.inetCondition == 1);
+
+        IconState statusIcon =
+                new IconState(qsVisible, getCurrentIconIdForCarrierWifi(), contentDescription);
+        int qsTypeIcon = mCurrentState.connected ? icons.qsDataType : 0;
+        int typeIcon = mCurrentState.connected ? icons.dataType : 0;
+        IconState qsIcon = new IconState(
+                mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), contentDescription);
+        CharSequence description = mNetworkController.getMobileDataNetworkName();
+        callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
+                mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription,
+                dataContentDescriptionHtml, description, icons.isWide,
+                mCurrentState.subId, /* roaming= */ false);
+    }
+
+    private int getCurrentIconIdForCarrierWifi() {
+        int level = mCurrentState.level;
+        // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so
+        // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count.
+        int totalLevel = mWifiManager.getMaxSignalLevel() + 1;
+        boolean noInternet = mCurrentState.inetCondition == 0;
+        if (mCurrentState.connected) {
+            return SignalDrawable.getState(level, totalLevel, noInternet);
+        } else if (mCurrentState.enabled) {
+            return SignalDrawable.getEmptyState(totalLevel);
+        } else {
+            return 0;
+        }
+    }
+
+    private int getQsCurrentIconIdForCarrierWifi() {
+        return getCurrentIconIdForCarrierWifi();
     }
 
     /**
@@ -137,6 +176,21 @@
         notifyListenersIfNecessary();
     }
 
+    private void copyWifiStates() {
+        mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
+        mCurrentState.connected = mWifiTracker.connected;
+        mCurrentState.ssid = mWifiTracker.ssid;
+        mCurrentState.rssi = mWifiTracker.rssi;
+        mCurrentState.level = mWifiTracker.level;
+        mCurrentState.statusLabel = mWifiTracker.statusLabel;
+        mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
+        mCurrentState.subId = mWifiTracker.subId;
+        mCurrentState.iconGroup =
+                mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup
+                        : mUnmergedWifiIconGroup;
+    }
+
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT
@@ -157,10 +211,12 @@
     }
 
     static class WifiState extends State {
-        String ssid;
-        boolean isTransient;
-        boolean isDefault;
-        String statusLabel;
+        public String ssid;
+        public boolean isTransient;
+        public boolean isDefault;
+        public String statusLabel;
+        public boolean isCarrierMerged;
+        public int subId;
 
         @Override
         public void copyFrom(State s) {
@@ -170,6 +226,8 @@
             isTransient = state.isTransient;
             isDefault = state.isDefault;
             statusLabel = state.statusLabel;
+            isCarrierMerged = state.isCarrierMerged;
+            subId = state.subId;
         }
 
         @Override
@@ -178,7 +236,9 @@
             builder.append(",ssid=").append(ssid)
                 .append(",isTransient=").append(isTransient)
                 .append(",isDefault=").append(isDefault)
-                .append(",statusLabel=").append(statusLabel);
+                .append(",statusLabel=").append(statusLabel)
+                .append(",isCarrierMerged=").append(isCarrierMerged)
+                .append(",subId=").append(subId);
         }
 
         @Override
@@ -190,7 +250,9 @@
             return Objects.equals(other.ssid, ssid)
                     && other.isTransient == isTransient
                     && other.isDefault == isDefault
-                    && TextUtils.equals(other.statusLabel, statusLabel);
+                    && TextUtils.equals(other.statusLabel, statusLabel)
+                    && other.isCarrierMerged == isCarrierMerged
+                    && other.subId == subId;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 765fd32..1d3f26e7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -17,23 +17,22 @@
 package com.android.systemui.wmshell;
 
 import android.content.Context;
-import android.os.Handler;
 import android.view.IWindowManager;
 
 import com.android.systemui.dagger.WMSingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.Transitions;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 
-import java.util.concurrent.Executor;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -46,9 +45,9 @@
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
-            DisplayController displayController, @Main Executor mainExecutor,
+            DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor,
             TransactionPool transactionPool) {
-        return new DisplayImeController(wmService, displayController, mainExecutor,
+        return new DisplayImeController(wmService, displayController, shellMainExecutor,
                 transactionPool);
     }
 
@@ -56,11 +55,12 @@
     @Provides
     static LegacySplitScreen provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController displayImeController, @Main Handler handler,
-            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
-            SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) {
+            DisplayImeController displayImeController, TransactionPool transactionPool,
+            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
+            TaskStackListenerImpl taskStackListener, Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new LegacySplitScreenController(context, displayController, systemWindows,
-                displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue,
-                taskStackListener);
+                displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
+                taskStackListener, transitions, mainExecutor);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 88cbddd..b46751d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -36,7 +36,9 @@
 import com.android.wm.shell.FullscreenTaskListener;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellCommandHandler;
+import com.android.wm.shell.ShellCommandHandlerImpl;
 import com.android.wm.shell.ShellInit;
+import com.android.wm.shell.ShellInitImpl;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.Transitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -158,7 +160,7 @@
                 // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
                 // that uses the SF vsync
                 handler.setProvider(new SfVsyncFrameCallbackProvider());
-            }, 1, TimeUnit.SECONDS);
+            });
             return handler;
         } catch (InterruptedException e) {
             throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
@@ -173,14 +175,14 @@
             Optional<LegacySplitScreen> legacySplitScreenOptional,
             Optional<AppPairs> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions) {
-        return new ShellInit(displayImeController,
+            @ShellMainThread ShellExecutor shellMainExecutor) {
+        return ShellInitImpl.create(displayImeController,
                 dragAndDropController,
                 shellTaskOrganizer,
                 legacySplitScreenOptional,
                 appPairsOptional,
                 fullscreenTaskListener,
-                transitions);
+                shellMainExecutor);
     }
 
     /**
@@ -195,9 +197,11 @@
             Optional<Pip> pipOptional,
             Optional<OneHanded> oneHandedOptional,
             Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional) {
-        return Optional.of(new ShellCommandHandler(shellTaskOrganizer, legacySplitScreenOptional,
-                pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional));
+            Optional<AppPairs> appPairsOptional,
+            @ShellMainThread ShellExecutor shellMainExecutor) {
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+                legacySplitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
+                appPairsOptional, shellMainExecutor));
     }
 
     @WMSingleton
@@ -208,9 +212,9 @@
 
     @WMSingleton
     @Provides
-    static DisplayController provideDisplayController(Context context, @Main Handler handler,
-            IWindowManager wmService) {
-        return new DisplayController(context, handler, wmService);
+    static DisplayController provideDisplayController(Context context,
+            IWindowManager wmService, @ShellMainThread ShellExecutor shellMainExecutor) {
+        return new DisplayController(context, wmService, shellMainExecutor);
     }
 
     @WMSingleton
@@ -269,9 +273,9 @@
 
     @WMSingleton
     @Provides
-    static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler,
-            TransactionPool pool) {
-        return new SyncTransactionQueue(pool, handler);
+    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+            @ShellMainThread ShellExecutor shellMainExecutor) {
+        return new SyncTransactionQueue(pool, shellMainExecutor);
     }
 
     @WMSingleton
@@ -288,10 +292,12 @@
         return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
     }
 
+    // We currently dedupe multiple messages, so we use the shell main handler directly
     @WMSingleton
     @Provides
-    static TaskStackListenerImpl providerTaskStackListenerImpl(@Main Handler handler) {
-        return new TaskStackListenerImpl(handler);
+    static TaskStackListenerImpl providerTaskStackListenerImpl(
+            @ShellMainThread Handler shellMainHandler) {
+        return new TaskStackListenerImpl(shellMainHandler);
     }
 
     @BindsOptionalOf
@@ -309,11 +315,12 @@
             WindowManagerShellWrapper windowManagerShellWrapper,
             LauncherApps launcherApps,
             UiEventLogger uiEventLogger,
-            @Main Handler mainHandler,
-            ShellTaskOrganizer organizer) {
+            ShellTaskOrganizer organizer,
+            @ShellMainThread ShellExecutor shellMainExecutor) {
         return Optional.of(BubbleController.create(context, null /* synchronizer */,
                 floatingContentCoordinator, statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, uiEventLogger, mainHandler, organizer));
+                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+                shellMainExecutor));
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 3399a0b..509419e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.Transitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
@@ -35,6 +36,8 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -46,8 +49,6 @@
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -64,9 +65,9 @@
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
-            DisplayController displayController, @Main Executor mainExecutor,
+            DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor,
             TransactionPool transactionPool) {
-        return new DisplayImeController(wmService, displayController, mainExecutor,
+        return new DisplayImeController(wmService, displayController, shellMainExecutor,
                 transactionPool);
     }
 
@@ -74,12 +75,13 @@
     @Provides
     static LegacySplitScreen provideLegacySplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController displayImeController, @Main Handler handler,
-            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
-            SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) {
+            DisplayImeController displayImeController, TransactionPool transactionPool,
+            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
+            TaskStackListenerImpl taskStackListener, Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new LegacySplitScreenController(context, displayController, systemWindows,
-                displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue,
-                taskStackListener);
+                displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
+                taskStackListener, transitions, mainExecutor);
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
index b3176dd..4d32a3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
@@ -40,8 +40,8 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.pip.Pip;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index 4aa730e..c1c6371 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -96,13 +96,13 @@
 
         Connection conn = mConnectionConsumer.getValue();
 
-        conn.start(5, mSessionConsumer);
+        conn.start(mSessionConsumer);
         verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
 
         Session session = mSessionConsumer.getValue();
-        Rect request = new Rect(0, 0, session.getMaxTileWidth(), session.getMaxTileHeight());
+        Rect request = new Rect(0, 0, session.getPageWidth(), session.getTileHeight());
 
-        session.requestTile(request, mResultConsumer);
+        session.requestTile(0, mResultConsumer);
         verify(mResultConsumer, timeout(100)).accept(any(CaptureResult.class));
 
         CaptureResult result = mResultConsumer.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 230aeab..ccc2eb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,6 +89,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -100,6 +102,7 @@
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 
 import com.google.common.collect.ImmutableList;
 
@@ -203,6 +206,8 @@
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
     @Mock
     private BubbleLogger mBubbleLogger;
+    @Mock
+    private ShellTaskOrganizer mShellTaskOrganizer;
 
     private TestableBubblePositioner mPositioner;
 
@@ -268,6 +273,7 @@
                 );
 
         when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
+        when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mBubbleData,
@@ -278,9 +284,9 @@
                 mWindowManagerShellWrapper,
                 mLauncherApps,
                 mBubbleLogger,
-                mock(Handler.class),
-                mock(ShellTaskOrganizer.class),
-                mPositioner);
+                mShellTaskOrganizer,
+                mPositioner,
+                mock(ShellExecutor.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         mBubblesManager = new BubblesManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index bbcc30a..00f4e3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -83,6 +83,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.BubbleData;
@@ -93,6 +95,7 @@
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -185,6 +188,8 @@
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
     @Mock
     private BubbleLogger mBubbleLogger;
+    @Mock
+    private ShellTaskOrganizer mShellTaskOrganizer;
 
     private TestableBubblePositioner mPositioner;
 
@@ -236,6 +241,7 @@
                         mock(Handler.class)
                 );
         when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
+        when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mBubbleData,
@@ -246,9 +252,9 @@
                 mWindowManagerShellWrapper,
                 mLauncherApps,
                 mBubbleLogger,
-                mock(Handler.class),
-                mock(ShellTaskOrganizer.class),
-                mPositioner);
+                mShellTaskOrganizer,
+                mPositioner,
+                mock(ShellExecutor.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         mBubblesManager = new BubblesManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index fd39b6e..3f918e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -30,6 +30,7 @@
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 
 /**
  * Testable BubbleController subclass that immediately synchronizes surfaces.
@@ -46,12 +47,12 @@
             WindowManagerShellWrapper windowManagerShellWrapper,
             LauncherApps launcherApps,
             BubbleLogger bubbleLogger,
-            Handler mainHandler,
             ShellTaskOrganizer shellTaskOrganizer,
-            BubblePositioner positioner) {
+            BubblePositioner positioner,
+            ShellExecutor shellMainExecutor) {
         super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                bubbleLogger, mainHandler, shellTaskOrganizer, positioner);
+                bubbleLogger, shellTaskOrganizer, positioner, shellMainExecutor);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 73d87b0..31bf7120 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -35,12 +35,12 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml
index 7382565..3986119 100644
--- a/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml
+++ b/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml
@@ -18,5 +18,5 @@
 -->
 <resources>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">80dp</dimen>
+    <dimen name="navigation_bar_gesture_larger_height">80dp</dimen>
 </resources>
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index e7ddf86..b456ba6 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -1,2 +1,3 @@
 per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS
 per-file wifi.proto = file:/wifi/OWNERS
+per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6ce334b..019d8c5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -141,7 +141,7 @@
         "capture_state_listener-aidl-java",
         "dnsresolver_aidl_interface-java",
         "icu4j_calendar_astronomer",
-        "netd_aidl_interfaces-platform-java",
+        "netd-client",
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
         "com.android.sysprop.watchdog",
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 01021345..9ba71dc 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -20,12 +20,14 @@
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
 import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
 import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -49,12 +51,15 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import android.os.UserHandle;
 import android.service.SensorPrivacyIndividualEnabledSensorProto;
 import android.service.SensorPrivacyServiceDumpProto;
+import android.service.SensorPrivacyUserProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -63,9 +68,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.pm.UserManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -84,9 +91,19 @@
 
     private static final String TAG = "SensorPrivacyService";
 
+    /** Version number indicating compatibility parsing the persisted file */
+    private static final int CURRENT_PERSISTENCE_VERSION = 1;
+    /** Version number indicating the persisted data needs upgraded to match new internal data
+     *  structures and features */
+    private static final int CURRENT_VERSION = 1;
+
     private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
     private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
+    private static final String XML_TAG_USER = "user";
     private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy";
+    private static final String XML_ATTRIBUTE_ID = "id";
+    private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
+    private static final String XML_ATTRIBUTE_VERSION = "version";
     private static final String XML_ATTRIBUTE_ENABLED = "enabled";
     private static final String XML_ATTRIBUTE_SENSOR = "sensor";
 
@@ -96,10 +113,18 @@
     private static final String EXTRA_SENSOR = SensorPrivacyService.class.getName()
             + ".extra.sensor";
 
+    // These are associated with fields that existed for older persisted versions of files
+    private static final int VER0_ENABLED = 0;
+    private static final int VER0_INDIVIDUAL_ENABLED = 1;
+    private static final int VER1_ENABLED = 0;
+    private static final int VER1_INDIVIDUAL_ENABLED = 1;
+
     private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
+    private final UserManagerInternal mUserManagerInternal;
 
     public SensorPrivacyService(Context context) {
         super(context);
+        mUserManagerInternal = getLocalService(UserManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
     }
 
@@ -117,8 +142,9 @@
         @GuardedBy("mLock")
         private final AtomicFile mAtomicFile;
         @GuardedBy("mLock")
-        private boolean mEnabled;
-        private SparseBooleanArray mIndividualEnabled = new SparseBooleanArray();
+        private SparseBooleanArray mEnabled = new SparseBooleanArray();
+        @GuardedBy("mLock")
+        private SparseArray<SparseBooleanArray> mIndividualEnabled = new SparseArray<>();
 
         SensorPrivacyServiceImpl(Context context) {
             mContext = context;
@@ -127,7 +153,9 @@
                     SENSOR_PRIVACY_XML_FILE);
             mAtomicFile = new AtomicFile(sensorPrivacyFile);
             synchronized (mLock) {
-                readPersistedSensorPrivacyStateLocked();
+                if (readPersistedSensorPrivacyStateLocked()) {
+                    persistSensorPrivacyStateLocked();
+                }
             }
 
             int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA};
@@ -138,7 +166,8 @@
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    setIndividualSensorPrivacy(intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
+                    setIndividualSensorPrivacy(intent.getIntExtra(Intent.EXTRA_USER_ID, -1),
+                            intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
                 }
             }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY));
         }
@@ -174,7 +203,8 @@
          * @param sensor The sensor that is attempting to be used
          */
         private void onSensorUseStarted(int uid, String packageName, int sensor) {
-            if (!isIndividualSensorPrivacyEnabled(sensor)) {
+            int userId = UserHandle.getUserId(uid);
+            if (!isIndividualSensorPrivacyEnabled(userId, sensor)) {
                 return;
             }
 
@@ -216,7 +246,8 @@
                                     PendingIntent.getBroadcast(mContext, sensor,
                                             new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
                                                     .setPackage(mContext.getPackageName())
-                                                    .putExtra(EXTRA_SENSOR, sensor),
+                                                    .putExtra(EXTRA_SENSOR, sensor)
+                                                    .putExtra(Intent.EXTRA_USER_ID, userId),
                                             PendingIntent.FLAG_IMMUTABLE
                                                     | PendingIntent.FLAG_UPDATE_CURRENT))
                                     .build())
@@ -229,18 +260,27 @@
          */
         @Override
         public void setSensorPrivacy(boolean enable) {
+            // Keep the state consistent between all users to make it a single global state
+            forAllUsers(userId -> setSensorPrivacy(userId, enable));
+        }
+
+        private void setSensorPrivacy(@UserIdInt int userId, boolean enable) {
             enforceSensorPrivacyPermission();
             synchronized (mLock) {
-                mEnabled = enable;
+                mEnabled.put(userId, enable);
                 persistSensorPrivacyStateLocked();
             }
             mHandler.onSensorPrivacyChanged(enable);
         }
 
-        public void setIndividualSensorPrivacy(int sensor, boolean enable) {
+        @Override
+        public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) {
             enforceSensorPrivacyPermission();
             synchronized (mLock) {
-                mIndividualEnabled.put(sensor, enable);
+                SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId,
+                        new SparseBooleanArray());
+                userIndividualEnabled.put(sensor, enable);
+                mIndividualEnabled.put(userId, userIndividualEnabled);
 
                 if (!enable) {
                     // Remove any notifications prompting the user to disable sensory privacy
@@ -249,9 +289,20 @@
 
                     notificationManager.cancel(sensor);
                 }
-
                 persistSensorPrivacyState();
             }
+            mHandler.onSensorPrivacyChanged(userId, sensor, enable);
+        }
+
+        @Override
+        public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor,
+                boolean enable) {
+            int parentId = mUserManagerInternal.getProfileParentId(userId);
+            forAllUsers(userId2 -> {
+                if (parentId == mUserManagerInternal.getProfileParentId(userId2)) {
+                    setIndividualSensorPrivacy(userId2, sensor, enable);
+                }
+            });
         }
 
         /**
@@ -273,53 +324,179 @@
          */
         @Override
         public boolean isSensorPrivacyEnabled() {
+            return isSensorPrivacyEnabled(USER_SYSTEM);
+        }
+
+        private boolean isSensorPrivacyEnabled(@UserIdInt int userId) {
             synchronized (mLock) {
-                return mEnabled;
+                return mEnabled.get(userId, false);
             }
         }
 
         @Override
-        public boolean isIndividualSensorPrivacyEnabled(int sensor) {
+        public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
             synchronized (mLock) {
-                return mIndividualEnabled.get(sensor, false);
+                SparseBooleanArray states = mIndividualEnabled.get(userId);
+                if (states == null) {
+                    return false;
+                }
+                return states.get(sensor, false);
             }
         }
 
         /**
          * Returns the state of sensor privacy from persistent storage.
          */
-        private void readPersistedSensorPrivacyStateLocked() {
+        private boolean readPersistedSensorPrivacyStateLocked() {
             // if the file does not exist then sensor privacy has not yet been enabled on
             // the device.
-            if (!mAtomicFile.exists()) {
-                return;
-            }
-            try (FileInputStream inputStream = mAtomicFile.openRead()) {
-                TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
-                XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
-                parser.next();
-                mEnabled = parser.getAttributeBoolean(null, XML_ATTRIBUTE_ENABLED, false);
 
-                XmlUtils.nextElement(parser);
-                while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
-                    String tagName = parser.getName();
-                    if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) {
-                        int sensor = XmlUtils.readIntAttribute(parser, XML_ATTRIBUTE_SENSOR);
-                        boolean enabled = XmlUtils.readBooleanAttribute(parser,
-                                XML_ATTRIBUTE_ENABLED);
-                        mIndividualEnabled.put(sensor, enabled);
-                        XmlUtils.skipCurrentTag(parser);
-                    } else {
+            SparseArray<Object> map = new SparseArray<>();
+            int version = -1;
+
+            if (mAtomicFile.exists()) {
+                try (FileInputStream inputStream = mAtomicFile.openRead()) {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+                    XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+                    final int persistenceVersion = parser.getAttributeInt(null,
+                            XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
+
+                    // Use inline string literals for xml tags/attrs when parsing old versions since
+                    // these should never be changed even with refactorings.
+                    if (persistenceVersion == 0) {
+                        boolean enabled = parser.getAttributeBoolean(null, "enabled", false);
+                        SparseBooleanArray individualEnabled = new SparseBooleanArray();
+                        version = 0;
+
                         XmlUtils.nextElement(parser);
+                        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                            String tagName = parser.getName();
+                            if ("individual-sensor-privacy".equals(tagName)) {
+                                int sensor = XmlUtils.readIntAttribute(parser, "sensor");
+                                boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
+                                        "enabled");
+                                individualEnabled.put(sensor, indEnabled);
+                                XmlUtils.skipCurrentTag(parser);
+                            } else {
+                                XmlUtils.nextElement(parser);
+                            }
+                        }
+                        map.put(VER0_ENABLED, enabled);
+                        map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled);
+                    } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
+                        SparseBooleanArray enabled = new SparseBooleanArray();
+                        SparseArray<SparseBooleanArray> individualEnabled = new SparseArray<>();
+                        version = parser.getAttributeInt(null,
+                                XML_ATTRIBUTE_VERSION, 1);
+
+                        int currentUserId = -1;
+                        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                            XmlUtils.nextElement(parser);
+                            String tagName = parser.getName();
+                            if (XML_TAG_USER.equals(tagName)) {
+                                currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID);
+                                boolean isEnabled = parser.getAttributeBoolean(null,
+                                        XML_ATTRIBUTE_ENABLED);
+                                if (enabled.indexOfKey(currentUserId) >= 0) {
+                                    Log.e(TAG, "User listed multiple times in file.",
+                                            new RuntimeException());
+                                    mAtomicFile.delete();
+                                    version = -1;
+                                    break;
+                                }
+
+                                if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
+                                    // User may no longer exist, skip this user
+                                    currentUserId = -1;
+                                    continue;
+                                }
+
+                                enabled.put(currentUserId, isEnabled);
+                            }
+                            if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) {
+                                if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
+                                    // User may no longer exist or isn't set
+                                    continue;
+                                }
+                                int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR);
+                                boolean isEnabled = parser.getAttributeBoolean(null,
+                                        XML_ATTRIBUTE_ENABLED);
+                                SparseBooleanArray userIndividualEnabled = individualEnabled.get(
+                                        currentUserId, new SparseBooleanArray());
+
+                                userIndividualEnabled.put(sensor, isEnabled);
+                                individualEnabled.put(currentUserId, userIndividualEnabled);
+                            }
+                        }
+
+                        map.put(VER1_ENABLED, enabled);
+                        map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled);
+                    } else {
+                        Log.e(TAG, "Unknown persistence version: " + persistenceVersion
+                                        + ". Deleting.",
+                                new RuntimeException());
+                        mAtomicFile.delete();
+                        version = -1;
+                    }
+
+                } catch (IOException | XmlPullParserException e) {
+                    Log.e(TAG, "Caught an exception reading the state from storage: ", e);
+                    // Delete the file to prevent the same error on subsequent calls and assume
+                    // sensor privacy is not enabled.
+                    mAtomicFile.delete();
+                    version = -1;
+                }
+            }
+
+            return upgradeAndInit(version, map);
+        }
+
+        private boolean upgradeAndInit(int version, SparseArray map) {
+            if (version == -1) {
+                // New file, default state for current version goes here.
+                mEnabled = new SparseBooleanArray();
+                mIndividualEnabled = new SparseArray<>();
+                forAllUsers(userId -> mEnabled.put(userId, false));
+                forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseBooleanArray()));
+                return true;
+            }
+            boolean upgraded = false;
+            final int[] users = getLocalService(UserManagerInternal.class).getUserIds();
+            if (version == 0) {
+                final boolean enabled = (boolean) map.get(VER0_ENABLED);
+                final SparseBooleanArray individualEnabled =
+                        (SparseBooleanArray) map.get(VER0_INDIVIDUAL_ENABLED);
+
+                final SparseBooleanArray perUserEnabled = new SparseBooleanArray();
+                final SparseArray<SparseBooleanArray> perUserIndividualEnabled =
+                        new SparseArray<>();
+
+                // Copy global state to each user
+                for (int i = 0; i < users.length; i++) {
+                    int user = users[i];
+                    perUserEnabled.put(user, enabled);
+                    SparseBooleanArray userIndividualSensorEnabled = new SparseBooleanArray();
+                    perUserIndividualEnabled.put(user, userIndividualSensorEnabled);
+                    for (int j = 0; j < individualEnabled.size(); j++) {
+                        final int sensor = individualEnabled.keyAt(j);
+                        final boolean isSensorEnabled = individualEnabled.valueAt(j);
+                        userIndividualSensorEnabled.put(sensor, isSensorEnabled);
                     }
                 }
 
-            } catch (IOException | XmlPullParserException e) {
-                Log.e(TAG, "Caught an exception reading the state from storage: ", e);
-                // Delete the file to prevent the same error on subsequent calls and assume sensor
-                // privacy is not enabled.
-                mAtomicFile.delete();
+                map.clear();
+                map.put(VER1_ENABLED, perUserEnabled);
+                map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled);
+
+                version = 1;
+                upgraded = true;
             }
+            if (version == CURRENT_VERSION) {
+                mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED);
+                mIndividualEnabled =
+                        (SparseArray<SparseBooleanArray>) map.get(VER1_INDIVIDUAL_ENABLED);
+            }
+            return upgraded;
         }
 
         /**
@@ -338,16 +515,29 @@
                 TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
                 serializer.startDocument(null, true);
                 serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
-                serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, mEnabled);
-                int numIndividual = mIndividualEnabled.size();
-                for (int i = 0; i < numIndividual; i++) {
-                    serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
-                    int sensor = mIndividualEnabled.keyAt(i);
-                    boolean enabled = mIndividualEnabled.valueAt(i);
-                    serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
-                    serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
-                    serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
-                }
+                serializer.attributeInt(
+                        null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
+                serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
+                forAllUsers(userId -> {
+                    serializer.startTag(null, XML_TAG_USER);
+                    serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId);
+                    serializer.attributeBoolean(
+                            null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId));
+
+                    SparseBooleanArray individualEnabled =
+                            mIndividualEnabled.get(userId, new SparseBooleanArray());
+                    int numIndividual = individualEnabled.size();
+                    for (int i = 0; i < numIndividual; i++) {
+                        serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
+                        int sensor = individualEnabled.keyAt(i);
+                        boolean enabled = individualEnabled.valueAt(i);
+                        serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
+                        serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
+                        serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
+                    }
+                    serializer.endTag(null, XML_TAG_USER);
+
+                });
                 serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
                 serializer.endDocument();
                 mAtomicFile.finishWrite(outputStream);
@@ -369,6 +559,18 @@
         }
 
         /**
+         * Registers a listener to be notified when the sensor privacy state changes.
+         */
+        @Override
+        public void addIndividualSensorPrivacyListener(int userId, int sensor,
+                ISensorPrivacyListener listener) {
+            if (listener == null) {
+                throw new NullPointerException("listener cannot be null");
+            }
+            mHandler.addListener(userId, sensor, listener);
+        }
+
+        /**
          * Unregisters a listener from sensor privacy state change notifications.
          */
         @Override
@@ -422,22 +624,32 @@
          */
         private void dump(@NonNull DualDumpOutputStream dumpStream) {
             synchronized (mLock) {
-                dumpStream.write("is_enabled", SensorPrivacyServiceDumpProto.IS_ENABLED, mEnabled);
 
-                int numIndividualEnabled = mIndividualEnabled.size();
-                for (int i = 0; i < numIndividualEnabled; i++) {
-                    long token = dumpStream.start("individual_enabled_sensor",
-                            SensorPrivacyServiceDumpProto.INDIVIDUAL_ENABLED_SENSOR);
+                forAllUsers(userId -> {
+                    long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
+                    dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
+                    dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED,
+                            mEnabled.get(userId, false));
 
-                    dumpStream.write("sensor",
-                            SensorPrivacyIndividualEnabledSensorProto.SENSOR,
-                            mIndividualEnabled.keyAt(i));
-                    dumpStream.write("is_enabled",
-                            SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
-                            mIndividualEnabled.valueAt(i));
+                    SparseBooleanArray individualEnabled = mIndividualEnabled.get(userId);
+                    if (individualEnabled != null) {
+                        int numIndividualEnabled = individualEnabled.size();
+                        for (int i = 0; i < numIndividualEnabled; i++) {
+                            long individualToken = dumpStream.start("individual_enabled_sensor",
+                                    SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR);
 
-                    dumpStream.end(token);
-                }
+                            dumpStream.write("sensor",
+                                    SensorPrivacyIndividualEnabledSensorProto.SENSOR,
+                                    individualEnabled.keyAt(i));
+                            dumpStream.write("is_enabled",
+                                    SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
+                                    individualEnabled.valueAt(i));
+
+                            dumpStream.end(individualToken);
+                        }
+                    }
+                    dumpStream.end(userToken);
+                });
             }
 
             dumpStream.flush();
@@ -477,30 +689,32 @@
                         return handleDefaultCommands(cmd);
                     }
 
+                    int userId = Integer.parseInt(getNextArgRequired());
+
                     final PrintWriter pw = getOutPrintWriter();
                     switch (cmd) {
                         case "enable" : {
-                            int sensor = sensorStrToId(getNextArg());
+                            int sensor = sensorStrToId(getNextArgRequired());
                             if (sensor == UNKNOWN) {
                                 pw.println("Invalid sensor");
                                 return -1;
                             }
 
-                            setIndividualSensorPrivacy(sensor, true);
+                            setIndividualSensorPrivacy(userId, sensor, true);
                         }
                         break;
                         case "disable" : {
-                            int sensor = sensorStrToId(getNextArg());
+                            int sensor = sensorStrToId(getNextArgRequired());
                             if (sensor == UNKNOWN) {
                                 pw.println("Invalid sensor");
                                 return -1;
                             }
 
-                            setIndividualSensorPrivacy(sensor, false);
+                            setIndividualSensorPrivacy(userId, sensor, false);
                         }
                         break;
                         case "reset": {
-                            int sensor = sensorStrToId(getNextArg());
+                            int sensor = sensorStrToId(getNextArgRequired());
                             if (sensor == UNKNOWN) {
                                 pw.println("Invalid sensor");
                                 return -1;
@@ -509,7 +723,11 @@
                             enforceSensorPrivacyPermission();
 
                             synchronized (mLock) {
-                                mIndividualEnabled.delete(sensor);
+                                SparseBooleanArray individualEnabled =
+                                        mIndividualEnabled.get(userId);
+                                if (individualEnabled != null) {
+                                    individualEnabled.delete(sensor);
+                                }
                                 persistSensorPrivacyState();
                             }
                         }
@@ -530,13 +748,13 @@
                     pw.println("  help");
                     pw.println("    Print this help text.");
                     pw.println("");
-                    pw.println("  enable SENSOR");
+                    pw.println("  enable USER_ID SENSOR");
                     pw.println("    Enable privacy for a certain sensor.");
                     pw.println("");
-                    pw.println("  disable SENSOR");
+                    pw.println("  disable USER_ID SENSOR");
                     pw.println("    Disable privacy for a certain sensor.");
                     pw.println("");
-                    pw.println("  reset SENSOR");
+                    pw.println("  reset USER_ID SENSOR");
                     pw.println("    Reset privacy state for a certain sensor.");
                     pw.println("");
                 }
@@ -555,6 +773,9 @@
         @GuardedBy("mListenerLock")
         private final RemoteCallbackList<ISensorPrivacyListener> mListeners =
                 new RemoteCallbackList<>();
+        @GuardedBy("mListenerLock")
+        private final SparseArray<SparseArray<RemoteCallbackList<ISensorPrivacyListener>>>
+                mIndividualSensorListeners = new SparseArray<>();
         private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients;
         private final Context mContext;
 
@@ -572,6 +793,14 @@
                             mSensorPrivacyServiceImpl));
         }
 
+        public void onSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
+            sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
+                    this, userId, sensor, enabled));
+            sendMessage(
+                    PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
+                            mSensorPrivacyServiceImpl));
+        }
+
         public void addListener(ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
                 DeathRecipient deathRecipient = new DeathRecipient(listener);
@@ -580,6 +809,25 @@
             }
         }
 
+        public void addListener(int userId, int sensor, ISensorPrivacyListener listener) {
+            synchronized (mListenerLock) {
+                DeathRecipient deathRecipient = new DeathRecipient(listener);
+                mDeathRecipients.put(listener, deathRecipient);
+                SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
+                        mIndividualSensorListeners.get(userId);
+                if (listenersForUser == null) {
+                    listenersForUser = new SparseArray<>();
+                    mIndividualSensorListeners.put(userId, listenersForUser);
+                }
+                RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor);
+                if (listeners == null) {
+                    listeners = new RemoteCallbackList<>();
+                    listenersForUser.put(sensor, listeners);
+                }
+                listeners.register(listener);
+            }
+        }
+
         public void removeListener(ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
                 DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
@@ -587,6 +835,12 @@
                     deathRecipient.destroy();
                 }
                 mListeners.unregister(listener);
+                for (int i = 0, numUsers = mIndividualSensorListeners.size(); i < numUsers; i++) {
+                    for (int j = 0, numListeners = mIndividualSensorListeners.valueAt(i).size();
+                            j < numListeners; j++) {
+                        mIndividualSensorListeners.valueAt(i).valueAt(j).unregister(listener);
+                    }
+                }
             }
         }
 
@@ -602,6 +856,28 @@
             }
             mListeners.finishBroadcast();
         }
+
+        public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
+            SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
+                    mIndividualSensorListeners.get(userId);
+            if (listenersForUser == null) {
+                return;
+            }
+            RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor);
+            if (listeners == null) {
+                return;
+            }
+            final int count = listeners.beginBroadcast();
+            for (int i = 0; i < count; i++) {
+                ISensorPrivacyListener listener = listeners.getBroadcastItem(i);
+                try {
+                    listener.onSensorPrivacyChanged(enabled);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+                }
+            }
+            listeners.finishBroadcast();
+        }
     }
 
     private final class DeathRecipient implements IBinder.DeathRecipient {
@@ -628,4 +904,11 @@
             }
         }
     }
+
+    private void forAllUsers(FunctionalUtils.ThrowingConsumer<Integer> c) {
+        int[] userIds = mUserManagerInternal.getUserIds();
+        for (int i = 0; i < userIds.length; i++) {
+            c.accept(userIds[i]);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c951fd4..b0f2e24 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1716,7 +1716,7 @@
     public StorageManagerService(Context context) {
         sSelf = this;
         mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
-                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mContext = context;
         mResolver = mContext.getContentResolver();
         mCallbacks = new Callbacks(FgThread.get().getLooper());
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 74e3851..c191a78 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -16,13 +16,15 @@
 
 package com.android.server;
 
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.NetworkProvider;
-import android.net.NetworkRequest;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.VcnConfig;
 import android.os.Binder;
@@ -43,6 +45,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker;
+import com.android.server.vcn.Vcn;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import java.io.IOException;
@@ -51,6 +57,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
 
 /**
  * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
@@ -115,6 +122,10 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";
 
+    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
+
     /* Binder context for this service */
     @NonNull private final Context mContext;
     @NonNull private final Dependencies mDeps;
@@ -122,11 +133,23 @@
     @NonNull private final Looper mLooper;
     @NonNull private final Handler mHandler;
     @NonNull private final VcnNetworkProvider mNetworkProvider;
+    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
+    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
+    @NonNull private final VcnContext mVcnContext;
 
     @GuardedBy("mLock")
     @NonNull
     private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @NonNull
+    private TelephonySubscriptionSnapshot mLastSnapshot =
+            TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
+
     @NonNull private final Object mLock = new Object();
 
     @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
@@ -139,8 +162,12 @@
         mLooper = mDeps.getLooper();
         mHandler = new Handler(mLooper);
         mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
+        mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
+        mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
+                mContext, mLooper, mTelephonySubscriptionTrackerCb);
 
         mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
+        mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
 
         // Run on handler to ensure I/O does not block system server startup
         mHandler.post(() -> {
@@ -174,7 +201,10 @@
                             mConfigs.put(entry.getKey(), entry.getValue());
                         }
                     }
-                    // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed.
+
+                    // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
+                    // snapshot, and therefore safe even before telephony subscriptions are loaded.
+                    mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
                 }
             }
         });
@@ -203,6 +233,14 @@
             return mHandlerThread.getLooper();
         }
 
+        /** Creates a new VcnInstance using the provided configuration */
+        public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull TelephonySubscriptionTrackerCallback callback) {
+            return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
+        }
+
         /**
          * Retrieves the caller's UID
          *
@@ -225,12 +263,29 @@
                 newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
             return new PersistableBundleUtils.LockingReadWriteHelper(path);
         }
+
+        /** Creates a new VcnContext */
+        public VcnContext newVcnContext(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull VcnNetworkProvider vcnNetworkProvider) {
+            return new VcnContext(context, looper, vcnNetworkProvider);
+        }
+
+        /** Creates a new Vcn instance using the provided configuration */
+        public Vcn newVcn(
+                @NonNull VcnContext vcnContext,
+                @NonNull ParcelUuid subscriptionGroup,
+                @NonNull VcnConfig config) {
+            return new Vcn(vcnContext, subscriptionGroup, config);
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
     public void systemReady() {
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkProvider(mNetworkProvider);
+        mTelephonySubscriptionTracker.register();
     }
 
     private void enforcePrimaryUser() {
@@ -277,27 +332,112 @@
                 "Carrier privilege required for subscription group to set VCN Config");
     }
 
+    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
+        /**
+         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
+         *
+         * <p>Start any unstarted VCN instances
+         *
+         * @hide
+         */
+        public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+            // Startup VCN instances
+            synchronized (mLock) {
+                mLastSnapshot = snapshot;
+
+                // Start any VCN instances as necessary
+                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                    if (snapshot.packageHasPermissionsForSubscriptionGroup(
+                            entry.getKey(), entry.getValue().getProvisioningPackageName())) {
+                        if (!mVcns.containsKey(entry.getKey())) {
+                            startVcnLocked(entry.getKey(), entry.getValue());
+                        }
+
+                        // Cancel any scheduled teardowns for active subscriptions
+                        mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
+                    }
+                }
+
+                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
+                // delay)
+                for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
+                    final VcnConfig config = mConfigs.get(entry.getKey());
+                    if (config == null
+                            || !snapshot.packageHasPermissionsForSubscriptionGroup(
+                                    entry.getKey(), config.getProvisioningPackageName())) {
+                        final ParcelUuid uuidToTeardown = entry.getKey();
+                        final Vcn instanceToTeardown = entry.getValue();
+
+                        mHandler.postDelayed(() -> {
+                            synchronized (mLock) {
+                                // Guard against case where this is run after a old instance was
+                                // torn down, and a new instance was started. Verify to ensure
+                                // correct instance is torn down. This could happen as a result of a
+                                // Carrier App manually removing/adding a VcnConfig.
+                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
+                                    mVcns.remove(uuidToTeardown).teardownAsynchronously();
+                                }
+                            }
+                        }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+                    }
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
+        Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);
+
+        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
+        //                    VCN.
+
+        final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
+        mVcns.put(subscriptionGroup, newInstance);
+    }
+
+    @GuardedBy("mLock")
+    private void startOrUpdateVcnLocked(
+            @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
+        Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup);
+
+        if (mVcns.containsKey(subscriptionGroup)) {
+            mVcns.get(subscriptionGroup).updateConfig(config);
+        } else {
+            startVcnLocked(subscriptionGroup, config);
+        }
+    }
+
     /**
      * Sets a VCN config for a given subscription group.
      *
      * <p>Implements the IVcnManagementService Binder interface.
      */
     @Override
-    public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
+    public void setVcnConfig(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config,
+            @NonNull String opPkgName) {
         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
         requireNonNull(config, "config was null");
+        requireNonNull(opPkgName, "opPkgName was null");
+        if (!config.getProvisioningPackageName().equals(opPkgName)) {
+            throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
+        }
+        Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);
 
+        mContext.getSystemService(AppOpsManager.class)
+                .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
         enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
 
-        synchronized (mLock) {
-            mConfigs.put(subscriptionGroup, config);
+        Binder.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                mConfigs.put(subscriptionGroup, config);
+                startOrUpdateVcnLocked(subscriptionGroup, config);
 
-            // Must be done synchronously to ensure that writes do not happen out-of-order.
-            writeConfigsToDiskLocked();
-        }
-
-        // TODO: Clear Binder calling identity
-        // TODO: Trigger startup as necessary
+                writeConfigsToDiskLocked();
+            }
+        });
     }
 
     /**
@@ -308,18 +448,21 @@
     @Override
     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+        Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
 
         enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
 
-        synchronized (mLock) {
-            mConfigs.remove(subscriptionGroup);
+        Binder.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                mConfigs.remove(subscriptionGroup);
 
-            // Must be done synchronously to ensure that writes do not happen out-of-order.
-            writeConfigsToDiskLocked();
-        }
+                if (mVcns.containsKey(subscriptionGroup)) {
+                    mVcns.remove(subscriptionGroup).teardownAsynchronously();
+                }
 
-        // TODO: Clear Binder calling identity
-        // TODO: Trigger teardown as necessary
+                writeConfigsToDiskLocked();
+            }
+        });
     }
 
     @GuardedBy("mLock")
@@ -345,19 +488,11 @@
         }
     }
 
-    /**
-     * Network provider for VCN networks.
-     *
-     * @hide
-     */
-    public class VcnNetworkProvider extends NetworkProvider {
-        VcnNetworkProvider(Context context, Looper looper) {
-            super(context, looper, VcnNetworkProvider.class.getSimpleName());
-        }
-
-        @Override
-        public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
-            // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels.
+    /** Get current configuration list for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<ParcelUuid, Vcn> getAllVcns() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mVcns);
         }
     }
 }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 15e31ba..1170983 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4756,7 +4756,7 @@
 
         IAccountManagerResponse getResponseAndClose() {
             if (mResponse == null) {
-                // this session has already been closed
+                close();
                 return null;
             }
             IAccountManagerResponse response = mResponse;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a094eac..29c95bc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -174,7 +174,6 @@
 import android.app.WaitResult;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManager;
-import android.app.compat.CompatChanges;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManager;
@@ -13721,34 +13720,10 @@
                             false, false, userId, "package unstartable");
                     break;
                 case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
-                    if (!canCloseSystemDialogs(callingPid, callingUid, callerApp)) {
-                        // The app can't close system dialogs, throw only if it targets S+
-                        if (CompatChanges.isChangeEnabled(
-                                ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, callingUid)) {
-                            throw new SecurityException(
-                                    "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
-                                            + " broadcast from " + callerPackage + " (pid="
-                                            + callingPid + ", uid=" + callingUid + ")"
-                                            + " requires "
-                                            + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS + ".");
-                        } else if (CompatChanges.isChangeEnabled(
-                                ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, callingUid)) {
-                            Slog.w(TAG, "Permission Denial: " + intent.getAction()
-                                    + " broadcast from " + callerPackage + " (pid=" + callingPid
-                                    + ", uid=" + callingUid + ")"
-                                    + " requires "
-                                    + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
-                                    + ", dropping broadcast.");
-                            // Returning success seems to be the pattern here
-                            return ActivityManager.BROADCAST_SUCCESS;
-                        } else {
-                            Slog.w(TAG, intent.getAction()
-                                    + " broadcast from " + callerPackage + " (pid=" + callingPid
-                                    + ", uid=" + callingUid + ")"
-                                    + " will require  "
-                                    + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
-                                    + " in future builds.");
-                        }
+                    if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+                            callerPackage)) {
+                        // Returning success seems to be the pattern here
+                        return ActivityManager.BROADCAST_SUCCESS;
                     }
                     break;
             }
@@ -14043,39 +14018,6 @@
         return ActivityManager.BROADCAST_SUCCESS;
     }
 
-    private boolean canCloseSystemDialogs(int pid, int uid, @Nullable ProcessRecord callerApp) {
-        if (checkPermission(permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, pid, uid)
-                == PERMISSION_GRANTED) {
-            return true;
-        }
-        if (callerApp == null) {
-            synchronized (mPidsSelfLocked) {
-                callerApp = mPidsSelfLocked.get(pid);
-            }
-        }
-
-        if (callerApp != null) {
-            // Check if the instrumentation of the process has the permission. This covers the usual
-            // test started from the shell (which has the permission) case. This is needed for apps
-            // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
-            // avoid breaking a bunch of existing tests and asking them to adopt shell permissions
-            // to do this.
-            ActiveInstrumentation instrumentation = callerApp.getActiveInstrumentation();
-            if (instrumentation != null && checkPermission(
-                    permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, instrumentation.mSourceUid)
-                    == PERMISSION_GRANTED) {
-                return true;
-            }
-            // This is the notification trampoline use-case for example, where apps use Intent.ACSD
-            // to close the shade prior to starting an activity.
-            WindowProcessController wmApp = callerApp.getWindowProcessController();
-            if (wmApp.canCloseSystemDialogsByToken()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
      */
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6f6cad0..53d75d1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -711,7 +711,7 @@
         mAppDataIsolationEnabled =
                 SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
-                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mAppDataIsolationWhitelistedApps = new ArrayList<>(
                 SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e90423c..669d04a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1431,7 +1431,9 @@
     void setActiveInstrumentation(ActiveInstrumentation instr) {
         mInstr = instr;
         boolean isInstrumenting = instr != null;
-        mWindowProcessController.setInstrumenting(isInstrumenting,
+        mWindowProcessController.setInstrumenting(
+                isInstrumenting,
+                isInstrumenting ? instr.mSourceUid : -1,
                 isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
     }
 
diff --git a/services/core/java/com/android/server/apphibernation/OWNERS b/services/core/java/com/android/server/apphibernation/OWNERS
new file mode 100644
index 0000000..4804fa3
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/OWNERS
@@ -0,0 +1,3 @@
+# TODO: Include /core/java/android/apphibernation/OWNERS. See b/177005153
+kevhan@google.com
+rajekumar@google.com
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 0943c9ca..5663495 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
index d588b8d..49cddaa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
@@ -21,8 +21,10 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.AsyncTask;
 import android.os.Environment;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -35,6 +37,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -46,17 +49,16 @@
 public abstract class BiometricUserState<T extends BiometricAuthenticator.Identifier> {
     private static final String TAG = "UserState";
 
+    private static final String TAG_INVALIDATION = "authenticatorIdInvalidation_tag";
+    private static final String ATTR_INVALIDATION = "authenticatorIdInvalidation_attr";
+
     @GuardedBy("this")
     protected final ArrayList<T> mBiometrics = new ArrayList<>();
+    protected boolean mInvalidationInProgress;
     protected final Context mContext;
     protected final File mFile;
 
-    private final Runnable mWriteStateRunnable = new Runnable() {
-        @Override
-        public void run() {
-            doWriteState();
-        }
-    };
+    private final Runnable mWriteStateRunnable = this::doWriteStateInternal;
 
     /**
      * @return The tag for the biometrics. There may be multiple instances of a biometric within.
@@ -73,10 +75,40 @@
      */
     protected abstract ArrayList<T> getCopy(ArrayList<T> array);
 
+    protected abstract void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception;
+
     /**
-     * @return Writes the cached data to persistent storage.
+     * @Writes the cached data to persistent storage.
      */
-    protected abstract void doWriteState();
+    private void doWriteStateInternal() {
+        AtomicFile destination = new AtomicFile(mFile);
+
+        FileOutputStream out = null;
+
+        try {
+            out = destination.startWrite();
+            TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+
+            // Store the authenticatorId
+            serializer.startTag(null, TAG_INVALIDATION);
+            serializer.attributeBoolean(null, ATTR_INVALIDATION, mInvalidationInProgress);
+            serializer.endTag(null, TAG_INVALIDATION);
+
+            // Do any additional serialization that subclasses may require
+            doWriteState(serializer);
+
+            serializer.endDocument();
+            destination.finishWrite(out);
+        } catch (Throwable t) {
+            Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
+            destination.failWrite(out);
+            throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t);
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
 
     /**
      * @return
@@ -93,6 +125,19 @@
         }
     }
 
+    public void setInvalidationInProgress(boolean invalidationInProgress) {
+        synchronized (this) {
+            mInvalidationInProgress = invalidationInProgress;
+            scheduleWriteStateLocked();
+        }
+    }
+
+    public boolean isInvalidationInProgress() {
+        synchronized (this) {
+            return mInvalidationInProgress;
+        }
+    }
+
     public void addBiometric(T identifier) {
         synchronized (this) {
             mBiometrics.add(identifier);
@@ -202,6 +247,8 @@
             String tagName = parser.getName();
             if (tagName.equals(getBiometricsTag())) {
                 parseBiometricsLocked(parser);
+            } else if (tagName.equals(TAG_INVALIDATION)) {
+                mInvalidationInProgress = parser.getAttributeBoolean(null, ATTR_INVALIDATION);
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
index d52b340..ebe4679 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
@@ -31,4 +31,6 @@
     void removeBiometricForUser(Context context, int userId, int biometricId);
     void renameBiometricForUser(Context context, int userId, int biometricId, CharSequence name);
     CharSequence getUniqueName(Context context, int userId);
+    void setInvalidationInProgress(Context context, int userId, boolean inProgress);
+    boolean isInvalidationInProgress(Context context, int userId);
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
new file mode 100644
index 0000000..b8084d5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricsProtoEnums;
+
+/**
+ * ClientMonitor subclass for requesting authenticatorId invalidation. See
+ * {@link InvalidationRequesterClient} for more info.
+ */
+public abstract class InvalidationClient<S extends BiometricAuthenticator.Identifier, T>
+        extends ClientMonitor<T> {
+
+    private final BiometricUtils<S> mUtils;
+
+    public InvalidationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+            int userId, int sensorId, @NonNull BiometricUtils<S> utils) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId,
+                context.getOpPackageName(), 0 /* cookie */, sensorId,
+                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mUtils = utils;
+    }
+
+    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+        // TODO: Update framework w/ newAuthenticatorId
+        mCallback.onClientFinished(this, true /* success */);
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+
+        startHalOperation();
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
new file mode 100644
index 0000000..ca34eee
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -0,0 +1,86 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricsProtoEnums;
+
+/**
+ * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
+ * sensors. See {@link InvalidationClient} for the ClientMonitor subclass responsible for initiating
+ * the invalidation with individual HALs. AuthenticatorId invalidation is required on devices with
+ * multiple strong biometric sensors.
+ *
+ * The public Keystore and Biometric APIs are biometric-tied, not modality-tied, meaning that keys
+ * are unlockable by "any/all strong biometrics on the device", and not "only a specific strong
+ * sensor". The Keystore API allows for creation of biometric-tied keys that are invalidated upon
+ * new biometric enrollment. See
+ * {@link android.security.keystore.KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment}
+ *
+ * This has been supported on single-sensor devices by the various getAuthenticatorId APIs on the
+ * HIDL and AIDL biometric HAL interfaces, where:
+ * 1) authenticatorId is requested and stored during key generation
+ * 2) authenticatorId is contained within the HAT when biometric authentication succeeds
+ * 3) authenticatorId is automatically changed (below the framework) whenever a new biometric
+ *    enrollment occurs.
+ *
+ * For multi-biometric devices, this will be done the following way:
+ * 1) New enrollment added for Sensor1. Sensor1's HAL/TEE updates its authenticatorId automatically
+ *    when enrollment completes
+ * 2) Framework marks Sensor1 as "invalidationInProgress". See
+ *    {@link BiometricUtils#setInvalidationInProgress(Context, int, boolean)}
+ * 3) After all other sensors have finished invalidation, the framework will clear the invalidation
+ *    flag for Sensor1.
+ * 4) New keys that are generated will include all new authenticatorIds
+ *
+ * The above is robust to incomplete invalidation. For example, when system boots or after user
+ * switches, the framework can check if any sensor has the "invalidationInProgress" flag set. If so,
+ * the framework should re-start the invalidation process described above.
+ */
+public abstract class InvalidationRequesterClient<T> extends ClientMonitor<T> {
+
+    private final BiometricManager mBiometricManager;
+
+    public InvalidationRequesterClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+            int userId, int sensorId) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId,
+                context.getOpPackageName(), 0 /* cookie */, sensorId,
+                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mBiometricManager = context.getSystemService(BiometricManager.class);
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+
+        // TODO(b/159667191): Request BiometricManager/BiometricService to invalidate
+        //  authenticatorIds. Be sure to invoke BiometricUtils#setInvalidationInProgress(true)
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+
+    @Override
+    protected void startHalOperation() {
+        // No HAL operations necessary
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index d85ab25..3ca0691 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -38,6 +38,7 @@
     private final int mStatsAction;
     private final int mStatsClient;
     private long mFirstAcquireTimeMs;
+    private boolean mShouldLogMetrics = true;
 
     /**
      * Only valid for AuthenticationClient.
@@ -58,6 +59,10 @@
         mStatsClient = statsClient;
     }
 
+    protected void setShouldLog(boolean shouldLog) {
+        mShouldLogMetrics = shouldLog;
+    }
+
     public int getStatsClient() {
         return mStatsClient;
     }
@@ -70,6 +75,9 @@
 
     protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
             int targetUserId) {
+        if (!mShouldLogMetrics) {
+            return;
+        }
 
         final boolean isFace = mStatsModality == BiometricsProtoEnums.MODALITY_FACE;
         final boolean isFingerprint = mStatsModality == BiometricsProtoEnums.MODALITY_FINGERPRINT;
@@ -110,6 +118,10 @@
 
     protected final void logOnError(Context context, int error, int vendorCode, int targetUserId) {
 
+        if (!mShouldLogMetrics) {
+            return;
+        }
+
         final long latency = mFirstAcquireTimeMs != 0
                 ? (System.currentTimeMillis() - mFirstAcquireTimeMs) : -1;
 
@@ -144,6 +156,10 @@
 
     protected final void logOnAuthenticated(Context context, boolean authenticated,
             boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) {
+        if (!mShouldLogMetrics) {
+            return;
+        }
+
         int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
         if (!authenticated) {
             authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
@@ -189,6 +205,10 @@
     }
 
     protected final void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) {
+        if (!mShouldLogMetrics) {
+            return;
+        }
+
         if (DEBUG) {
             Slog.v(TAG, "Enrolled! Modality: " + mStatsModality
                     + ", User: " + targetUserId
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
index a26662d..a9981d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
@@ -16,23 +16,18 @@
 
 package com.android.server.biometrics.sensors.face;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.face.Face;
-import android.util.AtomicFile;
-import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
-import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.biometrics.sensors.BiometricUserState;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -75,46 +70,26 @@
     }
 
     @Override
-    protected void doWriteState() {
-        AtomicFile destination = new AtomicFile(mFile);
-
-        ArrayList<Face> faces;
+    protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception {
+        final ArrayList<Face> faces;
 
         synchronized (this) {
             faces = getCopy(mBiometrics);
         }
 
-        FileOutputStream out = null;
-        try {
-            out = destination.startWrite();
+        serializer.startTag(null, TAG_FACES);
 
-            TypedXmlSerializer serializer = Xml.resolveSerializer(out);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-            serializer.startTag(null, TAG_FACES);
-
-            final int count = faces.size();
-            for (int i = 0; i < count; i++) {
-                Face f = faces.get(i);
-                serializer.startTag(null, TAG_FACE);
-                serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId());
-                serializer.attribute(null, ATTR_NAME, f.getName().toString());
-                serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId());
-                serializer.endTag(null, TAG_FACE);
-            }
-
-            serializer.endTag(null, TAG_FACES);
-            serializer.endDocument();
-            destination.finishWrite(out);
-
-            // Any error while writing is fatal.
-        } catch (Throwable t) {
-            Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
-            destination.failWrite(out);
-            throw new IllegalStateException("Failed to write faces", t);
-        } finally {
-            IoUtils.closeQuietly(out);
+        final int count = faces.size();
+        for (int i = 0; i < count; i++) {
+            Face f = faces.get(i);
+            serializer.startTag(null, TAG_FACE);
+            serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId());
+            serializer.attribute(null, ATTR_NAME, f.getName().toString());
+            serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId());
+            serializer.endTag(null, TAG_FACE);
         }
+
+        serializer.endTag(null, TAG_FACES);
     }
 
     @GuardedBy("this")
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
index a0cd4a5..c574478 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.face.Face;
 import android.text.TextUtils;
 import android.util.SparseArray;
@@ -115,6 +114,16 @@
         return getStateForUser(context, userId).getUniqueName();
     }
 
+    @Override
+    public void setInvalidationInProgress(Context context, int userId, boolean inProgress) {
+        getStateForUser(context, userId).setInvalidationInProgress(inProgress);
+    }
+
+    @Override
+    public boolean isInvalidationInProgress(Context context, int userId) {
+        return getStateForUser(context, userId).isInvalidationInProgress();
+    }
+
     private FaceUserState getStateForUser(Context ctx, int userId) {
         synchronized (this) {
             FaceUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
new file mode 100644
index 0000000..f512cef
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -0,0 +1,48 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+
+import android.annotation.NonNull;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.InvalidationClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+public class FaceInvalidationClient extends InvalidationClient<Face, ISession> {
+    private static final String TAG = "FaceInvalidationClient";
+
+    public FaceInvalidationClient(@NonNull Context context,
+            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+            @NonNull FaceUtils utils) {
+        super(context, lazyDaemon, userId, sensorId, utils);
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 61f9cc4..e6cdbd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -66,10 +66,10 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.BiometricServiceCallback;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
 import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
@@ -188,7 +188,8 @@
 
         @Override // Binder call
         public void enroll(final IBinder token, final byte[] hardwareAuthToken, final int userId,
-                final IFingerprintServiceReceiver receiver, final String opPackageName) {
+                final IFingerprintServiceReceiver receiver, final String opPackageName,
+                boolean shouldLogMetrics) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -198,7 +199,7 @@
             }
 
             provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
-                    receiver, opPackageName);
+                    receiver, opPackageName, shouldLogMetrics);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
index 671e08b..ae173f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
@@ -16,23 +16,18 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.fingerprint.Fingerprint;
-import android.util.AtomicFile;
-import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
-import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.biometrics.sensors.BiometricUserState;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -76,47 +71,27 @@
     }
 
     @Override
-    protected void doWriteState() {
-        AtomicFile destination = new AtomicFile(mFile);
-
-        ArrayList<Fingerprint> fingerprints;
+    protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception {
+        final ArrayList<Fingerprint> fingerprints;
 
         synchronized (this) {
             fingerprints = getCopy(mBiometrics);
         }
 
-        FileOutputStream out = null;
-        try {
-            out = destination.startWrite();
+        serializer.startTag(null, TAG_FINGERPRINTS);
 
-            TypedXmlSerializer serializer = Xml.resolveSerializer(out);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-            serializer.startTag(null, TAG_FINGERPRINTS);
-
-            final int count = fingerprints.size();
-            for (int i = 0; i < count; i++) {
-                Fingerprint fp = fingerprints.get(i);
-                serializer.startTag(null, TAG_FINGERPRINT);
-                serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId());
-                serializer.attribute(null, ATTR_NAME, fp.getName().toString());
-                serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId());
-                serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId());
-                serializer.endTag(null, TAG_FINGERPRINT);
-            }
-
-            serializer.endTag(null, TAG_FINGERPRINTS);
-            serializer.endDocument();
-            destination.finishWrite(out);
-
-            // Any error while writing is fatal.
-        } catch (Throwable t) {
-            Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
-            destination.failWrite(out);
-            throw new IllegalStateException("Failed to write fingerprints", t);
-        } finally {
-            IoUtils.closeQuietly(out);
+        final int count = fingerprints.size();
+        for (int i = 0; i < count; i++) {
+            Fingerprint fp = fingerprints.get(i);
+            serializer.startTag(null, TAG_FINGERPRINT);
+            serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId());
+            serializer.attribute(null, ATTR_NAME, fp.getName().toString());
+            serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId());
+            serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId());
+            serializer.endTag(null, TAG_FINGERPRINT);
         }
+
+        serializer.endTag(null, TAG_FINGERPRINTS);
     }
 
     @GuardedBy("this")
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
index b3d2419..dc6fd3a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
@@ -118,6 +118,16 @@
         return getStateForUser(context, userId).getUniqueName();
     }
 
+    @Override
+    public void setInvalidationInProgress(Context context, int userId, boolean inProgress) {
+        getStateForUser(context, userId).setInvalidationInProgress(inProgress);
+    }
+
+    @Override
+    public boolean isInvalidationInProgress(Context context, int userId) {
+        return getStateForUser(context, userId).isInvalidationInProgress();
+    }
+
     private FingerprintUserState getStateForUser(Context ctx, int userId) {
         synchronized (this) {
             FingerprintUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 6e36cd2..d94c984 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -76,7 +76,8 @@
             @NonNull String opPackageName, long challenge);
 
     void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName);
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            boolean shouldLogMetrics);
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 3f9aef2..e95447b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -121,7 +121,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName());
+                mContext.getOpPackageName(), true /* shouldLogMetrics */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 3e13c45..ab59abd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -51,12 +51,14 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser) {
+            @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser,
+            boolean shouldLogMetrics) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
         mUdfpsOverlayController = udfpsOvelayController;
         mMaxTemplatesPerUser = maxTemplatesPerUser;
+        setShouldLog(shouldLogMetrics);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
new file mode 100644
index 0000000..b6d8892
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -0,0 +1,47 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.InvalidationClient;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, ISession> {
+    private static final String TAG = "FingerprintInvalidationClient";
+
+    public FingerprintInvalidationClient(@NonNull Context context,
+            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+            @NonNull FingerprintUtils utils) {
+        super(context, lazyDaemon, userId, sensorId, utils);
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 99c662a..a03deba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -348,7 +347,7 @@
     @Override
     public void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken,
             int userId, @NonNull IFingerprintServiceReceiver receiver,
-            @NonNull String opPackageName) {
+            @NonNull String opPackageName, boolean shouldLogMetrics) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -370,7 +369,7 @@
                         mSensors.get(sensorId).getLazySession(), token,
                         new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                         opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                        mUdfpsOverlayController, maxTemplatesPerUser);
+                        mUdfpsOverlayController, maxTemplatesPerUser, shouldLogMetrics);
                 scheduleForSensor(sensorId, client, new ClientMonitor.Callback() {
                     @Override
                     public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 74549b9..95c4cee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -122,7 +122,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName());
+                mContext.getOpPackageName(), true/* shouldLogMetrics */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index b8d27aa..f5ce894 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
 import android.app.SynchronousUserSwitchObserver;
 import android.app.TaskStackListener;
 import android.app.UserSwitchObserver;
@@ -548,14 +547,16 @@
     @Override
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            boolean shouldLogMetrics) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
+                    ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
+                    shouldLogMetrics);
             mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
                 @Override
                 public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index af61a8b..d1f1cf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -52,11 +52,13 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+            @Nullable IUdfpsOverlayController udfpsOverlayController,
+            boolean shouldLogMetrics) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
         mUdfpsOverlayController = udfpsOverlayController;
+        setShouldLog(shouldLogMetrics);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index c4ff99b..18907a1 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -213,6 +213,19 @@
     }
 
     /**
+     * Find if this change will be enabled for the given package after installation.
+     *
+     * @param packageName The package name in question
+     * @return {@code true} if the change should be enabled for the package.
+     */
+    boolean willBeEnabled(String packageName) {
+        if (hasDeferredOverride(packageName)) {
+            return mDeferredOverrides.get(packageName);
+        }
+        return defaultValue();
+    }
+
+    /**
      * Returns the default value for the change id, assuming there are no overrides.
      *
      * @return {@code false} if it's a default disabled change, {@code true} otherwise.
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index f03a608..9376e8d 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -146,6 +146,25 @@
     }
 
     /**
+     * Find if a given change will be enabled for a given package name, prior to installation.
+     *
+     * @param changeId    The ID of the change in question
+     * @param packageName Package name to check for
+     * @return {@code true} if the change would be enabled for this package name. Also returns
+     * {@code true} if the change ID is not known, as unknown changes are enabled by default.
+     */
+    boolean willChangeBeEnabled(long changeId, String packageName) {
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                // we know nothing about this change: default behaviour is enabled.
+                return true;
+            }
+            return c.willBeEnabled(packageName);
+        }
+    }
+
+    /**
      * Overrides the enabled state for a given change and app. This method is intended to be used
      * *only* for debugging purposes, ultimately invoked either by an adb command, or from some
      * developer settings UI.
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 283dba7..1ea468c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -137,6 +137,9 @@
             @UserIdInt int userId) {
         checkCompatChangeReadAndLogPermission();
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
+        if (appInfo == null) {
+            return mCompatConfig.willChangeBeEnabled(changeId, packageName);
+        }
         return isChangeEnabled(changeId, appInfo);
     }
 
diff --git a/services/core/java/com/android/server/content/OWNERS b/services/core/java/com/android/server/content/OWNERS
new file mode 100644
index 0000000..b6a9fe86
--- /dev/null
+++ b/services/core/java/com/android/server/content/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/am/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/content/SyncManager.md b/services/core/java/com/android/server/content/SyncManager.md
index 8507abd..8564fea 100644
--- a/services/core/java/com/android/server/content/SyncManager.md
+++ b/services/core/java/com/android/server/content/SyncManager.md
@@ -29,8 +29,10 @@
 
 ### Two Levels of Exemption
 Specifically, there are two different levels of exemption, depending on the state of the caller:
-1. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET`
+1. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET`.
+  This is shown as `STANDBY-EXEMPTED` in `dumpsys content`.
 2. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP`, which is more powerful than 1.
+  This is shown as `STANDBY-EXEMPTED(TOP)` in `dumpsys content`.
 
 The exemption level is calculated in
 [ContentService.getSyncExemptionAndCleanUpExtrasForCaller()](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/content/ContentService.java?q=%22int%20getSyncExemptionAndCleanUpExtrasForCaller%22&ss=android%2Fplatform%2Fsuperproject),
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 9ab30b4..7ec4a5a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -26,6 +26,8 @@
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
+import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
@@ -64,7 +66,7 @@
 import android.location.LocationProvider;
 import android.location.LocationRequest;
 import android.location.LocationTime;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
 import android.os.Bundle;
@@ -213,11 +215,6 @@
     public static final String TAG = "LocationManagerService";
     public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String NETWORK_LOCATION_SERVICE_ACTION =
-            "com.android.location.service.v3.NetworkLocationProvider";
-    private static final String FUSED_LOCATION_SERVICE_ACTION =
-            "com.android.location.service.FusedLocationProvider";
-
     private static final String ATTRIBUTION_TAG = "LocationService";
 
     private final Object mLock = new Object();
@@ -339,7 +336,7 @@
         // provider has unfortunate hard dependencies on the network provider
         ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
                 mContext,
-                NETWORK_LOCATION_SERVICE_ACTION,
+                ACTION_NETWORK_PROVIDER,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                 com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
@@ -352,13 +349,13 @@
 
         // ensure that a fused provider exists which will work in direct boot
         Preconditions.checkState(!mContext.getPackageManager().queryIntentServicesAsUser(
-                new Intent(FUSED_LOCATION_SERVICE_ACTION),
+                new Intent(ACTION_FUSED_PROVIDER),
                 MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(),
                 "Unable to find a direct boot aware fused location provider");
 
         ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
                 mContext,
-                FUSED_LOCATION_SERVICE_ACTION,
+                ACTION_FUSED_PROVIDER,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
@@ -410,16 +407,17 @@
         for (String testProviderString : testProviderStrings) {
             String[] fragments = testProviderString.split(",");
             String name = fragments[0].trim();
-            ProviderProperties properties = new ProviderProperties(
-                    Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
-                    Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
-                    Boolean.parseBoolean(fragments[3]) /* requiresCell */,
-                    Boolean.parseBoolean(fragments[4]) /* hasMonetaryCost */,
-                    Boolean.parseBoolean(fragments[5]) /* supportsAltitude */,
-                    Boolean.parseBoolean(fragments[6]) /* supportsSpeed */,
-                    Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
-                    Integer.parseInt(fragments[8]) /* powerUsage */,
-                    Integer.parseInt(fragments[9]) /* accuracy */);
+            ProviderProperties properties = new ProviderProperties.Builder()
+                    .setHasNetworkRequirement(Boolean.parseBoolean(fragments[1]))
+                    .setHasSatelliteRequirement(Boolean.parseBoolean(fragments[2]))
+                    .setHasCellRequirement(Boolean.parseBoolean(fragments[3]))
+                    .setHasMonetaryCost(Boolean.parseBoolean(fragments[4]))
+                    .setHasAltitudeSupport(Boolean.parseBoolean(fragments[5]))
+                    .setHasSpeedSupport(Boolean.parseBoolean(fragments[6]))
+                    .setHasBearingSupport(Boolean.parseBoolean(fragments[7]))
+                    .setPowerUsage(Integer.parseInt(fragments[8]))
+                    .setAccuracy(Integer.parseInt(fragments[9]))
+                    .build();
             getOrAddLocationProviderManager(name).setMockProvider(
                     new MockLocationProvider(properties, CallerIdentity.fromContext(mContext)));
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 477c037b..d16267f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.location.gnss;
 
+import static android.location.provider.ProviderProperties.ACCURACY_FINE;
+import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
+
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_GSM_CELLID;
 import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
@@ -58,7 +61,8 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
@@ -88,7 +92,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.location.GpsNetInitiatedHandler;
 import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.FgThread;
 import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
@@ -122,16 +125,14 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            /* requiresNetwork = */false,
-            /* requiresSatellite = */true,
-            /* requiresCell = */false,
-            /* hasMonetaryCost = */false,
-            /* supportAltitude = */true,
-            /* supportsSpeed = */true,
-            /* supportsBearing = */true,
-            ProviderProperties.POWER_USAGE_HIGH,
-            ProviderProperties.ACCURACY_FINE);
+    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
+                .setHasSatelliteRequirement(true)
+                .setHasAltitudeSupport(true)
+                .setHasSpeedSupport(true)
+                .setHasBearingSupport(true)
+                .setPowerUsage(POWER_USAGE_HIGH)
+                .setAccuracy(ACCURACY_FINE)
+                .build();
 
     // The AGPS SUPL mode
     private static final int AGPS_SUPL_MODE_MSA = 0x02;
diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java
index b34beed..b8b54b3 100644
--- a/services/core/java/com/android/server/location/injector/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/injector/LocationEventLog.java
@@ -26,11 +26,11 @@
 
 import android.annotation.Nullable;
 import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Build;
 import android.os.PowerManager.LocationPowerSaveMode;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.server.utils.eventlog.LocalEventLog;
 
 /** In memory event log for location events. */
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index 5364feb..e22a014 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -18,12 +18,12 @@
 
 import android.annotation.Nullable;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
 import android.os.Bundle;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderController.java b/services/core/java/com/android/server/location/provider/LocationProviderController.java
index e49d9db..a0e3794 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderController.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderController.java
@@ -17,9 +17,9 @@
 package com.android.server.location.provider;
 
 import android.annotation.Nullable;
+import android.location.provider.ProviderRequest;
 import android.os.Bundle;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.server.location.provider.AbstractLocationProvider.Listener;
 import com.android.server.location.provider.AbstractLocationProvider.State;
 
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 858b762..14f0100 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -55,7 +55,8 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
 import android.os.Build;
@@ -82,7 +83,6 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index f9aa402..dce7b08 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,12 +21,11 @@
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
 
-import com.android.internal.location.ProviderRequest;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
index c1b0abf..cb7264e 100644
--- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
@@ -21,12 +21,12 @@
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
index 1f4c4cf..a5758a3 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
@@ -16,16 +16,18 @@
 
 package com.android.server.location.provider;
 
+import static android.location.provider.ProviderProperties.ACCURACY_FINE;
+import static android.location.provider.ProviderProperties.POWER_USAGE_LOW;
+
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 
 import android.content.Context;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
 
-import com.android.internal.location.ProviderRequest;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -38,16 +40,10 @@
  */
 public class PassiveLocationProvider extends AbstractLocationProvider {
 
-    private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            /* requiresNetwork = */false,
-            /* requiresSatellite = */false,
-            /* requiresCell = */false,
-            /* hasMonetaryCost = */false,
-            /* supportsAltitude = */false,
-            /* supportsSpeed = */false,
-            /* supportsBearing = */false,
-            ProviderProperties.POWER_USAGE_LOW,
-            ProviderProperties.ACCURACY_COARSE);
+    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
+            .setPowerUsage(POWER_USAGE_LOW)
+            .setAccuracy(ACCURACY_FINE)
+            .build();
 
     public PassiveLocationProvider(Context context) {
         // using a direct executor is ok because this class has no locks that could deadlock
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index d3fee82..b35af4f 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -20,9 +20,9 @@
 import android.content.Context;
 import android.location.LocationManager;
 import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
 import android.os.Binder;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.injector.Injector;
 
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 345fdc0..c274c28 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -22,7 +22,10 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ILocationProvider;
+import android.location.provider.ILocationProviderManager;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
 import android.os.Bundle;
@@ -30,9 +33,6 @@
 import android.os.RemoteException;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.location.ILocationProvider;
-import com.android.internal.location.ILocationProviderManager;
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.ServiceWatcher;
 import com.android.server.location.provider.AbstractLocationProvider;
@@ -106,7 +106,7 @@
 
             ProviderRequest request = mRequest;
             if (!request.equals(ProviderRequest.EMPTY_REQUEST)) {
-                provider.setRequest(request, request.getWorkSource());
+                provider.setRequest(request);
             }
         }
     }
@@ -142,7 +142,7 @@
         mRequest = request;
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
-            provider.setRequest(request, request.getWorkSource());
+            provider.setRequest(request);
         });
     }
 
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index 03ce8ec..2b3c0bf 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -610,6 +610,38 @@
         }
     }
 
+    /**
+     * Sets whether the controller should record provider state changes for later dumping via
+     * {@link #getStateForTests()}.
+     */
+    void setProviderStateRecordingEnabled(boolean enabled) {
+        mThreadingDomain.assertCurrentThread();
+
+        synchronized (mSharedLock) {
+            mPrimaryProvider.setStateChangeRecordingEnabled(enabled);
+            mSecondaryProvider.setStateChangeRecordingEnabled(enabled);
+        }
+    }
+
+    /**
+     * Returns a snapshot of the current controller state for tests.
+     */
+    @NonNull
+    LocationTimeZoneManagerServiceState getStateForTests() {
+        mThreadingDomain.assertCurrentThread();
+
+        synchronized (mSharedLock) {
+            LocationTimeZoneManagerServiceState.Builder builder =
+                    new LocationTimeZoneManagerServiceState.Builder();
+            if (mLastSuggestion != null) {
+                builder.setLastSuggestion(mLastSuggestion);
+            }
+            builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates())
+                    .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates());
+            return builder.build();
+        }
+    }
+
     @Nullable
     private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) {
         LocationTimeZoneProvider targetProvider;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 9f159fb..54535eb 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -17,11 +17,10 @@
 package com.android.server.location.timezone;
 
 import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
 import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,7 +32,6 @@
 import android.os.RemoteCallback;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemProperties;
 import android.service.timezone.TimeZoneProviderService;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -42,6 +40,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 import com.android.server.timezonedetector.TimeZoneDetectorInternal;
@@ -161,6 +160,14 @@
     @GuardedBy("mSharedLock")
     private ControllerEnvironmentImpl mEnvironment;
 
+    @GuardedBy("mSharedLock")
+    @NonNull
+    private String mPrimaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;
+
+    @GuardedBy("mSharedLock")
+    @NonNull
+    private String mSecondaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;
+
     LocationTimeZoneManagerService(Context context) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mHandler = FgThread.getHandler();
@@ -231,9 +238,9 @@
 
     private LocationTimeZoneProvider createPrimaryProvider() {
         LocationTimeZoneProviderProxy proxy;
-        if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) {
+        if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) {
             proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
-        } else if (isDisabled(PRIMARY_PROVIDER_NAME)) {
+        } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) {
             proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
         } else {
             proxy = new RealLocationTimeZoneProviderProxy(
@@ -250,9 +257,9 @@
 
     private LocationTimeZoneProvider createSecondaryProvider() {
         LocationTimeZoneProviderProxy proxy;
-        if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) {
+        if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) {
             proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
-        } else if (isDisabled(SECONDARY_PROVIDER_NAME)) {
+        } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) {
             proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
         } else {
             proxy = new RealLocationTimeZoneProviderProxy(
@@ -268,16 +275,14 @@
     }
 
     /** Used for bug triage and in tests to simulate provider events. */
-    private static boolean isInSimulationMode(String providerName) {
-        return isProviderModeSetInSystemProperties(providerName,
-                SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
+    private boolean isProviderInSimulationMode(String providerName) {
+        return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED);
     }
 
     /** Used for bug triage, tests and experiments to remove a provider. */
-    private boolean isDisabled(String providerName) {
+    private boolean isProviderDisabled(String providerName) {
         return !isProviderEnabledInConfig(providerName)
-                || isProviderModeSetInSystemProperties(
-                        providerName, SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
+                || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED);
     }
 
     private boolean isProviderEnabledInConfig(String providerName) {
@@ -299,25 +304,18 @@
         return resources.getBoolean(providerEnabledConfigId);
     }
 
-    private static boolean isProviderModeSetInSystemProperties(
-            @NonNull String providerName, @NonNull String mode) {
-        String systemPropertyKey;
+    private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) {
         switch (providerName) {
             case PRIMARY_PROVIDER_NAME: {
-                systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
-                break;
+                return Objects.equals(mPrimaryProviderModeOverride, mode);
             }
             case SECONDARY_PROVIDER_NAME: {
-                systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
-                break;
+                return Objects.equals(mSecondaryProviderModeOverride, mode);
             }
             default: {
                 throw new IllegalArgumentException(providerName);
             }
         }
-
-        String systemPropertyProviderMode = SystemProperties.get(systemPropertyKey, null);
-        return Objects.equals(systemPropertyProviderMode, mode);
     }
 
     /**
@@ -347,6 +345,67 @@
                 this, in, out, err, args, callback, resultReceiver);
     }
 
+    /** Sets this service into provider state recording mode for tests. */
+    void setProviderModeOverride(@NonNull String providerName, @NonNull String mode) {
+        enforceManageTimeZoneDetectorPermission();
+
+        Preconditions.checkArgument(
+                PRIMARY_PROVIDER_NAME.equals(providerName)
+                        || SECONDARY_PROVIDER_NAME.equals(providerName));
+        Preconditions.checkArgument(PROVIDER_MODE_OVERRIDE_DISABLED.equals(mode)
+                || PROVIDER_MODE_OVERRIDE_SIMULATED.equals(mode)
+                || PROVIDER_MODE_OVERRIDE_NONE.equals(mode));
+
+        mThreadingDomain.postAndWait(() -> {
+            synchronized (mSharedLock) {
+                switch (providerName) {
+                    case PRIMARY_PROVIDER_NAME: {
+                        mPrimaryProviderModeOverride = mode;
+                        break;
+                    }
+                    case SECONDARY_PROVIDER_NAME: {
+                        mSecondaryProviderModeOverride = mode;
+                        break;
+                    }
+                }
+            }
+        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
+    }
+
+    /** Sets this service into provider state recording mode for tests. */
+    void setProviderStateRecordingEnabled(boolean enabled) {
+        enforceManageTimeZoneDetectorPermission();
+
+        mThreadingDomain.postAndWait(() -> {
+            synchronized (mSharedLock) {
+                if (mLocationTimeZoneDetectorController != null) {
+                    mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled);
+                }
+            }
+        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
+    }
+
+    /** Returns a snapshot of the current controller state for tests. */
+    @NonNull
+    LocationTimeZoneManagerServiceState getStateForTests() {
+        enforceManageTimeZoneDetectorPermission();
+
+        try {
+            return mThreadingDomain.postAndWait(
+                    () -> {
+                        synchronized (mSharedLock) {
+                            if (mLocationTimeZoneDetectorController == null) {
+                                return null;
+                            }
+                            return mLocationTimeZoneDetectorController.getStateForTests();
+                        }
+                    },
+                    BLOCKING_OP_WAIT_DURATION_MILLIS);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * Passes a {@link TestCommand} to the specified provider and waits for the response.
      */
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
new file mode 100644
index 0000000..b1dd55f
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
@@ -0,0 +1,97 @@
+/*
+ * 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.location.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/** A snapshot of the location time zone manager service's state for tests. */
+final class LocationTimeZoneManagerServiceState {
+
+    @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+    @NonNull private final List<ProviderState> mPrimaryProviderStates;
+    @NonNull private final List<ProviderState> mSecondaryProviderStates;
+
+    LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
+        mLastSuggestion = builder.mLastSuggestion;
+        mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
+        mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
+    }
+
+    @Nullable
+    public GeolocationTimeZoneSuggestion getLastSuggestion() {
+        return mLastSuggestion;
+    }
+
+    @NonNull
+    public List<ProviderState> getPrimaryProviderStates() {
+        return Collections.unmodifiableList(mPrimaryProviderStates);
+    }
+
+    @NonNull
+    public List<ProviderState> getSecondaryProviderStates() {
+        return Collections.unmodifiableList(mSecondaryProviderStates);
+    }
+
+    @Override
+    public String toString() {
+        return "LocationTimeZoneManagerServiceState{"
+                + "mLastSuggestion=" + mLastSuggestion
+                + ", mPrimaryProviderStates=" + mPrimaryProviderStates
+                + ", mSecondaryProviderStates=" + mSecondaryProviderStates
+                + '}';
+    }
+
+    static final class Builder {
+
+        private GeolocationTimeZoneSuggestion mLastSuggestion;
+        private List<ProviderState> mPrimaryProviderStates;
+        private List<ProviderState> mSecondaryProviderStates;
+
+        @NonNull
+        Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
+            mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+            return this;
+        }
+
+        @NonNull
+        Builder setPrimaryProviderStateChanges(@NonNull List<ProviderState> primaryProviderStates) {
+            mPrimaryProviderStates = new ArrayList<>(primaryProviderStates);
+            return this;
+        }
+
+        @NonNull
+        Builder setSecondaryProviderStateChanges(
+                @NonNull List<ProviderState> secondaryProviderStates) {
+            mSecondaryProviderStates = new ArrayList<>(secondaryProviderStates);
+            return this;
+        }
+
+        @NonNull
+        LocationTimeZoneManagerServiceState build() {
+            return new LocationTimeZoneManagerServiceState(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
index 554df07..6f9863c 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
@@ -15,23 +15,46 @@
  */
 package com.android.server.location.timezone;
 
+import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
 import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
 import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
 import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE;
 import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START;
 import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED;
+
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
 
 import android.annotation.NonNull;
+import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneManagerProto;
+import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.TimeZoneProviderStateProto;
 import android.os.Bundle;
 import android.os.ShellCommand;
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */
 class LocationTimeZoneManagerShellCommand extends ShellCommand {
@@ -58,9 +81,18 @@
             case SHELL_COMMAND_STOP: {
                 return runStop();
             }
+            case SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE: {
+                return runSetProviderModeOverride();
+            }
             case SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND: {
                 return runSendProviderTestCommand();
             }
+            case SHELL_COMMAND_RECORD_PROVIDER_STATES: {
+                return runRecordProviderStates();
+            }
+            case SHELL_COMMAND_DUMP_STATE: {
+                return runDumpControllerState();
+            }
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -70,35 +102,42 @@
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
-        pw.println("Location Time Zone Manager (location_time_zone_manager) commands:");
+        pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:");
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.printf("  %s\n", SHELL_COMMAND_START);
         pw.println("    Starts the location_time_zone_manager, creating time zone providers.");
         pw.printf("  %s\n", SHELL_COMMAND_STOP);
         pw.println("    Stops the location_time_zone_manager, destroying time zone providers.");
+        pw.printf("  %s <provider name> <mode>\n", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE);
+        pw.println("    Sets a provider into a test mode next time the service started.");
+        pw.printf("    Values: %s|%s|%s\n", PROVIDER_MODE_OVERRIDE_NONE,
+                PROVIDER_MODE_OVERRIDE_DISABLED, PROVIDER_MODE_OVERRIDE_SIMULATED);
+        pw.printf("  %s (true|false)\n", SHELL_COMMAND_RECORD_PROVIDER_STATES);
+        pw.printf("    Enables / disables provider state recording mode. See also %s. The default"
+                + " state is always \"false\".\n", SHELL_COMMAND_DUMP_STATE);
+        pw.println("    Note: When enabled, this mode consumes memory and it is only intended for"
+                + " testing.");
+        pw.println("     It should be disabled after use, or the device can be rebooted to"
+                + " reset the mode to disabled.");
+        pw.println("     Disabling (or enabling repeatedly) clears any existing stored states.");
+        pw.printf("  %s [%s]\n", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
+        pw.println("    Dumps Location Time Zone Manager state for tests as text or binary proto"
+                + " form.");
+        pw.println("    See the LocationTimeZoneManagerServiceStateProto definition for details.");
         pw.printf("  %s <provider name> <test command>\n",
                 SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
         pw.println("    Passes a test command to the named provider.");
         pw.println();
-        pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
-        pw.println();
         pw.printf("<provider name> = One of %s\n", VALID_PROVIDER_NAMES);
         pw.println();
-        pw.println("<test command> encoding:");
+        pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
+        pw.println();
+        pw.println("Provider <test command> encoding:");
         pw.println();
         TestCommand.printShellCommandEncodingHelp(pw);
         pw.println();
-        pw.printf("Provider modes can be modified by setting the \"%s\" or \"%s\"\n system"
-                        + " property and restarting the service or rebooting the device.\n",
-                SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY,
-                SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY);
-        pw.println("Values are:");
-        pw.printf("  %s - simulation mode (see below for commands)\n",
-                SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
-        pw.printf("  %s - disabled mode\n", SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
-        pw.println();
-        pw.println("Simulated providers can be used to test the system server behavior or to"
+        pw.println("Simulated provider mode can be used to test the system server behavior or to"
                 + " reproduce bugs without the complexity of using real providers.");
         pw.println();
         pw.println("The test commands for simulated providers are:");
@@ -132,6 +171,119 @@
         return 0;
     }
 
+    private int runSetProviderModeOverride() {
+        PrintWriter outPrintWriter = getOutPrintWriter();
+        try {
+            String providerName = getNextArgRequired();
+            String modeOverride = getNextArgRequired();
+            outPrintWriter.println("Setting provider mode override for " + providerName
+                    + " to " + modeOverride);
+            mService.setProviderModeOverride(providerName, modeOverride);
+        } catch (RuntimeException e) {
+            reportError(e);
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runRecordProviderStates() {
+        PrintWriter outPrintWriter = getOutPrintWriter();
+        boolean enabled;
+        try {
+            String nextArg = getNextArgRequired();
+            enabled = Boolean.parseBoolean(nextArg);
+        } catch (RuntimeException e) {
+            reportError(e);
+            return 1;
+        }
+
+        outPrintWriter.println("Setting provider state recording to " + enabled);
+        try {
+            mService.setProviderStateRecordingEnabled(enabled);
+        } catch (IllegalStateException e) {
+            reportError(e);
+            return 2;
+        }
+        return 0;
+    }
+
+    private int runDumpControllerState() {
+        LocationTimeZoneManagerServiceState state;
+        try {
+            state = mService.getStateForTests();
+        } catch (RuntimeException e) {
+            reportError(e);
+            return 1;
+        }
+
+        DualDumpOutputStream outputStream;
+        boolean useProto = Objects.equals(DUMP_STATE_OPTION_PROTO, getNextOption());
+        if (useProto) {
+            FileDescriptor outFd = getOutFileDescriptor();
+            outputStream = new DualDumpOutputStream(new ProtoOutputStream(outFd));
+        } else {
+            outputStream = new DualDumpOutputStream(
+                    new IndentingPrintWriter(getOutPrintWriter(), "  "));
+        }
+        if (state.getLastSuggestion() != null) {
+            GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
+            long lastSuggestionToken = outputStream.start(
+                    "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
+            for (String zoneId : lastSuggestion.getZoneIds()) {
+                outputStream.write(
+                        "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+            }
+            for (String debugInfo : lastSuggestion.getDebugInfo()) {
+                outputStream.write(
+                        "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+            }
+            outputStream.end(lastSuggestionToken);
+        }
+
+        writeProviderStates(outputStream, state.getPrimaryProviderStates(),
+                "primary_provider_states",
+                LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES);
+        writeProviderStates(outputStream, state.getSecondaryProviderStates(),
+                "secondary_provider_states",
+                LocationTimeZoneManagerServiceStateProto.SECONDARY_PROVIDER_STATES);
+        outputStream.flush();
+
+        return 0;
+    }
+
+    private static void writeProviderStates(DualDumpOutputStream outputStream,
+            List<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName,
+            long fieldId) {
+        for (LocationTimeZoneProvider.ProviderState providerState : providerStates) {
+            long providerStateToken = outputStream.start(fieldName, fieldId);
+            outputStream.write("state", TimeZoneProviderStateProto.STATE,
+                    convertProviderStateEnumToProtoEnum(providerState.stateEnum));
+            outputStream.end(providerStateToken);
+        }
+    }
+
+    private static int convertProviderStateEnumToProtoEnum(@ProviderStateEnum int stateEnum) {
+        switch (stateEnum) {
+            case PROVIDER_STATE_UNKNOWN:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNKNOWN;
+            case PROVIDER_STATE_STARTED_INITIALIZING:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_INITIALIZING;
+            case PROVIDER_STATE_STARTED_CERTAIN:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_CERTAIN;
+            case PROVIDER_STATE_STARTED_UNCERTAIN:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNCERTAIN;
+            case PROVIDER_STATE_STOPPED:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DISABLED;
+            case PROVIDER_STATE_PERM_FAILED:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_PERM_FAILED;
+            case PROVIDER_STATE_DESTROYED:
+                return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DESTROYED;
+            default: {
+                throw new IllegalArgumentException("Unknown stateEnum=" + stateEnum);
+            }
+        }
+    }
+
     private int runSendProviderTestCommand() {
         PrintWriter outPrintWriter = getOutPrintWriter();
 
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 132c167..9a7b775 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -49,6 +49,8 @@
 import com.android.server.timezonedetector.ReferenceWithHistory;
 
 import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -339,11 +341,20 @@
     @NonNull final String mProviderName;
 
     /**
+     * Usually {@code false} but can be set to {@code true} for testing.
+     */
+    @GuardedBy("mSharedLock")
+    private boolean mStateChangeRecording;
+
+    @GuardedBy("mSharedLock")
+    @NonNull
+    private final ArrayList<ProviderState> mRecordedStates = new ArrayList<>(0);
+
+    /**
      * The current state (with history for debugging).
      */
     @GuardedBy("mSharedLock")
-    final ReferenceWithHistory<ProviderState> mCurrentState =
-            new ReferenceWithHistory<>(10);
+    final ReferenceWithHistory<ProviderState> mCurrentState = new ReferenceWithHistory<>(10);
 
     /**
      * Used for scheduling initialization timeouts, i.e. for providers that have just been started.
@@ -423,6 +434,28 @@
     abstract void onDestroy();
 
     /**
+     * Sets the provider into state recording mode for tests.
+     */
+    final void setStateChangeRecordingEnabled(boolean enabled) {
+        mThreadingDomain.assertCurrentThread();
+        synchronized (mSharedLock) {
+            mStateChangeRecording = enabled;
+            mRecordedStates.clear();
+            mRecordedStates.trimToSize();
+        }
+    }
+
+    /**
+     * Returns recorded states.
+     */
+    final List<ProviderState> getRecordedStates() {
+        mThreadingDomain.assertCurrentThread();
+        synchronized (mSharedLock) {
+            return new ArrayList<>(mRecordedStates);
+        }
+    }
+
+    /**
      * Set the current state, for use by this class and subclasses only. If {@code #notifyChanges}
      * is {@code true} and {@code newState} is not equal to the old state, then {@link
      * ProviderListener#onProviderStateChange(ProviderState)} must be called on
@@ -434,8 +467,11 @@
             ProviderState oldState = mCurrentState.get();
             mCurrentState.set(newState);
             onSetCurrentState(newState);
-            if (notifyChanges) {
-                if (!Objects.equals(newState, oldState)) {
+            if (!Objects.equals(newState, oldState)) {
+                if (mStateChangeRecording) {
+                    mRecordedStates.add(newState);
+                }
+                if (notifyChanges) {
                     mProviderListener.onProviderStateChange(newState);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
index 06706cd..0ffc1ed 100644
--- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java
+++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
@@ -184,7 +184,7 @@
         List<PackageInfo> allPackages;
         try {
             allPackages = mPackageManager.getInstalledPackages(
-                    flags | PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM).getList();
+                    flags | PackageManager.MATCH_APEX, UserHandle.getCallingUserId()).getList();
         } catch (RemoteException e) {
             Slog.w(TAG, "Unable to retrieve all package names", e);
             return Collections.emptyList();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cf9867c..27008d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -393,7 +393,11 @@
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.utils.Watchable;
+import com.android.server.utils.Watched;
 import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedSparseBooleanArray;
+import com.android.server.utils.Watcher;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import dalvik.system.CloseGuard;
@@ -506,6 +510,8 @@
     public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
     public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
+    public static final boolean DEBUG_CACHES = false;
+    public static final boolean TRACE_CACHES = false;
 
     // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
     // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -793,11 +799,13 @@
     final Object mLock;
 
     // Keys are String (package name), values are Package.
+    @Watched
     @GuardedBy("mLock")
-    final ArrayMap<String, AndroidPackage> mPackages = new ArrayMap<>();
+    final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>();
 
     // Keys are isolated uids and values are the uid of the application
-    // that created the isolated proccess.
+    // that created the isolated process.
+    @Watched
     @GuardedBy("mLock")
     final SparseIntArray mIsolatedOwners = new SparseIntArray();
 
@@ -822,6 +830,7 @@
     private final TestUtilityService mTestUtilityService;
 
 
+    @Watched
     @GuardedBy("mLock")
     final Settings mSettings;
 
@@ -852,6 +861,7 @@
     @GuardedBy("mAvailableFeatures")
     final ArrayMap<String, FeatureInfo> mAvailableFeatures;
 
+    @Watched
     private final InstantAppRegistry mInstantAppRegistry;
 
     @GuardedBy("mLock")
@@ -1208,12 +1218,14 @@
             // Avoid invalidation-thrashing by preventing cache invalidations from causing property
             // writes if the cache isn't enabled yet.  We re-enable writes later when we're
             // done initializing.
+            sSnapshotCorked = true;
             PackageManager.corkPackageInfoCache();
         }
 
         @Override
         public void enablePackageCaches() {
             // Uncork cache invalidations and allow clients to cache package information.
+            sSnapshotCorked = false;
             PackageManager.uncorkPackageInfoCache();
         }
     }
@@ -1286,18 +1298,23 @@
         public String incrementalVersion = Build.VERSION.INCREMENTAL;
     }
 
+    @Watched
     private final AppsFilter mAppsFilter;
 
     final PackageParser2.Callback mPackageParserCallback;
 
     // Currently known shared libraries.
-    final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
-    final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mStaticLibsByDeclaringPackage =
-            new ArrayMap<>();
+    @Watched
+    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries =
+            new WatchedArrayMap<>();
+    @Watched
+    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+            mStaticLibsByDeclaringPackage = new WatchedArrayMap<>();
 
     // Mapping from instrumentation class names to info about them.
-    final ArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation =
-            new ArrayMap<>();
+    @Watched
+    final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation =
+            new WatchedArrayMap<>();
 
     // Packages whose data we have transfered into another package, thus
     // should no longer exist.
@@ -1336,15 +1353,22 @@
     /** Token for keys in mPendingEnableRollback. */
     private int mPendingEnableRollbackToken = 0;
 
+    @Watched
     volatile boolean mSystemReady;
-    volatile boolean mSafeMode;
+    @Watched
+    private volatile boolean mSafeMode;
     volatile boolean mHasSystemUidErrors;
-    private volatile SparseBooleanArray mWebInstantAppsDisabled = new SparseBooleanArray();
+    @Watched
+    private final WatchedSparseBooleanArray mWebInstantAppsDisabled =
+            new WatchedSparseBooleanArray();
 
-    ApplicationInfo mAndroidApplication;
+    @Watched
+    private ApplicationInfo mAndroidApplication;
+    @Watched
     final ActivityInfo mResolveActivity = new ActivityInfo();
     final ResolveInfo mResolveInfo = new ResolveInfo();
-    ComponentName mResolveComponentName;
+    @Watched
+    private ComponentName mResolveComponentName;
     AndroidPackage mPlatformPackage;
     ComponentName mCustomResolverComponentName;
 
@@ -1361,8 +1385,10 @@
     final ComponentName mInstantAppResolverSettingsComponent;
 
     /** Activity used to install instant applications */
-    ActivityInfo mInstantAppInstallerActivity;
-    final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
+    @Watched
+    private ActivityInfo mInstantAppInstallerActivity;
+    @Watched
+    private final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
 
     private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
             mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
@@ -1744,7 +1770,7 @@
     private static final long DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
             2 * 60 * 60 * 1000L; /* two hours */
 
-    UserManagerService mUserManager;
+    final UserManagerService mUserManager;
 
     // Stores a list of users whose package restrictions file needs to be updated
     private ArraySet<Integer> mDirtyUsers = new ArraySet<>();
@@ -1828,6 +1854,2908 @@
      */
     public static void invalidatePackageInfoCache() {
         PackageManager.invalidatePackageInfoCache();
+        onChanged();
+    }
+
+    private final Watcher mWatcher = new Watcher() {
+            @Override
+            public void onChange(@Nullable Watchable what) {
+                PackageManagerService.this.onChange(what);
+            }
+        };
+
+    /**
+     * A Snapshot is a subset of PackageManagerService state.  A snapshot is either live
+     * or snapped.  Live snapshots directly reference PackageManagerService attributes.
+     * Snapped snapshots contain deep copies of the attributes.
+     */
+    private class Snapshot {
+        public static final int LIVE = 1;
+        public static final int SNAPPED = 2;
+
+        public final Settings settings;
+        public final SparseIntArray isolatedOwners;
+        public final WatchedArrayMap<String, AndroidPackage> packages;
+        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> sharedLibs;
+        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> staticLibs;
+        public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation;
+        public final WatchedSparseBooleanArray webInstantAppsDisabled;
+        public final ComponentName resolveComponentName;
+        public final ActivityInfo resolveActivity;
+        public final ActivityInfo instantAppInstallerActivity;
+        public final ResolveInfo instantAppInstallerInfo;
+        public final InstantAppRegistry instantAppRegistry;
+        public final ApplicationInfo androidApplication;
+        public final String appPredictionServicePackage;
+        public final AppsFilter appsFilter;
+        public final PackageManagerService service;
+
+        Snapshot(int type) {
+            if (type == Snapshot.SNAPPED) {
+                settings = mSettings.snapshot();
+                isolatedOwners = mIsolatedOwners.clone();
+                packages = mPackages.snapshot();
+                sharedLibs = mSharedLibraries.snapshot();
+                staticLibs = mStaticLibsByDeclaringPackage.snapshot();
+                instrumentation = mInstrumentation.snapshot();
+                resolveComponentName = mResolveComponentName.clone();
+                resolveActivity = new ActivityInfo(mResolveActivity);
+                instantAppInstallerActivity =
+                        (mInstantAppInstallerActivity == null)
+                        ? null
+                        : new ActivityInfo(mInstantAppInstallerActivity);
+                instantAppInstallerInfo = new ResolveInfo(mInstantAppInstallerInfo);
+                webInstantAppsDisabled = mWebInstantAppsDisabled.snapshot();
+                instantAppRegistry = mInstantAppRegistry.snapshot();
+                androidApplication =
+                        (mAndroidApplication == null)
+                        ? null
+                        : new ApplicationInfo(mAndroidApplication);
+                appPredictionServicePackage = mAppPredictionServicePackage;
+                appsFilter = mAppsFilter.snapshot();
+            } else if (type == Snapshot.LIVE) {
+                settings = mSettings;
+                isolatedOwners = mIsolatedOwners;
+                packages = mPackages;
+                sharedLibs = mSharedLibraries;
+                staticLibs = mStaticLibsByDeclaringPackage;
+                instrumentation = mInstrumentation;
+                resolveComponentName = mResolveComponentName;
+                resolveActivity = mResolveActivity;
+                instantAppInstallerActivity = mInstantAppInstallerActivity;
+                instantAppInstallerInfo = mInstantAppInstallerInfo;
+                webInstantAppsDisabled = mWebInstantAppsDisabled;
+                instantAppRegistry = mInstantAppRegistry;
+                androidApplication = mAndroidApplication;
+                appPredictionServicePackage = mAppPredictionServicePackage;
+                appsFilter = mAppsFilter;
+            } else {
+                throw new IllegalArgumentException();
+            }
+            service = PackageManagerService.this;
+        }
+    }
+
+    /**
+     * A computer provides the functional interface to the cache
+     */
+    private interface Computer {
+
+        @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
+                int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid,
+                int userId, boolean resolveForStart, boolean allowDynamicSplits);
+        @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
+                int flags, int userId);
+        @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType,
+                int flags, int userId, int callingUid, boolean includeInstantApps);
+        @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
+                String resolvedType, int flags, int userId, int callingUid,
+                String instantAppPkgName);
+        @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent,
+                String resolvedType, int flags, int filterCallingUid, int userId,
+                boolean resolveForStart, boolean allowDynamicSplits, String pkgName,
+                String instantAppPkgName);
+        @Nullable ComponentName findInstallFailureActivity(String packageName, int filterCallingUid,
+                int userId);
+        ActivityInfo getActivityInfo(ComponentName component, int flags, int userId);
+        ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
+                int filterCallingUid, int userId);
+        ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,
+                int filterCallingUid, int userId);
+        AndroidPackage getPackage(String packageName);
+        AndroidPackage getPackage(int uid);
+        ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
+                int filterCallingUid, int userId);
+        ApplicationInfo getApplicationInfo(String packageName, int flags, int userId);
+        ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
+                int filterCallingUid, int userId);
+        ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags,
+                int filterCallingUid, int userId);
+        ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(Intent intent,
+                int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
+                int userId, boolean debug);
+        ComponentName getDefaultHomeActivity(int userId);
+        ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, int userId);
+        CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType,
+                int flags, int sourceUserId, int parentUserId);
+        Intent getHomeIntent();
+        List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,
+                String resolvedType, int userId);
+        List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
+                String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
+                boolean resolveForStart, int userId, Intent intent);
+        List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos,
+                String instantAppPkgName, @UserIdInt int userId, int filterCallingUid);
+        List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
+                int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
+                int userId);
+        List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId);
+        List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent,
+                String resolvedType, int flags, int userId, boolean resolveForStart,
+                boolean isRequesterInstantApp);
+        PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId);
+        PackageInfo getPackageInfo(String packageName, int flags, int userId);
+        PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags,
+                int filterCallingUid, int userId);
+        PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, int flags,
+                int filterCallingUid, int userId);
+        PackageSetting getPackageSetting(String packageName);
+        PackageSetting getPackageSettingInternal(String packageName, int callingUid);
+        ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId);
+        ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
+                int callingUid);
+        ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, Intent intent,
+                String resolvedType, int flags, int sourceUserId);
+        ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter, int sourceUserId,
+                int targetUserId);
+        ResolveInfo queryCrossProfileIntents(List<CrossProfileIntentFilter> matchingFilters,
+                Intent intent, String resolvedType, int flags, int sourceUserId,
+                boolean matchInCurrentProfile);
+        ResolveInfo querySkipCurrentProfileIntents(List<CrossProfileIntentFilter> matchingFilters,
+                Intent intent, String resolvedType, int flags, int sourceUserId);
+        ServiceInfo getServiceInfo(ComponentName component, int flags, int userId);
+        ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId,
+                int callingUid);
+        SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version);
+        String getInstantAppPackageName(int callingUid);
+        String resolveExternalPackageNameLPr(AndroidPackage pkg);
+        String resolveInternalPackageNameInternalLocked(String packageName, long versionCode,
+                int callingUid);
+        String resolveInternalPackageNameLPr(String packageName, long versionCode);
+        String[] getPackagesForUid(int uid);
+        String[] getPackagesForUidInternal(int uid, int callingUid);
+        String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
+                boolean isCallerInstantApp);
+        UserInfo getProfileParent(int userId);
+        boolean areWebInstantAppsDisabled(int userId);
+        boolean canViewInstantApps(int callingUid, int userId);
+        boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId,
+                int flags);
+        boolean hasCrossUserPermission(int callingUid, int callingUserId, int userId,
+                boolean requireFullPermission, boolean requirePermissionWhenSameUser);
+        boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos);
+        boolean hasPermission(String permission);
+        boolean isCallerSameApp(String packageName, int uid);
+        boolean isComponentVisibleToInstantApp(@Nullable ComponentName component);
+        boolean isComponentVisibleToInstantApp(@Nullable ComponentName component,
+                @ComponentType int type);
+        boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
+                String resolvedType, int flags);
+        boolean isInstantApp(String packageName, int userId);
+        boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid);
+        boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid);
+        boolean isInstantAppResolutionAllowed(Intent intent, List<ResolveInfo> resolvedActivities,
+                int userId, boolean skipPackageCheck);
+        boolean isInstantAppResolutionAllowedBody(Intent intent,
+                List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck);
+        boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId,
+                String resolvedType, int flags);
+        boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId);
+        boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId);
+        boolean isUserEnabled(int userId);
+        boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid,
+                @Nullable ComponentName component, @ComponentType int componentType, int userId);
+        boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid,
+                int userId);
+        int bestDomainVerificationStatus(int status1, int status2);
+        int checkUidPermission(String permName, int uid);
+        int getPackageUidInternal(String packageName, int flags, int userId, int callingUid);
+        int updateFlags(int flags, int userId);
+        int updateFlagsForApplication(int flags, int userId);
+        int updateFlagsForComponent(int flags, int userId);
+        int updateFlagsForPackage(int flags, int userId);
+        int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
+                boolean isImplicitImageCaptureIntentAndNotSetByDpc);
+        int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
+                boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc);
+        long getDomainVerificationStatusLPr(PackageSetting ps, int userId);
+        void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell, String message);
+        void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell, String message);
+        void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell,
+                boolean requirePermissionWhenSameUser, String message);
+    }
+
+    /**
+     * This class contains the implementation of the Computer functions.  It
+     * is entirely self-contained - it has no implicit access to
+     * PackageManagerService.
+     */
+    private static class ComputerEngine implements Computer {
+
+        // Cached attributes.  The names in this class are the same as the
+        // names in PackageManagerService; see that class for documentation.
+        private final Settings mSettings;
+        private final SparseIntArray mIsolatedOwners;
+        private final WatchedArrayMap<String, AndroidPackage> mPackages;
+        private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
+                mInstrumentation;
+        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+                mStaticLibsByDeclaringPackage;
+        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+                mSharedLibraries;
+        private final ComponentName mLocalResolveComponentName;
+        private final ActivityInfo mResolveActivity;
+        private final WatchedSparseBooleanArray mWebInstantAppsDisabled;
+        private final ActivityInfo mLocalInstantAppInstallerActivity;
+        private final ResolveInfo mInstantAppInstallerInfo;
+        private final InstantAppRegistry mInstantAppRegistry;
+        private final ApplicationInfo mLocalAndroidApplication;
+
+        // Immutable service attribute
+        private final String mAppPredictionServicePackage;
+
+        // TODO: create cache copies of the following attributes
+        private final AppsFilter mAppsFilter;
+
+        // The following are not cloned since changes to these have never
+        // been guarded by the PMS lock.
+        private final Context mContext;
+        private final UserManagerService mUserManager;
+        private final PermissionManagerServiceInternal mPermissionManager;
+        private final ApexManager mApexManager;
+        private final Injector mInjector;
+        private final ComponentResolver mComponentResolver;
+        private final InstantAppResolverConnection mInstantAppResolverConnection;
+        private final DefaultAppProvider mDefaultAppProvider;
+
+        // PackageManagerService attributes that are primitives are referenced through the
+        // pms object directly.  Primitives are the only attributes so referenced.
+        protected final PackageManagerService mService;
+        protected boolean safeMode() {
+            return mService.mSafeMode;
+        }
+        protected ComponentName resolveComponentName() {
+            return mLocalResolveComponentName;
+        }
+        protected ActivityInfo instantAppInstallerActivity() {
+            return mLocalInstantAppInstallerActivity;
+        }
+        protected ApplicationInfo androidApplication() {
+            return mLocalAndroidApplication;
+        }
+
+        ComputerEngine(Snapshot args) {
+            mSettings = args.settings;
+            mIsolatedOwners = args.isolatedOwners;
+            mPackages = args.packages;
+            mSharedLibraries = args.sharedLibs;
+            mStaticLibsByDeclaringPackage = args.staticLibs;
+            mInstrumentation = args.instrumentation;
+            mWebInstantAppsDisabled = args.webInstantAppsDisabled;
+            mLocalResolveComponentName = args.resolveComponentName;
+            mResolveActivity = args.resolveActivity;
+            mLocalInstantAppInstallerActivity = args.instantAppInstallerActivity;
+            mInstantAppInstallerInfo = args.instantAppInstallerInfo;
+            mInstantAppRegistry = args.instantAppRegistry;
+            mLocalAndroidApplication = args.androidApplication;
+            mAppsFilter = args.appsFilter;
+
+            mAppPredictionServicePackage = args.appPredictionServicePackage;
+
+            // The following are not cached copies.  Instead they are
+            // references to outside services.
+            mPermissionManager = args.service.mPermissionManager;
+            mUserManager = args.service.mUserManager;
+            mContext = args.service.mContext;
+            mInjector = args.service.mInjector;
+            mApexManager = args.service.mApexManager;
+            mComponentResolver = args.service.mComponentResolver;
+            mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
+            mDefaultAppProvider = args.service.mDefaultAppProvider;
+
+            // Used to reference PMS attributes that are primitives and which are not
+            // updated under control of the PMS lock.
+            mService = args.service;
+        }
+
+        public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+                String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
+                int filterCallingUid, int userId, boolean resolveForStart,
+                boolean allowDynamicSplits) {
+            if (!mUserManager.exists(userId)) return Collections.emptyList();
+            final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
+            enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                    false /* requireFullPermission */, false /* checkShell */,
+                    "query intent activities");
+            final String pkgName = intent.getPackage();
+            ComponentName comp = intent.getComponent();
+            if (comp == null) {
+                if (intent.getSelector() != null) {
+                    intent = intent.getSelector();
+                    comp = intent.getComponent();
+                }
+            }
+
+            flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
+                    comp != null || pkgName != null /*onlyExposedExplicitly*/,
+                    isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+                            flags));
+            if (comp != null) {
+                final List<ResolveInfo> list = new ArrayList<>(1);
+                final ActivityInfo ai = getActivityInfo(comp, flags, userId);
+                if (ai != null) {
+                    // When specifying an explicit component, we prevent the activity from being
+                    // used when either 1) the calling package is normal and the activity is within
+                    // an ephemeral application or 2) the calling package is ephemeral and the
+                    // activity is not visible to ephemeral applications.
+                    final boolean matchInstantApp =
+                            (flags & PackageManager.MATCH_INSTANT) != 0;
+                    final boolean matchVisibleToInstantAppOnly =
+                            (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+                    final boolean matchExplicitlyVisibleOnly =
+                            (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
+                    final boolean isCallerInstantApp =
+                            instantAppPkgName != null;
+                    final boolean isTargetSameInstantApp =
+                            comp.getPackageName().equals(instantAppPkgName);
+                    final boolean isTargetInstantApp =
+                            (ai.applicationInfo.privateFlags
+                                    & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+                    final boolean isTargetVisibleToInstantApp =
+                            (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+                    final boolean isTargetExplicitlyVisibleToInstantApp =
+                            isTargetVisibleToInstantApp
+                            && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP)
+                            == 0;
+                    final boolean isTargetHiddenFromInstantApp =
+                            !isTargetVisibleToInstantApp
+                            || (matchExplicitlyVisibleOnly
+                                    && !isTargetExplicitlyVisibleToInstantApp);
+                    final boolean blockInstantResolution =
+                            !isTargetSameInstantApp
+                            && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+                                    || (matchVisibleToInstantAppOnly && isCallerInstantApp
+                                            && isTargetHiddenFromInstantApp));
+                    final boolean blockNormalResolution =
+                            !resolveForStart && !isTargetInstantApp && !isCallerInstantApp
+                                    && shouldFilterApplicationLocked(
+                                    getPackageSettingInternal(ai.applicationInfo.packageName,
+                                            Process.SYSTEM_UID), filterCallingUid, userId);
+                    if (!blockInstantResolution && !blockNormalResolution) {
+                        final ResolveInfo ri = new ResolveInfo();
+                        ri.activityInfo = ai;
+                        list.add(ri);
+                    }
+                }
+
+                List<ResolveInfo> result = applyPostResolutionFilter(
+                        list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
+                        resolveForStart,
+                        userId, intent);
+                return result;
+            }
+
+            QueryIntentActivitiesResult lockedResult =
+                    queryIntentActivitiesInternalBody(
+                        intent, resolvedType, flags, filterCallingUid, userId, resolveForStart,
+                        allowDynamicSplits, pkgName, instantAppPkgName);
+            if (lockedResult.answer != null) {
+                return lockedResult.answer;
+            }
+
+            if (lockedResult.addInstant) {
+                String callingPkgName = getInstantAppPackageName(filterCallingUid);
+                boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+                lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent,
+                        resolvedType, flags, userId, resolveForStart, isRequesterInstantApp);
+            }
+            if (lockedResult.sortResult) {
+                Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER);
+            }
+            return applyPostResolutionFilter(
+                    lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid,
+                    resolveForStart, userId, intent);
+        }
+
+        public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+                String resolvedType, int flags, int userId) {
+            return queryIntentActivitiesInternal(
+                    intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(),
+                    userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
+        }
+
+        public @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
+                String resolvedType, int flags, int userId, int callingUid,
+                boolean includeInstantApps) {
+            if (!mUserManager.exists(userId)) return Collections.emptyList();
+            enforceCrossUserOrProfilePermission(callingUid,
+                    userId,
+                    false /*requireFullPermission*/,
+                    false /*checkShell*/,
+                    "query intent receivers");
+            final String instantAppPkgName = getInstantAppPackageName(callingUid);
+            flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
+                    false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+            ComponentName comp = intent.getComponent();
+            if (comp == null) {
+                if (intent.getSelector() != null) {
+                    intent = intent.getSelector();
+                    comp = intent.getComponent();
+                }
+            }
+            if (comp != null) {
+                final List<ResolveInfo> list = new ArrayList<>(1);
+                final ServiceInfo si = getServiceInfo(comp, flags, userId);
+                if (si != null) {
+                    // When specifying an explicit component, we prevent the service from being
+                    // used when either 1) the service is in an instant application and the
+                    // caller is not the same instant application or 2) the calling package is
+                    // ephemeral and the activity is not visible to ephemeral applications.
+                    final boolean matchInstantApp =
+                            (flags & PackageManager.MATCH_INSTANT) != 0;
+                    final boolean matchVisibleToInstantAppOnly =
+                            (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+                    final boolean isCallerInstantApp =
+                            instantAppPkgName != null;
+                    final boolean isTargetSameInstantApp =
+                            comp.getPackageName().equals(instantAppPkgName);
+                    final boolean isTargetInstantApp =
+                            (si.applicationInfo.privateFlags
+                                    & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+                    final boolean isTargetHiddenFromInstantApp =
+                            (si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
+                    final boolean blockInstantResolution =
+                            !isTargetSameInstantApp
+                            && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+                                    || (matchVisibleToInstantAppOnly && isCallerInstantApp
+                                            && isTargetHiddenFromInstantApp));
+
+                    final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
+                            && shouldFilterApplicationLocked(
+                            getPackageSettingInternal(si.applicationInfo.packageName,
+                                    Process.SYSTEM_UID), callingUid, userId);
+                    if (!blockInstantResolution && !blockNormalResolution) {
+                        final ResolveInfo ri = new ResolveInfo();
+                        ri.serviceInfo = si;
+                        list.add(ri);
+                    }
+                }
+                return list;
+            }
+
+            return queryIntentServicesInternalBody(intent, resolvedType, flags,
+                    userId, callingUid, instantAppPkgName);
+        }
+
+        public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
+                String resolvedType, int flags, int userId, int callingUid,
+                String instantAppPkgName) {
+            // reader
+            String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+                        resolvedType, flags, userId);
+                if (resolveInfos == null) {
+                    return Collections.emptyList();
+                }
+                return applyPostServiceResolutionFilter(
+                        resolveInfos, instantAppPkgName, userId, callingUid);
+            }
+            final AndroidPackage pkg = mPackages.get(pkgName);
+            if (pkg != null) {
+                final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+                        resolvedType, flags, pkg.getServices(),
+                        userId);
+                if (resolveInfos == null) {
+                    return Collections.emptyList();
+                }
+                return applyPostServiceResolutionFilter(
+                        resolveInfos, instantAppPkgName, userId, callingUid);
+            }
+            return Collections.emptyList();
+        }
+
+        public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
+                Intent intent, String resolvedType, int flags, int filterCallingUid, int userId,
+                boolean resolveForStart, boolean allowDynamicSplits, String pkgName,
+                String instantAppPkgName) {
+            // reader
+            boolean sortResult = false;
+            boolean addInstant = false;
+            List<ResolveInfo> result = null;
+            if (pkgName == null) {
+                List<CrossProfileIntentFilter> matchingFilters =
+                        getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
+                // Check for results that need to skip the current profile.
+                ResolveInfo xpResolveInfo  = querySkipCurrentProfileIntents(matchingFilters, intent,
+                        resolvedType, flags, userId);
+                if (xpResolveInfo != null) {
+                    List<ResolveInfo> xpResult = new ArrayList<>(1);
+                    xpResult.add(xpResolveInfo);
+                    return new QueryIntentActivitiesResult(
+                            applyPostResolutionFilter(
+                                    filterIfNotSystemUser(xpResult, userId), instantAppPkgName,
+                                    allowDynamicSplits, filterCallingUid, resolveForStart, userId,
+                                    intent));
+                }
+
+                // Check for results in the current profile.
+                result = filterIfNotSystemUser(mComponentResolver.queryActivities(
+                        intent, resolvedType, flags, userId), userId);
+                addInstant = isInstantAppResolutionAllowed(intent, result, userId,
+                        false /*skipPackageCheck*/);
+                // Check for cross profile results.
+                boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
+                xpResolveInfo = queryCrossProfileIntents(
+                        matchingFilters, intent, resolvedType, flags, userId,
+                        hasNonNegativePriorityResult);
+                if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) {
+                    boolean isVisibleToUser = filterIfNotSystemUser(
+                            Collections.singletonList(xpResolveInfo), userId).size() > 0;
+                    if (isVisibleToUser) {
+                        result.add(xpResolveInfo);
+                        sortResult = true;
+                    }
+                }
+                if (intent.hasWebURI()) {
+                    CrossProfileDomainInfo xpDomainInfo = null;
+                    final UserInfo parent = getProfileParent(userId);
+                    if (parent != null) {
+                        xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
+                                flags, userId, parent.id);
+                    }
+                    if (xpDomainInfo != null) {
+                        if (xpResolveInfo != null) {
+                            // If we didn't remove it, the cross-profile ResolveInfo would be twice
+                            // in the result.
+                            result.remove(xpResolveInfo);
+                        }
+                        if (result.size() == 0 && !addInstant) {
+                            // No result in current profile, but found candidate in parent user.
+                            // And we are not going to add ephemeral app, so we can return the
+                            // result straight away.
+                            result.add(xpDomainInfo.resolveInfo);
+                            return new QueryIntentActivitiesResult(
+                                    applyPostResolutionFilter(result, instantAppPkgName,
+                                            allowDynamicSplits, filterCallingUid, resolveForStart,
+                                            userId, intent));
+                        }
+                    } else if (result.size() <= 1 && !addInstant) {
+                        // No result in parent user and <= 1 result in current profile, and we
+                        // are not going to add ephemeral app, so we can return the result without
+                        // further processing.
+                        return new QueryIntentActivitiesResult(
+                                applyPostResolutionFilter(result, instantAppPkgName,
+                                allowDynamicSplits, filterCallingUid, resolveForStart, userId,
+                                intent));
+                    }
+                    // We have more than one candidate (combining results from current and parent
+                    // profile), so we need filtering and sorting.
+                    result = filterCandidatesWithDomainPreferredActivitiesLPr(
+                            intent, flags, result, xpDomainInfo, userId);
+                    sortResult = true;
+                }
+            } else {
+                final PackageSetting setting =
+                        getPackageSettingInternal(pkgName, Process.SYSTEM_UID);
+                result = null;
+                if (setting != null && setting.pkg != null && (resolveForStart
+                        || !shouldFilterApplicationLocked(setting, filterCallingUid, userId))) {
+                    result = filterIfNotSystemUser(mComponentResolver.queryActivities(
+                            intent, resolvedType, flags, setting.pkg.getActivities(), userId),
+                            userId);
+                }
+                if (result == null || result.size() == 0) {
+                    // the caller wants to resolve for a particular package; however, there
+                    // were no installed results, so, try to find an ephemeral result
+                    addInstant = isInstantAppResolutionAllowed(
+                                    intent, null /*result*/, userId, true /*skipPackageCheck*/);
+                    if (result == null) {
+                        result = new ArrayList<>();
+                    }
+                }
+            }
+            return new QueryIntentActivitiesResult(sortResult, addInstant, result);
+        }
+
+        /**
+         * Returns the activity component that can handle install failures.
+         * <p>By default, the instant application installer handles failures. However, an
+         * application may want to handle failures on its own. Applications do this by
+         * creating an activity with an intent filter that handles the action
+         * {@link Intent#ACTION_INSTALL_FAILURE}.
+         */
+        public @Nullable ComponentName findInstallFailureActivity(
+                String packageName, int filterCallingUid, int userId) {
+            final Intent failureActivityIntent = new Intent(Intent.ACTION_INSTALL_FAILURE);
+            failureActivityIntent.setPackage(packageName);
+            // IMPORTANT: disallow dynamic splits to avoid an infinite loop
+            final List<ResolveInfo> result = queryIntentActivitiesInternal(
+                    failureActivityIntent, null /*resolvedType*/, 0 /*flags*/,
+                    0 /*privateResolveFlags*/, filterCallingUid, userId, false /*resolveForStart*/,
+                    false /*allowDynamicSplits*/);
+            final int NR = result.size();
+            if (NR > 0) {
+                for (int i = 0; i < NR; i++) {
+                    final ResolveInfo info = result.get(i);
+                    if (info.activityInfo.splitName != null) {
+                        continue;
+                    }
+                    return new ComponentName(packageName, info.activityInfo.name);
+                }
+            }
+            return null;
+        }
+
+        public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
+            return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
+        }
+
+        /**
+         * Important: The provided filterCallingUid is used exclusively to filter out activities
+         * that can be seen based on user state. It's typically the original caller uid prior
+         * to clearing. Because it can only be provided by trusted code, its value can be
+         * trusted and will be used as-is; unlike userId which will be validated by this method.
+         */
+        public ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
+                int filterCallingUid, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            flags = updateFlagsForComponent(flags, userId);
+
+            if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
+                enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                        false /* requireFullPermission */, false /* checkShell */,
+                        "get activity info");
+            }
+
+            return getActivityInfoInternalBody(component, flags, filterCallingUid, userId);
+        }
+
+        public ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,
+                int filterCallingUid, int userId) {
+            ParsedActivity a = mComponentResolver.getActivity(component);
+
+            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
+
+            AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
+            if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
+                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
+                if (ps == null) return null;
+                if (shouldFilterApplicationLocked(
+                        ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
+                    return null;
+                }
+                return PackageInfoUtils.generateActivityInfo(pkg,
+                        a, flags, ps.readUserState(userId), userId, ps);
+            }
+            if (resolveComponentName().equals(component)) {
+                return PackageParser.generateActivityInfo(
+                        mResolveActivity, flags, new PackageUserState(), userId);
+            }
+            return null;
+        }
+
+        public AndroidPackage getPackage(String packageName) {
+            packageName = resolveInternalPackageNameLPr(
+                    packageName, PackageManager.VERSION_CODE_HIGHEST);
+            return mPackages.get(packageName);
+        }
+
+        public AndroidPackage getPackage(int uid) {
+            final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
+            AndroidPackage pkg = null;
+            final int numPackages = packageNames == null ? 0 : packageNames.length;
+            for (int i = 0; pkg == null && i < numPackages; i++) {
+                pkg = mPackages.get(packageNames[i]);
+            }
+            return pkg;
+        }
+
+        public ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
+                int filterCallingUid, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            PackageSetting ps = mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
+                    return null;
+                }
+                if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
+                    return null;
+                }
+                if (ps.pkg == null) {
+                    final PackageInfo pInfo = generatePackageInfo(ps, flags, userId);
+                    if (pInfo != null) {
+                        return pInfo.applicationInfo;
+                    }
+                    return null;
+                }
+                ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(ps.pkg, flags,
+                        ps.readUserState(userId), userId, ps);
+                if (ai != null) {
+                    ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
+                }
+                return ai;
+            }
+            return null;
+        }
+
+        public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
+            return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
+        }
+
+        /**
+         * Important: The provided filterCallingUid is used exclusively to filter out applications
+         * that can be seen based on user state. It's typically the original caller uid prior
+         * to clearing. Because it can only be provided by trusted code, its value can be
+         * trusted and will be used as-is; unlike userId which will be validated by this method.
+         */
+        public ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
+                int filterCallingUid, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            flags = updateFlagsForApplication(flags, userId);
+
+            if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
+                enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                        false /* requireFullPermission */, false /* checkShell */,
+                        "get application info");
+            }
+
+            return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId);
+        }
+
+        public ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags,
+                int filterCallingUid, int userId) {
+            // writer
+            // Normalize package name to handle renamed packages and static libs
+            packageName = resolveInternalPackageNameLPr(packageName,
+                    PackageManager.VERSION_CODE_HIGHEST);
+
+            AndroidPackage p = mPackages.get(packageName);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                    TAG, "getApplicationInfo " + packageName
+                    + ": " + p);
+            if (p != null) {
+                PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps == null) return null;
+                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
+                    return null;
+                }
+                if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
+                    return null;
+                }
+                // Note: isEnabledLP() does not apply here - always return info
+                ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(
+                        p, flags, ps.readUserState(userId), userId, ps);
+                if (ai != null) {
+                    ai.packageName = resolveExternalPackageNameLPr(p);
+                }
+                return ai;
+            }
+            if ((flags & PackageManager.MATCH_APEX) != 0) {
+                // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo
+                // returned from getApplicationInfo, but for APEX packages difference shouldn't be
+                // very big.
+                // TODO(b/155328545): generate proper application info for APEXes as well.
+                int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE;
+                if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+                    apexFlags = ApexManager.MATCH_FACTORY_PACKAGE;
+                }
+                final PackageInfo pi = mApexManager.getPackageInfo(packageName, apexFlags);
+                if (pi == null) {
+                    return null;
+                }
+                return pi.applicationInfo;
+            }
+            if ("android".equals(packageName)||"system".equals(packageName)) {
+                return androidApplication();
+            }
+            if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+                // Already generates the external package name
+                return generateApplicationInfoFromSettingsLPw(packageName,
+                        flags, filterCallingUid, userId);
+            }
+            return null;
+        }
+
+        public ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
+                Intent intent, int matchFlags, List<ResolveInfo> candidates,
+                CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
+            final ArrayList<ResolveInfo> result = new ArrayList<>();
+            final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
+            final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
+            final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>();
+            final ArrayList<ResolveInfo> neverList = new ArrayList<>();
+            final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
+            final int count = candidates.size();
+            // First, try to use linked apps. Partition the candidates into four lists:
+            // one for the final results, one for the "do not use ever", one for "undefined status"
+            // and finally one for "browser app type".
+            for (int n=0; n<count; n++) {
+                ResolveInfo info = candidates.get(n);
+                String packageName = info.activityInfo.packageName;
+                PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    // Add to the special match all list (Browser use case)
+                    if (info.handleAllWebDataURI) {
+                        matchAllList.add(info);
+                        continue;
+                    }
+                    // Try to get the status from User settings first
+                    long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+                    int status = (int)(packedStatus >> 32);
+                    int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
+                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
+                            Slog.i(TAG, "  + always: " + info.activityInfo.packageName
+                                    + " : linkgen=" + linkGeneration);
+                        }
+
+                        if (!intent.hasCategory(CATEGORY_BROWSABLE)
+                                || !intent.hasCategory(CATEGORY_DEFAULT)) {
+                            undefinedList.add(info);
+                            continue;
+                        }
+
+                        // Use link-enabled generation as preferredOrder, i.e.
+                        // prefer newly-enabled over earlier-enabled.
+                        info.preferredOrder = linkGeneration;
+                        alwaysList.add(info);
+                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
+                            Slog.i(TAG, "  + never: " + info.activityInfo.packageName);
+                        }
+                        neverList.add(info);
+                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
+                            Slog.i(TAG, "  + always-ask: " + info.activityInfo.packageName);
+                        }
+                        alwaysAskList.add(info);
+                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
+                            status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
+                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
+                            Slog.i(TAG, "  + ask: " + info.activityInfo.packageName);
+                        }
+                        undefinedList.add(info);
+                    }
+                }
+            }
+
+            // We'll want to include browser possibilities in a few cases
+            boolean includeBrowser = false;
+
+            // First try to add the "always" resolution(s) for the current user, if any
+            if (alwaysList.size() > 0) {
+                result.addAll(alwaysList);
+            } else {
+                // Add all undefined apps as we want them to appear in the disambiguation dialog.
+                result.addAll(undefinedList);
+                // Maybe add one for the other profile.
+                if (xpDomainInfo != null && (
+                        xpDomainInfo.bestDomainVerificationStatus
+                        != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
+                    result.add(xpDomainInfo.resolveInfo);
+                }
+                includeBrowser = true;
+            }
+
+            // The presence of any 'always ask' alternatives means we'll also offer browsers.
+            // If there were 'always' entries their preferred order has been set, so we also
+            // back that off to make the alternatives equivalent
+            if (alwaysAskList.size() > 0) {
+                for (ResolveInfo i : result) {
+                    i.preferredOrder = 0;
+                }
+                result.addAll(alwaysAskList);
+                includeBrowser = true;
+            }
+
+            if (includeBrowser) {
+                // Also add browsers (all of them or only the default one)
+                if (DEBUG_DOMAIN_VERIFICATION) {
+                    Slog.v(TAG, "   ...including browsers in candidate set");
+                }
+                if ((matchFlags & MATCH_ALL) != 0) {
+                    result.addAll(matchAllList);
+                } else {
+                    // Browser/generic handling case.  If there's a default browser, go straight
+                    // to that (but only if there is no other higher-priority match).
+                    final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(
+                            userId);
+                    int maxMatchPrio = 0;
+                    ResolveInfo defaultBrowserMatch = null;
+                    final int numCandidates = matchAllList.size();
+                    for (int n = 0; n < numCandidates; n++) {
+                        ResolveInfo info = matchAllList.get(n);
+                        // track the highest overall match priority...
+                        if (info.priority > maxMatchPrio) {
+                            maxMatchPrio = info.priority;
+                        }
+                        // ...and the highest-priority default browser match
+                        if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
+                            if (defaultBrowserMatch == null
+                                    || (defaultBrowserMatch.priority < info.priority)) {
+                                if (debug) {
+                                    Slog.v(TAG, "Considering default browser match " + info);
+                                }
+                                defaultBrowserMatch = info;
+                            }
+                        }
+                    }
+                    if (defaultBrowserMatch != null
+                            && defaultBrowserMatch.priority >= maxMatchPrio
+                            && !TextUtils.isEmpty(defaultBrowserPackageName))
+                    {
+                        if (debug) {
+                            Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
+                        }
+                        result.add(defaultBrowserMatch);
+                    } else {
+                        result.addAll(matchAllList);
+                    }
+                }
+
+                // If there is nothing selected, add all candidates and remove the ones that the
+                //user
+                // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
+                if (result.size() == 0) {
+                    result.addAll(candidates);
+                    result.removeAll(neverList);
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Report the 'Home' activity which is currently set as "always use this one". If non is set
+         * then reports the most likely home activity or null if there are more than one.
+         */
+        public ComponentName getDefaultHomeActivity(int userId) {
+            List<ResolveInfo> allHomeCandidates = new ArrayList<>();
+            ComponentName cn = getHomeActivitiesAsUser(allHomeCandidates, userId);
+            if (cn != null) {
+                return cn;
+            }
+            // TODO: This should not happen since there should always be a default package set for
+            //  ROLE_HOME in RoleManager. Continue with a warning log for now.
+            Slog.w(TAG, "Default package for ROLE_HOME is not set in RoleManager");
+
+            // Find the launcher with the highest priority and return that component if there are no
+            // other home activity with the same priority.
+            int lastPriority = Integer.MIN_VALUE;
+            ComponentName lastComponent = null;
+            final int size = allHomeCandidates.size();
+            for (int i = 0; i < size; i++) {
+                final ResolveInfo ri = allHomeCandidates.get(i);
+                if (ri.priority > lastPriority) {
+                    lastComponent = ri.activityInfo.getComponentName();
+                    lastPriority = ri.priority;
+                } else if (ri.priority == lastPriority) {
+                    // Two components found with same priority.
+                    lastComponent = null;
+                }
+            }
+            return lastComponent;
+        }
+
+        public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
+                int userId) {
+            Intent intent  = getHomeIntent();
+            List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
+                    PackageManager.GET_META_DATA, userId);
+            allHomeCandidates.clear();
+            if (resolveInfos == null) {
+                return null;
+            }
+            allHomeCandidates.addAll(resolveInfos);
+
+            final String packageName = mDefaultAppProvider.getDefaultHome(userId);
+            if (packageName == null) {
+                return null;
+            }
+
+            int resolveInfosSize = resolveInfos.size();
+            for (int i = 0; i < resolveInfosSize; i++) {
+                ResolveInfo resolveInfo = resolveInfos.get(i);
+
+                if (resolveInfo.activityInfo != null && TextUtils.equals(
+                        resolveInfo.activityInfo.packageName, packageName)) {
+                    return new ComponentName(resolveInfo.activityInfo.packageName,
+                            resolveInfo.activityInfo.name);
+                }
+            }
+            return null;
+        }
+
+        public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
+                String resolvedType, int flags, int sourceUserId, int parentUserId) {
+            if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
+                    sourceUserId)) {
+                return null;
+            }
+            List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
+                    resolvedType, flags, parentUserId);
+
+            if (resultTargetUser == null || resultTargetUser.isEmpty()) {
+                return null;
+            }
+            CrossProfileDomainInfo result = null;
+            int size = resultTargetUser.size();
+            for (int i = 0; i < size; i++) {
+                ResolveInfo riTargetUser = resultTargetUser.get(i);
+                // Intent filter verification is only for filters that specify a host. So don't
+                //return
+                // those that handle all web uris.
+                if (riTargetUser.handleAllWebDataURI) {
+                    continue;
+                }
+                String packageName = riTargetUser.activityInfo.packageName;
+                PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps == null) {
+                    continue;
+                }
+                long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
+                int status = (int)(verificationState >> 32);
+                if (result == null) {
+                    result = new CrossProfileDomainInfo();
+                    result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
+                            sourceUserId, parentUserId);
+                    result.bestDomainVerificationStatus = status;
+                } else {
+                    result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
+                            result.bestDomainVerificationStatus);
+                }
+            }
+            // Don't consider matches with status NEVER across profiles.
+            if (result != null && result.bestDomainVerificationStatus
+                    == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                return null;
+            }
+            return result;
+        }
+
+        public Intent getHomeIntent() {
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.addCategory(Intent.CATEGORY_HOME);
+            intent.addCategory(Intent.CATEGORY_DEFAULT);
+            return intent;
+        }
+
+        public List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,
+                String resolvedType, int userId) {
+            CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId);
+            if (resolver != null) {
+                return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);
+            }
+            return null;
+        }
+
+        /**
+         * Filters out ephemeral activities.
+         * <p>When resolving for an ephemeral app, only activities that 1) are defined in the
+         * ephemeral app or 2) marked with {@code visibleToEphemeral} are returned.
+         *
+         * @param resolveInfos The pre-filtered list of resolved activities
+         * @param ephemeralPkgName The ephemeral package name. If {@code null}, no filtering
+         *          is performed.
+         * @param intent
+         * @return A filtered list of resolved activities.
+         */
+        public List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
+                String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
+                boolean resolveForStart, int userId, Intent intent) {
+            final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId);
+            for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+                final ResolveInfo info = resolveInfos.get(i);
+                // remove locally resolved instant app web results when disabled
+                if (info.isInstantAppAvailable && blockInstant) {
+                    resolveInfos.remove(i);
+                    continue;
+                }
+                // allow activities that are defined in the provided package
+                if (allowDynamicSplits
+                        && info.activityInfo != null
+                        && info.activityInfo.splitName != null
+                        && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
+                                info.activityInfo.splitName)) {
+                    if (instantAppInstallerActivity() == null) {
+                        if (DEBUG_INSTALL) {
+                            Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
+                        }
+                        resolveInfos.remove(i);
+                        continue;
+                    }
+                    if (blockInstant && isInstantApp(info.activityInfo.packageName, userId)) {
+                        resolveInfos.remove(i);
+                        continue;
+                    }
+                    // requested activity is defined in a split that hasn't been installed yet.
+                    // add the installer to the resolve list
+                    if (DEBUG_INSTALL) {
+                        Slog.v(TAG, "Adding installer to the ResolveInfo list");
+                    }
+                    final ResolveInfo installerInfo = new ResolveInfo(
+                            mInstantAppInstallerInfo);
+                    final ComponentName installFailureActivity = findInstallFailureActivity(
+                            info.activityInfo.packageName,  filterCallingUid, userId);
+                    installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
+                            installFailureActivity,
+                            info.activityInfo.packageName,
+                            info.activityInfo.applicationInfo.longVersionCode,
+                            info.activityInfo.splitName);
+                    // add a non-generic filter
+                    installerInfo.filter = new IntentFilter();
+
+                    // This resolve info may appear in the chooser UI, so let us make it
+                    // look as the one it replaces as far as the user is concerned which
+                    // requires loading the correct label and icon for the resolve info.
+                    installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+                    installerInfo.labelRes = info.resolveLabelResId();
+                    installerInfo.icon = info.resolveIconResId();
+                    installerInfo.isInstantAppAvailable = true;
+                    resolveInfos.set(i, installerInfo);
+                    continue;
+                }
+                if (ephemeralPkgName == null) {
+                    // caller is a full app
+                    SettingBase callingSetting =
+                            mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid));
+                    PackageSetting resolvedSetting =
+                            getPackageSettingInternal(info.activityInfo.packageName, 0);
+                    if (resolveForStart
+                            || !mAppsFilter.shouldFilterApplication(
+                                    filterCallingUid, callingSetting, resolvedSetting, userId)) {
+                        continue;
+                    }
+                } else if (ephemeralPkgName.equals(info.activityInfo.packageName)) {
+                    // caller is same app; don't need to apply any other filtering
+                    continue;
+                } else if (resolveForStart
+                        && (intent.isWebIntent()
+                                || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0)
+                        && intent.getPackage() == null
+                        && intent.getComponent() == null) {
+                    // ephemeral apps can launch other ephemeral apps indirectly
+                    continue;
+                } else if (((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP)
+                                != 0)
+                        && !info.activityInfo.applicationInfo.isInstantApp()) {
+                    // allow activities that have been explicitly exposed to ephemeral apps
+                    continue;
+                }
+                resolveInfos.remove(i);
+            }
+            return resolveInfos;
+        }
+
+        public List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos,
+                String instantAppPkgName, @UserIdInt int userId, int filterCallingUid) {
+            for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+                final ResolveInfo info = resolveInfos.get(i);
+                if (instantAppPkgName == null) {
+                    SettingBase callingSetting =
+                            mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid));
+                    PackageSetting resolvedSetting =
+                            getPackageSettingInternal(info.serviceInfo.packageName, 0);
+                    if (!mAppsFilter.shouldFilterApplication(
+                            filterCallingUid, callingSetting, resolvedSetting, userId)) {
+                        continue;
+                    }
+                }
+                final boolean isEphemeralApp = info.serviceInfo.applicationInfo.isInstantApp();
+                // allow services that are defined in the provided package
+                if (isEphemeralApp && instantAppPkgName.equals(info.serviceInfo.packageName)) {
+                    if (info.serviceInfo.splitName != null
+                            && !ArrayUtils.contains(info.serviceInfo.applicationInfo.splitNames,
+                                    info.serviceInfo.splitName)) {
+                        // requested service is defined in a split that hasn't been installed yet.
+                        // add the installer to the resolve list
+                        if (DEBUG_INSTANT) {
+                            Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
+                        }
+                        final ResolveInfo installerInfo = new ResolveInfo(
+                                mInstantAppInstallerInfo);
+                        installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
+                                null /* installFailureActivity */,
+                                info.serviceInfo.packageName,
+                                info.serviceInfo.applicationInfo.longVersionCode,
+                                info.serviceInfo.splitName);
+                        // add a non-generic filter
+                        installerInfo.filter = new IntentFilter();
+                        // load resources from the correct package
+                        installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+                        resolveInfos.set(i, installerInfo);
+                    }
+                    continue;
+                }
+                // allow services that have been explicitly exposed to ephemeral apps
+                if (!isEphemeralApp
+                        && ((info.serviceInfo.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP)
+                                != 0)) {
+                    continue;
+                }
+                resolveInfos.remove(i);
+            }
+            return resolveInfos;
+        }
+
+        public List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
+                int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
+                int userId) {
+            final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
+
+            if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
+                Slog.v(TAG, "Filtering results with preferred activities. Candidates count: " +
+                        candidates.size());
+            }
+
+            final ArrayList<ResolveInfo> result =
+                    filterCandidatesWithDomainPreferredActivitiesLPrBody(
+                        intent, matchFlags, candidates, xpDomainInfo, userId, debug);
+
+            if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
+                Slog.v(TAG, "Filtered results with preferred activities. New candidates count: "
+                        + result.size());
+                for (ResolveInfo info : result) {
+                    Slog.v(TAG, "  + " + info.activityInfo);
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Filter out activities with systemUserOnly flag set, when current user is not System.
+         *
+         * @return filtered list
+         */
+        public List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                return resolveInfos;
+            }
+            for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+                ResolveInfo info = resolveInfos.get(i);
+                if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+                    resolveInfos.remove(i);
+                }
+            }
+            return resolveInfos;
+        }
+
+        public List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result,
+                Intent intent,
+                String resolvedType, int flags, int userId, boolean resolveForStart,
+                boolean isRequesterInstantApp) {
+            // first, check to see if we've got an instant app already installed
+            final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
+            ResolveInfo localInstantApp = null;
+            boolean blockResolution = false;
+            if (!alreadyResolvedLocally) {
+                final List<ResolveInfo> instantApps = mComponentResolver.queryActivities(
+                        intent,
+                        resolvedType,
+                        flags
+                            | PackageManager.GET_RESOLVED_FILTER
+                            | PackageManager.MATCH_INSTANT
+                            | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
+                        userId);
+                for (int i = instantApps.size() - 1; i >= 0; --i) {
+                    final ResolveInfo info = instantApps.get(i);
+                    final String packageName = info.activityInfo.packageName;
+                    final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                    if (ps.getInstantApp(userId)) {
+                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+                        final int status = (int)(packedStatus >> 32);
+                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                            // there's a local instant application installed, but, the user has
+                            // chosen to never use it; skip resolution and don't acknowledge
+                            // an instant application is even available
+                            if (DEBUG_INSTANT) {
+                                Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName);
+                            }
+                            blockResolution = true;
+                            break;
+                        } else {
+                            // we have a locally installed instant application; skip resolution
+                            // but acknowledge there's an instant application available
+                            if (DEBUG_INSTANT) {
+                                Slog.v(TAG, "Found installed instant app; pkg: " + packageName);
+                            }
+                            localInstantApp = info;
+                            break;
+                        }
+                    }
+                }
+            }
+            // no app installed, let's see if one's available
+            AuxiliaryResolveInfo auxiliaryResponse = null;
+            if (!blockResolution) {
+                if (localInstantApp == null) {
+                    // we don't have an instant app locally, resolve externally
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
+                    String token = UUID.randomUUID().toString();
+                    InstantAppDigest digest = InstantAppResolver.parseDigest(intent);
+                    final InstantAppRequest requestObject =
+                            new InstantAppRequest(null /*responseObj*/,
+                            intent /*origIntent*/, resolvedType, null /*callingPackage*/,
+                            null /*callingFeatureId*/, isRequesterInstantApp, userId,
+                            null /*verificationBundle*/, resolveForStart,
+                            digest.getDigestPrefixSecure(), token);
+                    auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
+                            mInstantAppResolverConnection, requestObject);
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                } else {
+                    // we have an instant application locally, but, we can't admit that since
+                    // callers shouldn't be able to determine prior browsing. create a placeholder
+                    // auxiliary response so the downstream code behaves as if there's an
+                    // instant application available externally. when it comes time to start
+                    // the instant application, we'll do the right thing.
+                    final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo;
+                    auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */,
+                                            ai.packageName, ai.longVersionCode,
+                            null /* splitName */);
+                }
+            }
+            if (intent.isWebIntent() && auxiliaryResponse == null) {
+                return result;
+            }
+            final PackageSetting ps =
+                    mSettings.getPackageLPr(instantAppInstallerActivity().packageName);
+            if (ps == null
+                    || !ps.readUserState(userId).isEnabled(instantAppInstallerActivity(), 0)) {
+                return result;
+            }
+            final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
+            ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
+                    instantAppInstallerActivity(), 0, ps.readUserState(userId), userId);
+            ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
+                    | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
+            // add a non-generic filter
+            ephemeralInstaller.filter = new IntentFilter();
+            if (intent.getAction() != null) {
+                ephemeralInstaller.filter.addAction(intent.getAction());
+            }
+            if (intent.getData() != null && intent.getData().getPath() != null) {
+                ephemeralInstaller.filter.addDataPath(
+                        intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
+            }
+            ephemeralInstaller.isInstantAppAvailable = true;
+            // make sure this resolver is the default
+            ephemeralInstaller.isDefault = true;
+            ephemeralInstaller.auxiliaryInfo = auxiliaryResponse;
+            if (DEBUG_INSTANT) {
+                Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
+            }
+
+            result.add(ephemeralInstaller);
+            return result;
+        }
+
+        public PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            if (ps == null) {
+                return null;
+            }
+            final int callingUid = Binder.getCallingUid();
+            // Filter out ephemeral app metadata:
+            //   * The system/shell/root can see metadata for any app
+            //   * An installed app can see metadata for 1) other installed apps
+            //     and 2) ephemeral apps that have explicitly interacted with it
+            //   * Ephemeral apps can only see their own data and exposed installed apps
+            //   * Holding a signature permission allows seeing instant apps
+            if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                return null;
+            }
+
+            if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0
+                    && ps.isSystem()) {
+                flags |= MATCH_ANY_USER;
+            }
+
+            final PackageUserState state = ps.readUserState(userId);
+            AndroidPackage p = ps.pkg;
+            if (p != null) {
+                // Compute GIDs only if requested
+                final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY
+                        : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId));
+                // Compute granted permissions only if package has requested permissions
+                final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions())
+                        ? Collections.emptySet()
+                        : mPermissionManager.getGrantedPermissions(ps.name, userId);
+
+                PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags,
+                        ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps);
+
+                if (packageInfo == null) {
+                    return null;
+                }
+
+                packageInfo.packageName = packageInfo.applicationInfo.packageName =
+                        resolveExternalPackageNameLPr(p);
+
+                return packageInfo;
+            } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 && state.isAvailable(flags)) {
+                PackageInfo pi = new PackageInfo();
+                pi.packageName = ps.name;
+                pi.setLongVersionCode(ps.versionCode);
+                pi.sharedUserId = (ps.sharedUser != null) ? ps.sharedUser.name : null;
+                pi.firstInstallTime = ps.firstInstallTime;
+                pi.lastUpdateTime = ps.lastUpdateTime;
+
+                ApplicationInfo ai = new ApplicationInfo();
+                ai.packageName = ps.name;
+                ai.uid = UserHandle.getUid(userId, ps.appId);
+                ai.primaryCpuAbi = ps.primaryCpuAbiString;
+                ai.secondaryCpuAbi = ps.secondaryCpuAbiString;
+                ai.setVersionCode(ps.versionCode);
+                ai.flags = ps.pkgFlags;
+                ai.privateFlags = ps.pkgPrivateFlags;
+                pi.applicationInfo =
+                        PackageParser.generateApplicationInfo(ai, flags, state, userId);
+
+                if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for ["
+                        + ps.name + "]. Provides a minimum info.");
+                return pi;
+            } else {
+                return null;
+            }
+        }
+
+        public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+            return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
+                    flags, Binder.getCallingUid(), userId);
+        }
+
+        /**
+         * Important: The provided filterCallingUid is used exclusively to filter out packages
+         * that can be seen based on user state. It's typically the original caller uid prior
+         * to clearing. Because it can only be provided by trusted code, its value can be
+         * trusted and will be used as-is; unlike userId which will be validated by this method.
+         */
+        public PackageInfo getPackageInfoInternal(String packageName, long versionCode,
+                int flags, int filterCallingUid, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            flags = updateFlagsForPackage(flags, userId);
+            enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                    false /* requireFullPermission */, false /* checkShell */, "get package info");
+
+            return getPackageInfoInternalBody(packageName, versionCode, flags, filterCallingUid,
+                    userId);
+        }
+
+        public PackageInfo getPackageInfoInternalBody(String packageName, long versionCode,
+                int flags, int filterCallingUid, int userId) {
+            // reader
+            // Normalize package name to handle renamed packages and static libs
+            packageName = resolveInternalPackageNameLPr(packageName, versionCode);
+
+            final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
+            if (matchFactoryOnly) {
+                // Instant app filtering for APEX modules is ignored
+                if ((flags & MATCH_APEX) != 0) {
+                    return mApexManager.getPackageInfo(packageName,
+                            ApexManager.MATCH_FACTORY_PACKAGE);
+                }
+                final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
+                if (ps != null) {
+                    if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
+                        return null;
+                    }
+                    if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
+                        return null;
+                    }
+                    return generatePackageInfo(ps, flags, userId);
+                }
+            }
+
+            AndroidPackage p = mPackages.get(packageName);
+            if (matchFactoryOnly && p != null && !p.isSystem()) {
+                return null;
+            }
+            if (DEBUG_PACKAGE_INFO)
+                Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
+            if (p != null) {
+                final PackageSetting ps = getPackageSetting(p.getPackageName());
+                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
+                    return null;
+                }
+                if (ps != null && shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
+                    return null;
+                }
+
+                return generatePackageInfo(ps, flags, userId);
+            }
+            if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
+                final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps == null) return null;
+                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
+                    return null;
+                }
+                if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
+                    return null;
+                }
+                return generatePackageInfo(ps, flags, userId);
+            }
+            if ((flags & MATCH_APEX) != 0) {
+                return mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE);
+            }
+            return null;
+        }
+
+        @Nullable
+        public PackageSetting getPackageSetting(String packageName) {
+            return getPackageSettingInternal(packageName, Binder.getCallingUid());
+        }
+
+        public PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
+            packageName = resolveInternalPackageNameInternalLocked(
+                    packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
+            return mSettings.getPackageLPr(packageName);
+        }
+
+        public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            if (getInstantAppPackageName(callingUid) != null) {
+                return ParceledListSlice.emptyList();
+            }
+            if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
+            flags = updateFlagsForPackage(flags, userId);
+
+            enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+                    false /* checkShell */, "get installed packages");
+
+            return getInstalledPackagesBody(flags, userId, callingUid);
+        }
+
+        public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
+                                                                          int callingUid) {
+            // writer
+            final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
+            final boolean listApex = (flags & MATCH_APEX) != 0;
+            final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0;
+
+            ArrayList<PackageInfo> list;
+            if (listUninstalled) {
+                list = new ArrayList<>(mSettings.getPackagesLocked().size());
+                for (PackageSetting ps : mSettings.getPackagesLocked().values()) {
+                    if (listFactory) {
+                        if (!ps.isSystem()) {
+                            continue;
+                        }
+                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
+                        if (psDisabled != null) {
+                            ps = psDisabled;
+                        }
+                    }
+                    if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
+                        continue;
+                    }
+                    if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        continue;
+                    }
+                    final PackageInfo pi = generatePackageInfo(ps, flags, userId);
+                    if (pi != null) {
+                        list.add(pi);
+                    }
+                }
+            } else {
+                list = new ArrayList<>(mPackages.size());
+                for (AndroidPackage p : mPackages.values()) {
+                    PackageSetting ps = getPackageSetting(p.getPackageName());
+                    if (listFactory) {
+                        if (!p.isSystem()) {
+                            continue;
+                        }
+                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
+                        if (psDisabled != null) {
+                            ps = psDisabled;
+                        }
+                    }
+                    if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
+                        continue;
+                    }
+                    if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        continue;
+                    }
+                    final PackageInfo pi = generatePackageInfo(ps, flags, userId);
+                    if (pi != null) {
+                        list.add(pi);
+                    }
+                }
+            }
+            if (listApex) {
+                if (listFactory) {
+                    list.addAll(mApexManager.getFactoryPackages());
+                } else {
+                    list.addAll(mApexManager.getActivePackages());
+                }
+                if (listUninstalled) {
+                    list.addAll(mApexManager.getInactivePackages());
+                }
+            }
+            return new ParceledListSlice<>(list);
+        }
+
+        /**
+         * If the filter's target user can handle the intent and is enabled: returns a ResolveInfo
+         * that
+         * will forward the intent to the filter's target user.
+         * Otherwise, returns null.
+         */
+        public ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter,
+                Intent intent,
+                String resolvedType, int flags, int sourceUserId) {
+            int targetUserId = filter.getTargetUserId();
+            List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
+                    resolvedType, flags, targetUserId);
+            if (resultTargetUser != null && isUserEnabled(targetUserId)) {
+                // If all the matches in the target profile are suspended, return null.
+                for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
+                    if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags
+                            & ApplicationInfo.FLAG_SUSPENDED) == 0) {
+                        return createForwardingResolveInfoUnchecked(filter, sourceUserId,
+                                targetUserId);
+                    }
+                }
+            }
+            return null;
+        }
+
+        public ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter,
+                int sourceUserId, int targetUserId) {
+            ResolveInfo forwardingResolveInfo = new ResolveInfo();
+            final long ident = Binder.clearCallingIdentity();
+            boolean targetIsProfile;
+            try {
+                targetIsProfile = mUserManager.getUserInfo(targetUserId).isManagedProfile();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            String className;
+            if (targetIsProfile) {
+                className = FORWARD_INTENT_TO_MANAGED_PROFILE;
+            } else {
+                className = FORWARD_INTENT_TO_PARENT;
+            }
+            ComponentName forwardingActivityComponentName = new ComponentName(
+                    androidApplication().packageName, className);
+            ActivityInfo forwardingActivityInfo =
+                    getActivityInfo(forwardingActivityComponentName, 0,
+                    sourceUserId);
+            if (!targetIsProfile) {
+                forwardingActivityInfo.showUserIcon = targetUserId;
+                forwardingResolveInfo.noResourceId = true;
+            }
+            forwardingResolveInfo.activityInfo = forwardingActivityInfo;
+            forwardingResolveInfo.priority = 0;
+            forwardingResolveInfo.preferredOrder = 0;
+            forwardingResolveInfo.match = 0;
+            forwardingResolveInfo.isDefault = true;
+            forwardingResolveInfo.filter = filter;
+            forwardingResolveInfo.targetUserId = targetUserId;
+            return forwardingResolveInfo;
+        }
+
+        // Return matching ResolveInfo in target user if any.
+        public ResolveInfo queryCrossProfileIntents(
+                List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
+                int flags, int sourceUserId, boolean matchInCurrentProfile) {
+            if (matchingFilters != null) {
+                // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
+                // match the same intent. For performance reasons, it is better not to
+                // run queryIntent twice for the same userId
+                SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
+                int size = matchingFilters.size();
+                for (int i = 0; i < size; i++) {
+                    CrossProfileIntentFilter filter = matchingFilters.get(i);
+                    int targetUserId = filter.getTargetUserId();
+                    boolean skipCurrentProfile =
+                            (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
+                    boolean skipCurrentProfileIfNoMatchFound =
+                            (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
+                    if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
+                            && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
+                        // Checking if there are activities in the target user that can handle the
+                        // intent.
+                        ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
+                                resolvedType, flags, sourceUserId);
+                        if (resolveInfo != null) return resolveInfo;
+                        alreadyTriedUserIds.put(targetUserId, true);
+                    }
+                }
+            }
+            return null;
+        }
+
+        public ResolveInfo querySkipCurrentProfileIntents(
+                List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
+                int flags, int sourceUserId) {
+            if (matchingFilters != null) {
+                int size = matchingFilters.size();
+                for (int i = 0; i < size; i ++) {
+                    CrossProfileIntentFilter filter = matchingFilters.get(i);
+                    if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
+                        // Checking if there are activities in the target user that can handle the
+                        // intent.
+                        ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
+                                resolvedType, flags, sourceUserId);
+                        if (resolveInfo != null) {
+                            return resolveInfo;
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+
+        public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            final int callingUid = Binder.getCallingUid();
+            flags = updateFlagsForComponent(flags, userId);
+            enforceCrossUserOrProfilePermission(callingUid, userId,
+                    false /* requireFullPermission */,
+                    false /* checkShell */, "get service info");
+            return getServiceInfoBody(component, flags, userId, callingUid);
+        }
+
+        public ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId,
+                                                 int callingUid) {
+            ParsedService s = mComponentResolver.getService(component);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                    TAG, "getServiceInfo " + component + ": " + s);
+            if (s == null) {
+                return null;
+            }
+
+            AndroidPackage pkg = mPackages.get(s.getPackageName());
+            if (mSettings.isEnabledAndMatchLPr(pkg, s, flags, userId)) {
+                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
+                if (ps == null) return null;
+                if (shouldFilterApplicationLocked(
+                        ps, callingUid, component, TYPE_SERVICE, userId)) {
+                    return null;
+                }
+                return PackageInfoUtils.generateServiceInfo(pkg,
+                        s, flags, ps.readUserState(userId), userId, ps);
+            }
+            return null;
+        }
+
+        @Nullable
+        public SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) {
+            return getSharedLibraryInfo(name, version, mSharedLibraries, null);
+        }
+
+        /**
+         * Returns the package name of the calling Uid if it's an instant app. If it isn't
+         * instant, returns {@code null}.
+         */
+        public String getInstantAppPackageName(int callingUid) {
+            // If the caller is an isolated app use the owner's uid for the lookup.
+            if (Process.isIsolated(callingUid)) {
+                callingUid = mIsolatedOwners.get(callingUid);
+            }
+            final int appId = UserHandle.getAppId(callingUid);
+            final Object obj = mSettings.getSettingLPr(appId);
+            if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                final boolean isInstantApp = ps.getInstantApp(UserHandle.getUserId(callingUid));
+                return isInstantApp ? ps.pkg.getPackageName() : null;
+            }
+            return null;
+        }
+
+        public String resolveExternalPackageNameLPr(AndroidPackage pkg) {
+            if (pkg.getStaticSharedLibName() != null) {
+                return pkg.getManifestPackageName();
+            }
+            return pkg.getPackageName();
+        }
+
+        public String resolveInternalPackageNameInternalLocked(
+                String packageName, long versionCode, int callingUid) {
+            // Handle renamed packages
+            String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
+            packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
+
+            // Is this a static library?
+            LongSparseArray<SharedLibraryInfo> versionedLib =
+                    mStaticLibsByDeclaringPackage.get(packageName);
+            if (versionedLib == null || versionedLib.size() <= 0) {
+                return packageName;
+            }
+
+            // Figure out which lib versions the caller can see
+            LongSparseLongArray versionsCallerCanSee = null;
+            final int callingAppId = UserHandle.getAppId(callingUid);
+            if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
+                    && callingAppId != Process.ROOT_UID) {
+                versionsCallerCanSee = new LongSparseLongArray();
+                String libName = versionedLib.valueAt(0).getName();
+                String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
+                if (uidPackages != null) {
+                    for (String uidPackage : uidPackages) {
+                        PackageSetting ps = mSettings.getPackageLPr(uidPackage);
+                        final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName);
+                        if (libIdx >= 0) {
+                            final long libVersion = ps.usesStaticLibrariesVersions[libIdx];
+                            versionsCallerCanSee.append(libVersion, libVersion);
+                        }
+                    }
+                }
+            }
+
+            // Caller can see nothing - done
+            if (versionsCallerCanSee != null && versionsCallerCanSee.size() <= 0) {
+                return packageName;
+            }
+
+            // Find the version the caller can see and the app version code
+            SharedLibraryInfo highestVersion = null;
+            final int versionCount = versionedLib.size();
+            for (int i = 0; i < versionCount; i++) {
+                SharedLibraryInfo libraryInfo = versionedLib.valueAt(i);
+                if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey(
+                        libraryInfo.getLongVersion()) < 0) {
+                    continue;
+                }
+                final long libVersionCode = libraryInfo.getDeclaringPackage().getLongVersionCode();
+                if (versionCode != PackageManager.VERSION_CODE_HIGHEST) {
+                    if (libVersionCode == versionCode) {
+                        return libraryInfo.getPackageName();
+                    }
+                } else if (highestVersion == null) {
+                    highestVersion = libraryInfo;
+                } else if (libVersionCode  > highestVersion
+                        .getDeclaringPackage().getLongVersionCode()) {
+                    highestVersion = libraryInfo;
+                }
+            }
+
+            if (highestVersion != null) {
+                return highestVersion.getPackageName();
+            }
+
+            return packageName;
+        }
+
+        public String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+            final int callingUid = Binder.getCallingUid();
+            return resolveInternalPackageNameInternalLocked(packageName, versionCode,
+                    callingUid);
+        }
+
+        /**
+         * <em>IMPORTANT:</em> Not all packages returned by this method may be known
+         * to the system. There are two conditions in which this may occur:
+         * <ol>
+         *   <li>The package is on adoptable storage and the device has been removed</li>
+         *   <li>The package is being removed and the internal structures are partially updated</li>
+         * </ol>
+         * The second is an artifact of the current data structures and should be fixed. See
+         * b/111075456 for one such instance.
+         * This binder API is cached.  If the algorithm in this method changes,
+         * or if the underlying objecs (as returned by getSettingLPr()) change
+         * then the logic that invalidates the cache must be revisited.  See
+         * calls to invalidateGetPackagesForUidCache() to locate the points at
+         * which the cache is invalidated.
+         */
+        public String[] getPackagesForUid(int uid) {
+            return getPackagesForUidInternal(uid, Binder.getCallingUid());
+        }
+
+        public String[] getPackagesForUidInternal(int uid, int callingUid) {
+            final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
+            final int userId = UserHandle.getUserId(uid);
+            final int appId = UserHandle.getAppId(uid);
+            return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
+        }
+
+        public String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
+                                                         boolean isCallerInstantApp) {
+            // reader
+            final Object obj = mSettings.getSettingLPr(appId);
+            if (obj instanceof SharedUserSetting) {
+                if (isCallerInstantApp) {
+                    return null;
+                }
+                final SharedUserSetting sus = (SharedUserSetting) obj;
+                final int N = sus.packages.size();
+                String[] res = new String[N];
+                int i = 0;
+                for (int index = 0; index < N; index++) {
+                    final PackageSetting ps = sus.packages.valueAt(index);
+                    if (ps.getInstalled(userId)) {
+                        res[i++] = ps.name;
+                    }
+                }
+                return ArrayUtils.trimToSize(res, i);
+            } else if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                if (ps.getInstalled(userId)
+                        && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                    return new String[]{ps.name};
+                }
+            }
+            return null;
+        }
+
+        public UserInfo getProfileParent(int userId) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return mUserManager.getProfileParent(userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Returns whether or not instant apps have been disabled remotely.
+         */
+        public boolean areWebInstantAppsDisabled(int userId) {
+            return mWebInstantAppsDisabled.get(userId);
+        }
+
+        /**
+         * Returns whether or not a full application can see an instant application.
+         * <p>
+         * Currently, there are four cases in which this can occur:
+         * <ol>
+         * <li>The calling application is a "special" process. Special processes
+         *     are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li>
+         * <li>The calling application has the permission
+         *     {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li>
+         * <li>The calling application is the default launcher on the
+         *     system partition.</li>
+         * <li>The calling application is the default app prediction service.</li>
+         * </ol>
+         */
+        public boolean canViewInstantApps(int callingUid, int userId) {
+            if (callingUid < Process.FIRST_APPLICATION_UID) {
+                return true;
+            }
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_INSTANT_APPS) == PERMISSION_GRANTED) {
+                return true;
+            }
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.VIEW_INSTANT_APPS) == PERMISSION_GRANTED) {
+                final ComponentName homeComponent = getDefaultHomeActivity(userId);
+                if (homeComponent != null
+                        && isCallerSameApp(homeComponent.getPackageName(), callingUid)) {
+                    return true;
+                }
+                // TODO(b/122900055) Change/Remove this and replace with new permission role.
+                if (mAppPredictionServicePackage != null
+                        && isCallerSameApp(mAppPredictionServicePackage, callingUid)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId,
+                int flags) {
+            // Callers can access only the libs they depend on, otherwise they need to explicitly
+            // ask for the shared libraries given the caller is allowed to access all static libs.
+            if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) {
+                // System/shell/root get to see all static libs
+                final int appId = UserHandle.getAppId(uid);
+                if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
+                        || appId == Process.ROOT_UID) {
+                    return false;
+                }
+                // Installer gets to see all static libs.
+                if (PackageManager.PERMISSION_GRANTED
+                        == checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)) {
+                    return false;
+                }
+            }
+
+            // No package means no static lib as it is always on internal storage
+            if (ps == null || ps.pkg == null || !ps.pkg.isStaticSharedLibrary()) {
+                return false;
+            }
+
+            final SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr(
+                    ps.pkg.getStaticSharedLibName(), ps.pkg.getStaticSharedLibVersion());
+            if (libraryInfo == null) {
+                return false;
+            }
+
+            final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+            final String[] uidPackageNames = getPackagesForUid(resolvedUid);
+            if (uidPackageNames == null) {
+                return true;
+            }
+
+            for (String uidPackageName : uidPackageNames) {
+                if (ps.name.equals(uidPackageName)) {
+                    return false;
+                }
+                PackageSetting uidPs = mSettings.getPackageLPr(uidPackageName);
+                if (uidPs != null) {
+                    final int index = ArrayUtils.indexOf(uidPs.usesStaticLibraries,
+                            libraryInfo.getName());
+                    if (index < 0) {
+                        continue;
+                    }
+                    if (uidPs.pkg.getUsesStaticLibrariesVersions()[index]
+                            == libraryInfo.getLongVersion()) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        public boolean hasCrossUserPermission(
+                int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+                boolean requirePermissionWhenSameUser) {
+            if (!requirePermissionWhenSameUser && userId == callingUserId) {
+                return true;
+            }
+            if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+                return true;
+            }
+            if (requireFullPermission) {
+                return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+            }
+            return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+        }
+
+        /**
+         * @param resolveInfos list of resolve infos in descending priority order
+         * @return if the list contains a resolve info with non-negative priority
+         */
+        public boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos) {
+            return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0;
+        }
+
+        public boolean hasPermission(String permission) {
+            return mContext.checkCallingOrSelfPermission(permission)
+                    == PackageManager.PERMISSION_GRANTED;
+        }
+
+        public boolean isCallerSameApp(String packageName, int uid) {
+            AndroidPackage pkg = mPackages.get(packageName);
+            return pkg != null
+                    && UserHandle.getAppId(uid) == pkg.getUid();
+        }
+
+        public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) {
+            if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) {
+                return true;
+            }
+            if (isComponentVisibleToInstantApp(component, TYPE_SERVICE)) {
+                return true;
+            }
+            if (isComponentVisibleToInstantApp(component, TYPE_PROVIDER)) {
+                return true;
+            }
+            return false;
+        }
+
+        public boolean isComponentVisibleToInstantApp(
+                @Nullable ComponentName component, @ComponentType int type) {
+            if (type == TYPE_ACTIVITY) {
+                final ParsedActivity activity = mComponentResolver.getActivity(component);
+                if (activity == null) {
+                    return false;
+                }
+                final boolean visibleToInstantApp =
+                        (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+                final boolean explicitlyVisibleToInstantApp =
+                        (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP)
+                        == 0;
+                return visibleToInstantApp && explicitlyVisibleToInstantApp;
+            } else if (type == TYPE_RECEIVER) {
+                final ParsedActivity activity = mComponentResolver.getReceiver(component);
+                if (activity == null) {
+                    return false;
+                }
+                final boolean visibleToInstantApp =
+                        (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+                final boolean explicitlyVisibleToInstantApp =
+                        (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP)
+                        == 0;
+                return visibleToInstantApp && !explicitlyVisibleToInstantApp;
+            } else if (type == TYPE_SERVICE) {
+                final ParsedService service = mComponentResolver.getService(component);
+                return service != null
+                        && (service.getFlags() & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+            } else if (type == TYPE_PROVIDER) {
+                final ParsedProvider provider = mComponentResolver.getProvider(component);
+                return provider != null
+                        && (provider.getFlags() & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+            } else if (type == TYPE_UNKNOWN) {
+                return isComponentVisibleToInstantApp(component);
+            }
+            return false;
+        }
+
+        /**
+         * From Android R,
+         *  camera intents have to match system apps. The only exception to this is if
+         * the DPC has set the camera persistent preferred activity. This case was introduced
+         * because it is important that the DPC has the ability to set both system and non-system
+         * camera persistent preferred activities.
+         *
+         * @return {@code true} if the intent is a camera intent and the persistent preferred
+         * activity was not set by the DPC.
+         */
+        public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
+                String resolvedType, int flags) {
+            return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm(
+                    intent, userId, resolvedType, flags);
+        }
+
+        public boolean isInstantApp(String packageName, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+                    false /* checkShell */, "isInstantApp");
+
+            return isInstantAppInternal(packageName, userId, callingUid);
+        }
+
+        public boolean isInstantAppInternal(String packageName, @UserIdInt int userId,
+                int callingUid) {
+            if (HIDE_EPHEMERAL_APIS) {
+                return false;
+            }
+            return isInstantAppInternalBody(packageName, userId, callingUid);
+        }
+
+        public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId,
+                int callingUid) {
+            if (Process.isIsolated(callingUid)) {
+                callingUid = mIsolatedOwners.get(callingUid);
+            }
+            final PackageSetting ps = mSettings.getPackageLPr(packageName);
+            final boolean returnAllowed =
+                    ps != null
+                    && (isCallerSameApp(packageName, callingUid)
+                            || canViewInstantApps(callingUid, userId)
+                            || mInstantAppRegistry.isInstantAccessGranted(
+                                    userId, UserHandle.getAppId(callingUid), ps.appId));
+            if (returnAllowed) {
+                return ps.getInstantApp(userId);
+            }
+            return false;
+        }
+
+        public boolean isInstantAppResolutionAllowed(
+                Intent intent, List<ResolveInfo> resolvedActivities, int userId,
+                boolean skipPackageCheck) {
+            if (mInstantAppResolverConnection == null) {
+                return false;
+            }
+            if (instantAppInstallerActivity() == null) {
+                return false;
+            }
+            if (intent.getComponent() != null) {
+                return false;
+            }
+            if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
+                return false;
+            }
+            if (!skipPackageCheck && intent.getPackage() != null) {
+                return false;
+            }
+            if (!intent.isWebIntent()) {
+                // for non web intents, we should not resolve externally if an app already exists to
+                // handle it or if the caller didn't explicitly request it.
+                if ((resolvedActivities != null && resolvedActivities.size() != 0)
+                        || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) {
+                    return false;
+                }
+            } else {
+                if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) {
+                    return false;
+                } else if (areWebInstantAppsDisabled(userId)) {
+                    return false;
+                }
+            }
+            // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
+            // Or if there's already an ephemeral app installed that handles the action
+            return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId,
+                                                       skipPackageCheck);
+        }
+
+        // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
+        // Or if there's already an ephemeral app installed that handles the action
+        public boolean isInstantAppResolutionAllowedBody(
+                Intent intent, List<ResolveInfo> resolvedActivities, int userId,
+                boolean skipPackageCheck) {
+            final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
+            for (int n = 0; n < count; n++) {
+                final ResolveInfo info = resolvedActivities.get(n);
+                final String packageName = info.activityInfo.packageName;
+                final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    // only check domain verification status if the app is not a browser
+                    if (!info.handleAllWebDataURI) {
+                        // Try to get the status from User settings first
+                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+                        final int status = (int) (packedStatus >> 32);
+                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+                            || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                            if (DEBUG_INSTANT) {
+                                Slog.v(TAG, "DENY instant app;"
+                                    + " pkg: " + packageName + ", status: " + status);
+                            }
+                            return false;
+                        }
+                    }
+                    if (ps.getInstantApp(userId)) {
+                        if (DEBUG_INSTANT) {
+                            Slog.v(TAG, "DENY instant app installed;"
+                                    + " pkg: " + packageName);
+                        }
+                        return false;
+                    }
+                }
+            }
+            // We've exhausted all ways to deny ephemeral application; let the system look for them.
+            return true;
+        }
+
+        public boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId,
+                String resolvedType, int flags) {
+            PersistentPreferredIntentResolver ppir =
+                    mSettings.getPersistentPreferredActivities(userId);
+            //TODO(b/158003772): Remove double query
+            List<PersistentPreferredActivity> pprefs = ppir != null
+                    ? ppir.queryIntent(intent, resolvedType,
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+                    userId)
+                    : new ArrayList<>();
+            for (PersistentPreferredActivity ppa : pprefs) {
+                if (ppa.mIsSetByDpm) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId) {
+            if (!mInjector.getLocalService(ActivityTaskManagerInternal.class)
+                    .isCallerRecents(callingUid)) {
+                return false;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final int callingUserId = UserHandle.getUserId(callingUid);
+                if (ActivityManager.getCurrentUser() != callingUserId) {
+                    return false;
+                }
+                return mUserManager.isSameProfileGroup(callingUserId, targetUserId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        public boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        public boolean isUserEnabled(int userId) {
+            final long callingId = Binder.clearCallingIdentity();
+            try {
+                UserInfo userInfo = mUserManager.getUserInfo(userId);
+                return userInfo != null && userInfo.isEnabled();
+            } finally {
+                Binder.restoreCallingIdentity(callingId);
+            }
+        }
+
+        /**
+         * Returns whether or not access to the application should be filtered.
+         * <p>
+         * Access may be limited based upon whether the calling or target applications
+         * are instant applications.
+         *
+         * @see #canViewInstantApps(int, int)
+         */
+        public boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid,
+                @Nullable ComponentName component, @ComponentType int componentType, int userId) {
+            // if we're in an isolated process, get the real calling UID
+            if (Process.isIsolated(callingUid)) {
+                callingUid = mIsolatedOwners.get(callingUid);
+            }
+            final String instantAppPkgName = getInstantAppPackageName(callingUid);
+            final boolean callerIsInstantApp = instantAppPkgName != null;
+            if (ps == null) {
+                if (callerIsInstantApp) {
+                    // pretend the application exists, but, needs to be filtered
+                    return true;
+                }
+                return false;
+            }
+            // if the target and caller are the same application, don't filter
+            if (isCallerSameApp(ps.name, callingUid)) {
+                return false;
+            }
+            if (callerIsInstantApp) {
+                // both caller and target are both instant, but, different applications, filter
+                if (ps.getInstantApp(userId)) {
+                    return true;
+                }
+                // request for a specific component; if it hasn't been explicitly exposed through
+                // property or instrumentation target, filter
+                if (component != null) {
+                    final ParsedInstrumentation instrumentation =
+                            mInstrumentation.get(component);
+                    if (instrumentation != null
+                            && isCallerSameApp(instrumentation.getTargetPackage(), callingUid)) {
+                        return false;
+                    }
+                    return !isComponentVisibleToInstantApp(component, componentType);
+                }
+                // request for application; if no components have been explicitly exposed, filter
+                return !ps.pkg.isVisibleToInstantApps();
+            }
+            if (ps.getInstantApp(userId)) {
+                // caller can see all components of all instant applications, don't filter
+                if (canViewInstantApps(callingUid, userId)) {
+                    return false;
+                }
+                // request for a specific instant application component, filter
+                if (component != null) {
+                    return true;
+                }
+                // request for an instant application; if the caller hasn't been granted access,
+                //filter
+                return !mInstantAppRegistry.isInstantAccessGranted(
+                        userId, UserHandle.getAppId(callingUid), ps.appId);
+            }
+            int appId = UserHandle.getAppId(callingUid);
+            final SettingBase callingPs = mSettings.getSettingLPr(appId);
+            return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId);
+        }
+
+        /**
+         * @see #shouldFilterApplicationLocked(PackageSetting, int, ComponentName, int, int)
+         */
+        public boolean shouldFilterApplicationLocked(
+                @Nullable PackageSetting ps, int callingUid, int userId) {
+            return shouldFilterApplicationLocked(ps, callingUid, null, TYPE_UNKNOWN, userId);
+        }
+
+        /**
+         * Verification statuses are ordered from the worse to the best, except for
+         * INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse.
+         */
+        public int bestDomainVerificationStatus(int status1, int status2) {
+            if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                return status2;
+            }
+            if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                return status1;
+            }
+            return (int) MathUtils.max(status1, status2);
+        }
+
+        // NOTE: Can't remove without a major refactor. Keep around for now.
+        public int checkUidPermission(String permName, int uid) {
+            return mPermissionManager.checkUidPermission(uid, permName);
+        }
+
+        public int getPackageUidInternal(String packageName, int flags, int userId,
+                int callingUid) {
+            // reader
+            final AndroidPackage p = mPackages.get(packageName);
+            if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) {
+                PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
+                if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                    return -1;
+                }
+                return UserHandle.getUid(userId, p.getUid());
+            }
+            if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+                final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                if (ps != null && ps.isMatch(flags)
+                        && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                    return UserHandle.getUid(userId, ps.appId);
+                }
+            }
+
+            return -1;
+        }
+
+        /**
+         * Update given flags based on encryption status of current user.
+         */
+        public int updateFlags(int flags, int userId) {
+            if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) {
+                // Caller expressed an explicit opinion about what encryption
+                // aware/unaware components they want to see, so fall through and
+                // give them what they want
+            } else {
+                // Caller expressed no opinion, so match based on user state
+                if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
+                    flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+                } else {
+                    flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE;
+                }
+            }
+            return flags;
+        }
+
+        /**
+         * Update given flags when being used to request {@link ApplicationInfo}.
+         */
+        public int updateFlagsForApplication(int flags, int userId) {
+            return updateFlagsForPackage(flags, userId);
+        }
+
+        /**
+         * Update given flags when being used to request {@link ComponentInfo}.
+         */
+        public int updateFlagsForComponent(int flags, int userId) {
+            return updateFlags(flags, userId);
+        }
+
+        /**
+         * Update given flags when being used to request {@link PackageInfo}.
+         */
+        public int updateFlagsForPackage(int flags, int userId) {
+            final boolean isCallerSystemUser = UserHandle.getCallingUserId()
+                                               == UserHandle.USER_SYSTEM;
+            if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
+                // require the permission to be held; the calling uid and given user id referring
+                // to the same user is not sufficient
+                enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+                        !isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId),
+                        "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
+            } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
+                    && isCallerSystemUser
+                    && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) {
+                // If the caller wants all packages and has a restricted profile associated with it,
+                // then match all users. This is to make sure that launchers that need to access
+                //work
+                // profile apps don't start breaking. TODO: Remove this hack when launchers stop
+                //using
+                // MATCH_UNINSTALLED_PACKAGES to query apps in other profiles. b/31000380
+                flags |= PackageManager.MATCH_ANY_USER;
+            }
+            return updateFlags(flags, userId);
+        }
+
+        /**
+         * Update given flags when being used to request {@link ResolveInfo}.
+         * <p>Instant apps are resolved specially, depending upon context. Minimally,
+         * {@code}flags{@code} must have the {@link PackageManager#MATCH_INSTANT}
+         * flag set. However, this flag is only honoured in three circumstances:
+         * <ul>
+         * <li>when called from a system process</li>
+         * <li>when the caller holds the permission {@code
+         * android.permission.ACCESS_INSTANT_APPS}</li>
+         * <li>when resolution occurs to start an activity with a {@code android.intent.action.VIEW}
+         * action and a {@code android.intent.category.BROWSABLE} category</li>
+         * </ul>
+         */
+        public int updateFlagsForResolve(int flags, int userId, int callingUid,
+                boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
+            return updateFlagsForResolve(flags, userId, callingUid,
+                    wantInstantApps, false /*onlyExposedExplicitly*/,
+                    isImplicitImageCaptureIntentAndNotSetByDpc);
+        }
+
+        public int updateFlagsForResolve(int flags, int userId, int callingUid,
+                boolean wantInstantApps, boolean onlyExposedExplicitly,
+                boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
+            // Safe mode means we shouldn't match any third-party components
+            if (safeMode() || isImplicitImageCaptureIntentAndNotSetByDpc) {
+                flags |= PackageManager.MATCH_SYSTEM_ONLY;
+            }
+            if (getInstantAppPackageName(callingUid) != null) {
+                // But, ephemeral apps see both ephemeral and exposed, non-ephemeral components
+                if (onlyExposedExplicitly) {
+                    flags |= PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY;
+                }
+                flags |= PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY;
+                flags |= PackageManager.MATCH_INSTANT;
+            } else {
+                final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0;
+                final boolean allowMatchInstant = wantInstantApps
+                        || (wantMatchInstant && canViewInstantApps(callingUid, userId));
+                flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY
+                        | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY);
+                if (!allowMatchInstant) {
+                    flags &= ~PackageManager.MATCH_INSTANT;
+                }
+            }
+            return updateFlagsForComponent(flags, userId);
+        }
+
+        // Returns a packed value as a long:
+        //
+        // high 'int'-sized word: link status: undefined/ask/never/always.
+        // low 'int'-sized word: relative priority among 'always' results.
+        public long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
+            long result = ps.getDomainVerificationStatusForUser(userId);
+            // if none available, get the status
+            if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+                if (ps.getIntentFilterVerificationInfo() != null) {
+                    result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Checks if the request is from the system or an app that has the appropriate cross-user
+         * permissions defined as follows:
+         * <ul>
+         * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+         * <li>INTERACT_ACROSS_USERS if the given {@code userId} is in a different profile group
+         * to the caller.</li>
+         * <li>Otherwise,
+         *  INTERACT_ACROSS_PROFILES if the given {@code userId} is in the same profile
+         * group as the caller.</li>
+         * </ul>
+         *
+         * @param checkShell whether to prevent shell from access if there's a debugging restriction
+         * @param message the message to log on security exception
+         */
+        public void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            if (userId < 0) {
+                throw new IllegalArgumentException("Invalid userId " + userId);
+            }
+            if (checkShell) {
+                PackageManagerServiceUtils.enforceShellRestriction(
+                    mInjector.getUserManagerInternal(),
+                        UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+            }
+            final int callingUserId = UserHandle.getUserId(callingUid);
+            if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                    /*requirePermissionWhenSameUser= */ false)) {
+                return;
+            }
+            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+            if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
+                    mContext,
+                    android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+                    PermissionChecker.PID_UNKNOWN,
+                    callingUid,
+                    getPackage(callingUid).getPackageName())
+                    == PermissionChecker.PERMISSION_GRANTED) {
+                return;
+            }
+            String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                    callingUid, userId, message, requireFullPermission, isSameProfileGroup);
+            Slog.w(TAG, errorMessage);
+            throw new SecurityException(errorMessage);
+        }
+
+        /**
+         * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+         * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller.
+         *
+         * @param checkShell whether to prevent shell from access if there's a debugging restriction
+         * @param message the message to log on security exception
+         */
+        public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, false,
+                    message);
+        }
+
+        /**
+         * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+         * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller.
+         *
+         * @param checkShell whether to prevent shell from access if there's a debugging restriction
+         * @param requirePermissionWhenSameUser When {@code true}, still require the cross user
+         *                                      permission to be held even if the callingUid and
+         * userId
+         *                                      reference the same user.
+         * @param message the message to log on security exception
+         */
+        public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
+                boolean requireFullPermission, boolean checkShell,
+                boolean requirePermissionWhenSameUser, String message) {
+            if (userId < 0) {
+                throw new IllegalArgumentException("Invalid userId " + userId);
+            }
+            if (checkShell) {
+                PackageManagerServiceUtils.enforceShellRestriction(
+                    mInjector.getUserManagerInternal(),
+                        UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+            }
+            final int callingUserId = UserHandle.getUserId(callingUid);
+            if (hasCrossUserPermission(
+                    callingUid, callingUserId, userId, requireFullPermission,
+                    requirePermissionWhenSameUser)) {
+                return;
+            }
+            String errorMessage = buildInvalidCrossUserPermissionMessage(
+                    callingUid, userId, message, requireFullPermission);
+            Slog.w(TAG, errorMessage);
+            throw new SecurityException(errorMessage);
+        }
+
+    }
+
+    /**
+     * The live computer differs from the ComputerEngine in the methods that fetch data
+     * from PackageManagerService.
+     **/
+    private static class ComputerEngineLive extends ComputerEngine {
+        ComputerEngineLive(Snapshot args) {
+            super(args);
+        }
+        protected ComponentName resolveComponentName() {
+            return mService.mResolveComponentName;
+        }
+        protected ActivityInfo instantAppInstallerActivity() {
+            return mService.mInstantAppInstallerActivity;
+        }
+        protected ApplicationInfo androidApplication() {
+            return mService.mAndroidApplication;
+        }
+    }
+
+    /**
+     * This subclass is the external interface to the live computer.  For each
+     * interface, it takes the PM lock and then delegates to the live
+     * computer engine.  This is required because there are no locks taken in
+     * the engine itself.
+     */
+    private static class ComputerLocked extends ComputerEngine {
+        private final Object mLock;
+
+        ComputerLocked(Snapshot args) {
+            super(args);
+            mLock = mService.mLock;
+        }
+
+        protected ComponentName resolveComponentName() {
+            return mService.mResolveComponentName;
+        }
+        protected ActivityInfo instantAppInstallerActivity() {
+            return mService.mInstantAppInstallerActivity;
+        }
+        protected ApplicationInfo androidApplication() {
+            return mService.mAndroidApplication;
+        }
+
+        public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
+                String resolvedType, int flags, int userId, int callingUid,
+                String instantAppPkgName) {
+            synchronized (mLock) {
+                return super.queryIntentServicesInternalBody(intent, resolvedType, flags, userId,
+                        callingUid, instantAppPkgName);
+            }
+        }
+        public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent,
+                String resolvedType, int flags, int filterCallingUid, int userId,
+                boolean resolveForStart, boolean allowDynamicSplits, String pkgName,
+                String instantAppPkgName) {
+            synchronized (mLock) {
+                return super.queryIntentActivitiesInternalBody(intent, resolvedType, flags,
+                        filterCallingUid, userId, resolveForStart, allowDynamicSplits, pkgName,
+                        instantAppPkgName);
+            }
+        }
+        public ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,
+                int filterCallingUid, int userId) {
+            synchronized (mLock) {
+                return super.getActivityInfoInternalBody(component, flags, filterCallingUid,
+                        userId);
+            }
+        }
+        public AndroidPackage getPackage(String packageName) {
+            synchronized (mLock) {
+                return super.getPackage(packageName);
+            }
+        }
+        public AndroidPackage getPackage(int uid) {
+            synchronized (mLock) {
+                return super.getPackage(uid);
+            }
+        }
+        public ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags,
+                int filterCallingUid, int userId) {
+            synchronized (mLock) {
+                return super.getApplicationInfoInternalBody(packageName, flags, filterCallingUid,
+                        userId);
+            }
+        }
+        public ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
+            Intent intent, int matchFlags, List<ResolveInfo> candidates,
+            CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
+            synchronized (mLock) {
+                return super.filterCandidatesWithDomainPreferredActivitiesLPrBody(intent,
+                        matchFlags, candidates, xpDomainInfo, userId, debug);
+            }
+        }
+        public PackageInfo getPackageInfoInternalBody(String packageName, long versionCode,
+                int flags, int filterCallingUid, int userId) {
+            synchronized (mLock) {
+                return super.getPackageInfoInternalBody(packageName, versionCode, flags,
+                        filterCallingUid, userId);
+            }
+        }
+        public PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
+            synchronized (mLock) {
+                return super.getPackageSettingInternal(packageName, callingUid);
+            }
+        }
+        public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
+                int callingUid) {
+            synchronized (mLock) {
+                return super.getInstalledPackagesBody(flags, userId, callingUid);
+            }
+        }
+        public ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId,
+                int callingUid) {
+            synchronized (mLock) {
+                return super.getServiceInfoBody(component, flags, userId, callingUid);
+            }
+        }
+        public String getInstantAppPackageName(int callingUid) {
+            synchronized (mLock) {
+                return super.getInstantAppPackageName(callingUid);
+            }
+        }
+        public String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
+                boolean isCallerInstantApp) {
+            synchronized (mLock) {
+                return super.getPackagesForUidInternalBody(callingUid, userId, appId,
+                        isCallerInstantApp);
+            }
+        }
+        public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId,
+                int callingUid) {
+            synchronized (mLock) {
+                return super.isInstantAppInternalBody(packageName, userId, callingUid);
+            }
+        }
+        public boolean isInstantAppResolutionAllowedBody(Intent intent,
+                List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) {
+            synchronized (mLock) {
+                return super.isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId,
+                        skipPackageCheck);
+            }
+        }
+        public int getPackageUidInternal(String packageName, int flags, int userId,
+                int callingUid) {
+            synchronized (mLock) {
+                return super.getPackageUidInternal(packageName, flags, userId, callingUid);
+            }
+        }
+    }
+
+
+    // Compute read-only functions, based on live data.
+    private final Computer mLiveComputer;
+    // A lock-free cache for frequently called functions.
+    private volatile Computer mSnapshotComputer;
+    // If true, the cached computer object is invalid (the cache is stale).
+    // The attribute is static since it may be set from outside classes.
+    private static volatile boolean sSnapshotInvalid = true;
+     // If true, the cache is corked.  Do not create a new cache but continue to use the
+    // existing one.  This throttles cache creation during periods of churn in Package
+    // Manager.
+    private static volatile boolean sSnapshotCorked = false;
+
+    // A counter of all queries that hit the cache.
+    private AtomicInteger mSnapshotHits = new AtomicInteger(0);
+
+    // The number of queries at the last miss.  This is updated when the cache is rebuilt
+    // (guarded by mLock) and is used to report the hit run-length.
+    @GuardedBy("mLock")
+    private int mSnapshotRebuilt = 0;
+
+    // The snapshot disable/enable switch.  An image with the flag set true uses snapshots
+    // and an image with the flag set false does not use snapshots.
+    private static final boolean SNAPSHOT_ENABLED = true;
+
+    /**
+     * Return the live or cached computer.  The method will rebuild the
+     * cached computer if necessary.
+     */
+    private Computer computer(boolean live) {
+        if (live || !SNAPSHOT_ENABLED) {
+            return mLiveComputer;
+        } else {
+            int hits = 0;
+            if (TRACE_CACHES) {
+                hits = mSnapshotHits.incrementAndGet();
+            }
+            Computer c = mSnapshotComputer;
+            if ((sSnapshotInvalid || (c == null)) && !sSnapshotCorked) {
+                synchronized (mLock) {
+                    // Rebuild the computer if it is invalid and if the cache is not
+                    // corked.  The lock is taken inside the rebuild method.  Note that
+                    // the cache might be invalidated as it is rebuilt.  However, the
+                    // cache is still consistent and is current as of the time this
+                    // function is entered.
+                    if (sSnapshotInvalid) {
+                        rebuildSnapshot(hits);
+                    }
+                    // Guaranteed to be non-null
+                    c = mSnapshotComputer;
+                }
+            }
+            return c;
+        }
+    }
+
+    /**
+     * Return the live computer if the thread holds the lock, and the cached
+     * computer otehrwise.  This method is for functions that are unsure
+     * which computer to use.
+     **/
+    private Computer computer() {
+        return computer(Thread.holdsLock(mLock));
+    }
+
+    /**
+     * Rebuild the cached computer.
+     */
+    @GuardedBy("mLock")
+    private void rebuildSnapshot(int hits) {
+        mSnapshotComputer = null;
+        sSnapshotInvalid = false;
+        final Snapshot args = new Snapshot(Snapshot.SNAPPED);
+        mSnapshotComputer = new ComputerEngine(args);
+
+        // Still guarded by mLock
+        final int run = hits - mSnapshotRebuilt;
+        mSnapshotRebuilt = hits;
+        if (TRACE_CACHES) {
+            Log.w(TAG, "computer: rebuild after " + run + " hits");
+        }
+    }
+
+    /**
+     * Create a live computer
+     */
+    private ComputerLocked liveComputer() {
+        return new ComputerLocked(new Snapshot(Snapshot.LIVE));
+    }
+
+    /**
+     * This method is called when the state of PackageManagerService changes so as to
+     * invalidate the current snapshot.
+     * @param what The {@link Watchable} that reported the change
+     * @hide
+     */
+    public static void onChange(@Nullable Watchable what) {
+        if (TRACE_CACHES) {
+            Log.e(TAG, "computer: onChange(" + what + ")");
+        }
+        sSnapshotInvalid = true;
+    }
+
+    /**
+     * Report a locally-detected change to observers.  The <what> parameter is left null,
+     * but it signifies that the change was detected by PackageManagerService itself.
+     */
+    private static void onChanged() {
+        onChange(null);
     }
 
     class PackageHandler extends Handler {
@@ -3093,6 +6021,25 @@
         mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
         mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
         mResolveComponentName = testParams.resolveComponentName;
+
+        // Create the computer as soon as the state objects have been installed.  The
+        // cached computer is the same as the live computer until the end of the
+        // constructor, at which time the invalidation method updates it.  The cache is
+        // corked initially to ensure a cached computer is not built until the end of the
+        // constructor.
+        sSnapshotCorked = true;
+        mLiveComputer = liveComputer();
+        mSnapshotComputer = mLiveComputer;
+
+        // Link up the watchers
+        mPackages.registerObserver(mWatcher);
+        mSharedLibraries.registerObserver(mWatcher);
+        mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
+        mInstrumentation.registerObserver(mWatcher);
+        mWebInstantAppsDisabled.registerObserver(mWatcher);
+        mAppsFilter.registerObserver(mWatcher);
+        Watchable.verifyWatchedAttributes(this, mWatcher);
+
         mPackages.putAll(testParams.packages);
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
         mSdkVersion = testParams.sdkVersion;
@@ -3102,6 +6049,9 @@
         mIsEngBuild = testParams.isEngBuild;
         mIsUserDebugBuild = testParams.isUserDebugBuild;
         mIncrementalVersion = testParams.incrementalVersion;
+
+        invalidatePackageInfoCache();
+        sSnapshotCorked = false;
     }
 
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest,
@@ -3229,6 +6179,8 @@
             }
         }
 
+        mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager);
+
         mDirsToScanAsSystem = new ArrayList<>();
         mDirsToScanAsSystem.addAll(injector.getSystemPartitions());
         mDirsToScanAsSystem.addAll(scanPartitions);
@@ -3237,6 +6189,24 @@
         mAppInstallDir = new File(Environment.getDataDirectory(), "app");
         mAppLib32InstallDir = getAppLib32InstallDir();
 
+        // Link up the watchers
+        mPackages.registerObserver(mWatcher);
+        mSharedLibraries.registerObserver(mWatcher);
+        mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
+        mInstrumentation.registerObserver(mWatcher);
+        mWebInstantAppsDisabled.registerObserver(mWatcher);
+        mAppsFilter.registerObserver(mWatcher);
+        Watchable.verifyWatchedAttributes(this, mWatcher);
+
+        // Create the computer as soon as the state objects have been installed.  The
+        // cached computer is the same as the live computer until the end of the
+        // constructor, at which time the invalidation method updates it.  The cache is
+        // corked initially to ensure a cached computer is not built until the end of the
+        // constructor.
+        sSnapshotCorked = true;
+        mLiveComputer = liveComputer();
+        mSnapshotComputer = mLiveComputer;
+
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
         // writer
@@ -3247,7 +6217,6 @@
             mHandler = new PackageHandler(handlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-            mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager);
 
             ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
                     = systemConfig.getSharedLibraries();
@@ -4616,96 +7585,11 @@
      * </ol>
      */
     private boolean canViewInstantApps(int callingUid, int userId) {
-        if (callingUid < Process.FIRST_APPLICATION_UID) {
-            return true;
-        }
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_INSTANT_APPS) == PERMISSION_GRANTED) {
-            return true;
-        }
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.VIEW_INSTANT_APPS) == PERMISSION_GRANTED) {
-            final ComponentName homeComponent = getDefaultHomeActivity(userId);
-            if (homeComponent != null
-                    && isCallerSameApp(homeComponent.getPackageName(), callingUid)) {
-                return true;
-            }
-            // TODO(b/122900055) Change/Remove this and replace with new permission role.
-            if (mAppPredictionServicePackage != null
-                    && isCallerSameApp(mAppPredictionServicePackage, callingUid)) {
-                return true;
-            }
-        }
-        return false;
+        return computer(true).canViewInstantApps(callingUid, userId);
     }
 
     private PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        if (ps == null) {
-            return null;
-        }
-        final int callingUid = Binder.getCallingUid();
-        // Filter out ephemeral app metadata:
-        //   * The system/shell/root can see metadata for any app
-        //   * An installed app can see metadata for 1) other installed apps
-        //     and 2) ephemeral apps that have explicitly interacted with it
-        //   * Ephemeral apps can only see their own data and exposed installed apps
-        //   * Holding a signature permission allows seeing instant apps
-        if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
-            return null;
-        }
-
-        if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0
-                && ps.isSystem()) {
-            flags |= MATCH_ANY_USER;
-        }
-
-        final PackageUserState state = ps.readUserState(userId);
-        AndroidPackage p = ps.pkg;
-        if (p != null) {
-            // Compute GIDs only if requested
-            final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY
-                    : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId));
-            // Compute granted permissions only if package has requested permissions
-            final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions())
-                    ? Collections.emptySet()
-                    : mPermissionManager.getGrantedPermissions(ps.name, userId);
-
-            PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags,
-                    ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps);
-
-            if (packageInfo == null) {
-                return null;
-            }
-
-            packageInfo.packageName = packageInfo.applicationInfo.packageName =
-                    resolveExternalPackageNameLPr(p);
-
-            return packageInfo;
-        } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 && state.isAvailable(flags)) {
-            PackageInfo pi = new PackageInfo();
-            pi.packageName = ps.name;
-            pi.setLongVersionCode(ps.versionCode);
-            pi.sharedUserId = (ps.sharedUser != null) ? ps.sharedUser.name : null;
-            pi.firstInstallTime = ps.firstInstallTime;
-            pi.lastUpdateTime = ps.lastUpdateTime;
-
-            ApplicationInfo ai = new ApplicationInfo();
-            ai.packageName = ps.name;
-            ai.uid = UserHandle.getUid(userId, ps.appId);
-            ai.primaryCpuAbi = ps.primaryCpuAbiString;
-            ai.secondaryCpuAbi = ps.secondaryCpuAbiString;
-            ai.setVersionCode(ps.versionCode);
-            ai.flags = ps.pkgFlags;
-            ai.privateFlags = ps.pkgPrivateFlags;
-            pi.applicationInfo = PackageParser.generateApplicationInfo(ai, flags, state, userId);
-
-            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for ["
-                    + ps.name + "]. Provides a minimum info.");
-            return pi;
-        } else {
-            return null;
-        }
+        return computer(true).generatePackageInfo(ps, flags, userId);
     }
 
     @Override
@@ -4770,8 +7654,8 @@
 
     @Override
     public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
-        return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
-                flags, Binder.getCallingUid(), userId);
+        // SNAPSHOT
+        return computer(false).getPackageInfo(packageName, flags, userId);
     }
 
     @Override
@@ -4784,128 +7668,29 @@
     /**
      * Important: The provided filterCallingUid is used exclusively to filter out packages
      * that can be seen based on user state. It's typically the original caller uid prior
-     * to clearing. Because it can only be provided by trusted code, it's value can be
+     * to clearing. Because it can only be provided by trusted code, its value can be
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     private PackageInfo getPackageInfoInternal(String packageName, long versionCode,
             int flags, int filterCallingUid, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        flags = updateFlagsForPackage(flags, userId);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                false /* requireFullPermission */, false /* checkShell */, "get package info");
-
-        return getPackageInfoInternalBody(packageName, versionCode, flags, filterCallingUid,
-                userId);
+        return computer(true).getPackageInfoInternal(packageName, versionCode,
+                flags, filterCallingUid, userId);
     }
 
     private PackageInfo getPackageInfoInternalBody(String packageName, long versionCode,
             int flags, int filterCallingUid, int userId) {
-        // reader
-        synchronized (mLock) {
-            // Normalize package name to handle renamed packages and static libs
-            packageName = resolveInternalPackageNameLPr(packageName, versionCode);
-
-            final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
-            if (matchFactoryOnly) {
-                // Instant app filtering for APEX modules is ignored
-                if ((flags & MATCH_APEX) != 0) {
-                    return mApexManager.getPackageInfo(packageName,
-                            ApexManager.MATCH_FACTORY_PACKAGE);
-                }
-                final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
-                if (ps != null) {
-                    if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
-                        return null;
-                    }
-                    if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
-                        return null;
-                    }
-                    return generatePackageInfo(ps, flags, userId);
-                }
-            }
-
-            AndroidPackage p = mPackages.get(packageName);
-            if (matchFactoryOnly && p != null && !p.isSystem()) {
-                return null;
-            }
-            if (DEBUG_PACKAGE_INFO)
-                Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
-            if (p != null) {
-                final PackageSetting ps = getPackageSetting(p.getPackageName());
-                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
-                    return null;
-                }
-                if (ps != null && shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
-                    return null;
-                }
-
-                return generatePackageInfo(ps, flags, userId);
-            }
-            if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
-                final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps == null) return null;
-                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
-                    return null;
-                }
-                if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
-                    return null;
-                }
-                return generatePackageInfo(ps, flags, userId);
-            }
-            if ((flags & MATCH_APEX) != 0) {
-                return mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE);
-            }
-        }
-        return null;
+        return computer(true).getPackageInfoInternalBody(packageName, versionCode,
+                flags, filterCallingUid, userId);
     }
 
     private boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) {
-        if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) {
-            return true;
-        }
-        if (isComponentVisibleToInstantApp(component, TYPE_SERVICE)) {
-            return true;
-        }
-        if (isComponentVisibleToInstantApp(component, TYPE_PROVIDER)) {
-            return true;
-        }
-        return false;
+        return computer(true).isComponentVisibleToInstantApp(component);
     }
 
     private boolean isComponentVisibleToInstantApp(
             @Nullable ComponentName component, @ComponentType int type) {
-        if (type == TYPE_ACTIVITY) {
-            final ParsedActivity activity = mComponentResolver.getActivity(component);
-            if (activity == null) {
-                return false;
-            }
-            final boolean visibleToInstantApp =
-                    (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-            final boolean explicitlyVisibleToInstantApp =
-                    (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
-            return visibleToInstantApp && explicitlyVisibleToInstantApp;
-        } else if (type == TYPE_RECEIVER) {
-            final ParsedActivity activity = mComponentResolver.getReceiver(component);
-            if (activity == null) {
-                return false;
-            }
-            final boolean visibleToInstantApp =
-                    (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-            final boolean explicitlyVisibleToInstantApp =
-                    (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
-            return visibleToInstantApp && !explicitlyVisibleToInstantApp;
-        } else if (type == TYPE_SERVICE) {
-            final ParsedService service = mComponentResolver.getService(component);
-            return service != null
-                    && (service.getFlags() & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-        } else if (type == TYPE_PROVIDER) {
-            final ParsedProvider provider = mComponentResolver.getProvider(component);
-            return provider != null
-                    && (provider.getFlags() & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-        } else if (type == TYPE_UNKNOWN) {
-            return isComponentVisibleToInstantApp(component);
-        }
-        return false;
+        return computer(true).isComponentVisibleToInstantApp(
+            component, type);
     }
 
     /**
@@ -4919,58 +7704,8 @@
     @GuardedBy("mLock")
     private boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid,
             @Nullable ComponentName component, @ComponentType int componentType, int userId) {
-        // if we're in an isolated process, get the real calling UID
-        if (Process.isIsolated(callingUid)) {
-            callingUid = mIsolatedOwners.get(callingUid);
-        }
-        final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        final boolean callerIsInstantApp = instantAppPkgName != null;
-        if (ps == null) {
-            if (callerIsInstantApp) {
-                // pretend the application exists, but, needs to be filtered
-                return true;
-            }
-            return false;
-        }
-        // if the target and caller are the same application, don't filter
-        if (isCallerSameApp(ps.name, callingUid)) {
-            return false;
-        }
-        if (callerIsInstantApp) {
-            // both caller and target are both instant, but, different applications, filter
-            if (ps.getInstantApp(userId)) {
-                return true;
-            }
-            // request for a specific component; if it hasn't been explicitly exposed through
-            // property or instrumentation target, filter
-            if (component != null) {
-                final ParsedInstrumentation instrumentation =
-                        mInstrumentation.get(component);
-                if (instrumentation != null
-                        && isCallerSameApp(instrumentation.getTargetPackage(), callingUid)) {
-                    return false;
-                }
-                return !isComponentVisibleToInstantApp(component, componentType);
-            }
-            // request for application; if no components have been explicitly exposed, filter
-            return !ps.pkg.isVisibleToInstantApps();
-        }
-        if (ps.getInstantApp(userId)) {
-            // caller can see all components of all instant applications, don't filter
-            if (canViewInstantApps(callingUid, userId)) {
-                return false;
-            }
-            // request for a specific instant application component, filter
-            if (component != null) {
-                return true;
-            }
-            // request for an instant application; if the caller hasn't been granted access, filter
-            return !mInstantAppRegistry.isInstantAccessGranted(
-                    userId, UserHandle.getAppId(callingUid), ps.appId);
-        }
-        int appId = UserHandle.getAppId(callingUid);
-        final SettingBase callingPs = mSettings.getSettingLPr(appId);
-        return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId);
+        return computer(true).shouldFilterApplicationLocked(ps, callingUid,
+                component, componentType, userId);
     }
 
     /**
@@ -4979,63 +7714,15 @@
     @GuardedBy("mLock")
     private boolean shouldFilterApplicationLocked(
             @Nullable PackageSetting ps, int callingUid, int userId) {
-        return shouldFilterApplicationLocked(ps, callingUid, null, TYPE_UNKNOWN, userId);
+        return computer(true).shouldFilterApplicationLocked(
+            ps, callingUid, userId);
     }
 
     @GuardedBy("mLock")
     private boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId,
             int flags) {
-        // Callers can access only the libs they depend on, otherwise they need to explicitly
-        // ask for the shared libraries given the caller is allowed to access all static libs.
-        if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) {
-            // System/shell/root get to see all static libs
-            final int appId = UserHandle.getAppId(uid);
-            if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
-                    || appId == Process.ROOT_UID) {
-                return false;
-            }
-            // Installer gets to see all static libs.
-            if (PackageManager.PERMISSION_GRANTED
-                    == checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)) {
-                return false;
-            }
-        }
-
-        // No package means no static lib as it is always on internal storage
-        if (ps == null || ps.pkg == null || !ps.pkg.isStaticSharedLibrary()) {
-            return false;
-        }
-
-        final SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr(
-                ps.pkg.getStaticSharedLibName(), ps.pkg.getStaticSharedLibVersion());
-        if (libraryInfo == null) {
-            return false;
-        }
-
-        final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
-        final String[] uidPackageNames = getPackagesForUid(resolvedUid);
-        if (uidPackageNames == null) {
-            return true;
-        }
-
-        for (String uidPackageName : uidPackageNames) {
-            if (ps.name.equals(uidPackageName)) {
-                return false;
-            }
-            PackageSetting uidPs = mSettings.getPackageLPr(uidPackageName);
-            if (uidPs != null) {
-                final int index = ArrayUtils.indexOf(uidPs.usesStaticLibraries,
-                        libraryInfo.getName());
-                if (index < 0) {
-                    continue;
-                }
-                if (uidPs.pkg.getUsesStaticLibrariesVersions()[index]
-                        == libraryInfo.getLongVersion()) {
-                    return false;
-                }
-            }
-        }
-        return true;
+        return computer(true).filterSharedLibPackageLPr(ps, uid, userId,
+                flags);
     }
 
     @Override
@@ -5105,26 +7792,7 @@
     }
 
     private int getPackageUidInternal(String packageName, int flags, int userId, int callingUid) {
-        // reader
-        synchronized (mLock) {
-            final AndroidPackage p = mPackages.get(packageName);
-            if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) {
-                PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
-                if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    return -1;
-                }
-                return UserHandle.getUid(userId, p.getUid());
-            }
-            if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
-                final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null && ps.isMatch(flags)
-                        && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    return UserHandle.getUid(userId, ps.appId);
-                }
-            }
-        }
-
-        return -1;
+        return computer(true).getPackageUidInternal(packageName, flags, userId, callingUid);
     }
 
     @Override
@@ -5171,111 +7839,32 @@
     @GuardedBy("mLock")
     private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
             int filterCallingUid, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        PackageSetting ps = mSettings.getPackageLPr(packageName);
-        if (ps != null) {
-            if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
-                return null;
-            }
-            if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
-                return null;
-            }
-            if (ps.pkg == null) {
-                final PackageInfo pInfo = generatePackageInfo(ps, flags, userId);
-                if (pInfo != null) {
-                    return pInfo.applicationInfo;
-                }
-                return null;
-            }
-            ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(ps.pkg, flags,
-                    ps.readUserState(userId), userId, ps);
-            if (ai != null) {
-                ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
-            }
-            return ai;
-        }
-        return null;
+        return computer(true).generateApplicationInfoFromSettingsLPw(packageName, flags,
+                filterCallingUid, userId);
     }
 
     @Override
     public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
-        return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
+        // SNAPSHOT
+        return computer(false).getApplicationInfo(packageName, flags, userId);
     }
 
     /**
      * Important: The provided filterCallingUid is used exclusively to filter out applications
      * that can be seen based on user state. It's typically the original caller uid prior
-     * to clearing. Because it can only be provided by trusted code, it's value can be
+     * to clearing. Because it can only be provided by trusted code, its value can be
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
             int filterCallingUid, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        flags = updateFlagsForApplication(flags, userId);
-
-        if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
-            enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                    false /* requireFullPermission */, false /* checkShell */,
-                    "get application info");
-        }
-
-        return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId);
+        return computer(true).getApplicationInfoInternal(packageName, flags,
+                filterCallingUid, userId);
     }
 
     private ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags,
             int filterCallingUid, int userId) {
-        // writer
-        synchronized (mLock) {
-            // Normalize package name to handle renamed packages and static libs
-            packageName = resolveInternalPackageNameLPr(packageName,
-                    PackageManager.VERSION_CODE_HIGHEST);
-
-            AndroidPackage p = mPackages.get(packageName);
-            if (DEBUG_PACKAGE_INFO) Log.v(
-                    TAG, "getApplicationInfo " + packageName
-                    + ": " + p);
-            if (p != null) {
-                PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps == null) return null;
-                if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
-                    return null;
-                }
-                if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) {
-                    return null;
-                }
-                // Note: isEnabledLP() does not apply here - always return info
-                ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(
-                        p, flags, ps.readUserState(userId), userId, ps);
-                if (ai != null) {
-                    ai.packageName = resolveExternalPackageNameLPr(p);
-                }
-                return ai;
-            }
-            if ((flags & PackageManager.MATCH_APEX) != 0) {
-                // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo
-                // returned from getApplicationInfo, but for APEX packages difference shouldn't be
-                // very big.
-                // TODO(b/155328545): generate proper application info for APEXes as well.
-                int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE;
-                if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-                    apexFlags = ApexManager.MATCH_FACTORY_PACKAGE;
-                }
-                final PackageInfo pi = mApexManager.getPackageInfo(packageName, apexFlags);
-                if (pi == null) {
-                    return null;
-                }
-                return pi.applicationInfo;
-            }
-            if ("android".equals(packageName)||"system".equals(packageName)) {
-                return mAndroidApplication;
-            }
-            if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
-                // Already generates the external package name
-                return generateApplicationInfoFromSettingsLPw(packageName,
-                        flags, filterCallingUid, userId);
-            }
-        }
-        return null;
+        return computer(true).getApplicationInfoInternalBody(packageName, flags,
+                filterCallingUid, userId);
     }
 
     @GuardedBy("mLock")
@@ -5507,56 +8096,28 @@
      * Update given flags based on encryption status of current user.
      */
     private int updateFlags(int flags, int userId) {
-        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) {
-            // Caller expressed an explicit opinion about what encryption
-            // aware/unaware components they want to see, so fall through and
-            // give them what they want
-        } else {
-            // Caller expressed no opinion, so match based on user state
-            if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
-                flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
-            } else {
-                flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE;
-            }
-        }
-        return flags;
+        return computer(true).updateFlags(flags, userId);
     }
 
     /**
      * Update given flags when being used to request {@link PackageInfo}.
      */
     private int updateFlagsForPackage(int flags, int userId) {
-        final boolean isCallerSystemUser = UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM;
-        if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
-            // require the permission to be held; the calling uid and given user id referring
-            // to the same user is not sufficient
-            enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
-                    !isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId),
-                    "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
-        } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser
-                && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) {
-            // If the caller wants all packages and has a restricted profile associated with it,
-            // then match all users. This is to make sure that launchers that need to access work
-            // profile apps don't start breaking. TODO: Remove this hack when launchers stop using
-            // MATCH_UNINSTALLED_PACKAGES to query apps in other profiles. b/31000380
-            flags |= PackageManager.MATCH_ANY_USER;
-        }
-        return updateFlags(flags, userId);
+        return computer(true).updateFlagsForPackage(flags, userId);
     }
 
     /**
      * Update given flags when being used to request {@link ApplicationInfo}.
      */
     private int updateFlagsForApplication(int flags, int userId) {
-        return updateFlagsForPackage(flags, userId);
+        return computer(true).updateFlagsForApplication(flags, userId);
     }
 
     /**
      * Update given flags when being used to request {@link ComponentInfo}.
      */
     private int updateFlagsForComponent(int flags, int userId) {
-        return updateFlags(flags, userId);
+        return computer(true).updateFlagsForComponent(flags, userId);
     }
 
     /**
@@ -5584,38 +8145,18 @@
      * action and a {@code android.intent.category.BROWSABLE} category</li>
      * </ul>
      */
-    int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
-            boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
-        return updateFlagsForResolve(flags, userId, callingUid,
-                wantInstantApps, false /*onlyExposedExplicitly*/,
-                isImplicitImageCaptureIntentAndNotSetByDpc);
+    private int updateFlagsForResolve(int flags, int userId, int callingUid,
+            boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
+        return computer(true).updateFlagsForResolve(flags, userId, callingUid,
+                wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc);
     }
 
-    int updateFlagsForResolve(int flags, int userId, int callingUid,
+    private int updateFlagsForResolve(int flags, int userId, int callingUid,
             boolean wantInstantApps, boolean onlyExposedExplicitly,
             boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
-        // Safe mode means we shouldn't match any third-party components
-        if (mSafeMode || isImplicitImageCaptureIntentAndNotSetByDpc) {
-            flags |= PackageManager.MATCH_SYSTEM_ONLY;
-        }
-        if (getInstantAppPackageName(callingUid) != null) {
-            // But, ephemeral apps see both ephemeral and exposed, non-ephemeral components
-            if (onlyExposedExplicitly) {
-                flags |= PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY;
-            }
-            flags |= PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY;
-            flags |= PackageManager.MATCH_INSTANT;
-        } else {
-            final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0;
-            final boolean allowMatchInstant = wantInstantApps
-                    || (wantMatchInstant && canViewInstantApps(callingUid, userId));
-            flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY
-                    | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY);
-            if (!allowMatchInstant) {
-                flags &= ~PackageManager.MATCH_INSTANT;
-            }
-        }
-        return updateFlagsForComponent(flags, userId);
+        return computer(true).updateFlagsForResolve(flags, userId, callingUid,
+                wantInstantApps, onlyExposedExplicitly,
+                isImplicitImageCaptureIntentAndNotSetByDpc);
     }
 
     @Override
@@ -5637,69 +8178,30 @@
 
     @Override
     public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
-        return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
+        // SNAPSHOT
+        return computer(false).getActivityInfo(component, flags, userId);
     }
 
     /**
      * Important: The provided filterCallingUid is used exclusively to filter out activities
      * that can be seen based on user state. It's typically the original caller uid prior
-     * to clearing. Because it can only be provided by trusted code, it's value can be
+     * to clearing. Because it can only be provided by trusted code, its value can be
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     private ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
             int filterCallingUid, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        flags = updateFlagsForComponent(flags, userId);
-
-        if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
-            enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                    false /* requireFullPermission */, false /* checkShell */, "get activity info");
-        }
-
-        return getActivityInfoInternalBody(component, flags, filterCallingUid, userId);
+        return computer(true).getActivityInfoInternal(component, flags,
+                filterCallingUid, userId);
     }
 
     private ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,
             int filterCallingUid, int userId) {
-        synchronized (mLock) {
-            ParsedActivity a = mComponentResolver.getActivity(component);
-
-            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
-
-            AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
-            if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
-                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
-                if (ps == null) return null;
-                if (shouldFilterApplicationLocked(
-                        ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
-                    return null;
-                }
-                return PackageInfoUtils.generateActivityInfo(pkg,
-                        a, flags, ps.readUserState(userId), userId, ps);
-            }
-            if (mResolveComponentName.equals(component)) {
-                return PackageParser.generateActivityInfo(
-                        mResolveActivity, flags, new PackageUserState(), userId);
-            }
-        }
-        return null;
+        return computer(true).getActivityInfoInternalBody(component, flags,
+                filterCallingUid, userId);
     }
 
     private boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId) {
-        if (!mInjector.getLocalService(ActivityTaskManagerInternal.class)
-                .isCallerRecents(callingUid)) {
-            return false;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final int callingUserId = UserHandle.getUserId(callingUid);
-            if (ActivityManager.getCurrentUser() != callingUserId) {
-                return false;
-            }
-            return mUserManager.isSameProfileGroup(callingUserId, targetUserId);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return computer(true).isRecentsAccessingChildProfiles(callingUid, targetUserId);
     }
 
     @Override
@@ -5962,37 +8464,14 @@
 
     @Override
     public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
-        if (!mUserManager.exists(userId)) return null;
-        final int callingUid = Binder.getCallingUid();
-        flags = updateFlagsForComponent(flags, userId);
-        enforceCrossUserOrProfilePermission(callingUid, userId, false /* requireFullPermission */,
-                false /* checkShell */, "get service info");
-        return getServiceInfoBody(component, flags, userId, callingUid);
+        // SNAPSHOT
+        return computer(false).getServiceInfo(component, flags, userId);
     }
 
     private ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId,
                                              int callingUid) {
-        synchronized (mLock) {
-            ParsedService s = mComponentResolver.getService(component);
-            if (DEBUG_PACKAGE_INFO) Log.v(
-                    TAG, "getServiceInfo " + component + ": " + s);
-            if (s == null) {
-                return null;
-            }
-
-            AndroidPackage pkg = mPackages.get(s.getPackageName());
-            if (mSettings.isEnabledAndMatchLPr(pkg, s, flags, userId)) {
-                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
-                if (ps == null) return null;
-                if (shouldFilterApplicationLocked(
-                        ps, callingUid, component, TYPE_SERVICE, userId)) {
-                    return null;
-                }
-                return PackageInfoUtils.generateServiceInfo(pkg,
-                        s, flags, ps.readUserState(userId), userId, ps);
-            }
-        }
-        return null;
+        return computer(true).getServiceInfoBody(component, flags, userId,
+                callingUid);
     }
 
     @Override
@@ -6203,7 +8682,8 @@
     // NOTE: Can't remove without a major refactor. Keep around for now.
     @Override
     public int checkUidPermission(String permName, int uid) {
-        return mPermissionManager.checkUidPermission(uid, permName);
+        // SNAPSHOT
+        return computer(false).checkUidPermission(permName, uid);
     }
 
     @Override
@@ -6518,45 +8998,18 @@
      */
     @Override
     public String[] getPackagesForUid(int uid) {
-        return getPackagesForUidInternal(uid, Binder.getCallingUid());
+        // SNAPSHOT
+        return computer(false).getPackagesForUid(uid);
     }
 
     private String[] getPackagesForUidInternal(int uid, int callingUid) {
-        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
-        final int userId = UserHandle.getUserId(uid);
-        final int appId = UserHandle.getAppId(uid);
-        return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
+        return computer(true).getPackagesForUidInternal(uid, callingUid);
     }
 
     private String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
                                                      boolean isCallerInstantApp) {
-        // reader
-        synchronized (mLock) {
-            final Object obj = mSettings.getSettingLPr(appId);
-            if (obj instanceof SharedUserSetting) {
-                if (isCallerInstantApp) {
-                    return null;
-                }
-                final SharedUserSetting sus = (SharedUserSetting) obj;
-                final int N = sus.packages.size();
-                String[] res = new String[N];
-                int i = 0;
-                for (int index = 0; index < N; index++) {
-                    final PackageSetting ps = sus.packages.valueAt(index);
-                    if (ps.getInstalled(userId)) {
-                        res[i++] = ps.name;
-                    }
-                }
-                return ArrayUtils.trimToSize(res, i);
-            } else if (obj instanceof PackageSetting) {
-                final PackageSetting ps = (PackageSetting) obj;
-                if (ps.getInstalled(userId)
-                        && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    return new String[]{ps.name};
-                }
-            }
-        }
-        return null;
+        return computer(true).getPackagesForUidInternalBody(callingUid, userId, appId,
+                isCallerInstantApp);
     }
 
     @Override
@@ -6844,45 +9297,15 @@
      * Returns whether or not instant apps have been disabled remotely.
      */
     private boolean areWebInstantAppsDisabled(int userId) {
-        return mWebInstantAppsDisabled.get(userId);
+        return computer(true).areWebInstantAppsDisabled(userId);
     }
 
     private boolean isInstantAppResolutionAllowed(
             Intent intent, List<ResolveInfo> resolvedActivities, int userId,
             boolean skipPackageCheck) {
-        if (mInstantAppResolverConnection == null) {
-            return false;
-        }
-        if (mInstantAppInstallerActivity == null) {
-            return false;
-        }
-        if (intent.getComponent() != null) {
-            return false;
-        }
-        if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
-            return false;
-        }
-        if (!skipPackageCheck && intent.getPackage() != null) {
-            return false;
-        }
-        if (!intent.isWebIntent()) {
-            // for non web intents, we should not resolve externally if an app already exists to
-            // handle it or if the caller didn't explicitly request it.
-            if ((resolvedActivities != null && resolvedActivities.size() != 0)
-                    || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) {
-                return false;
-            }
-        } else {
-            if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) {
-                return false;
-            } else if (areWebInstantAppsDisabled(userId)) {
-                return false;
-            }
-        }
-        // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
-        // Or if there's already an ephemeral app installed that handles the action
-        return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId,
-                                                   skipPackageCheck);
+        return computer(true).isInstantAppResolutionAllowed(
+            intent, resolvedActivities, userId,
+            skipPackageCheck);
     }
 
     // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
@@ -6890,39 +9313,9 @@
     private boolean isInstantAppResolutionAllowedBody(
             Intent intent, List<ResolveInfo> resolvedActivities, int userId,
             boolean skipPackageCheck) {
-        synchronized (mLock) {
-            final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
-            for (int n = 0; n < count; n++) {
-                final ResolveInfo info = resolvedActivities.get(n);
-                final String packageName = info.activityInfo.packageName;
-                final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    // only check domain verification status if the app is not a browser
-                    if (!info.handleAllWebDataURI) {
-                        // Try to get the status from User settings first
-                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                        final int status = (int) (packedStatus >> 32);
-                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
-                            || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
-                            if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "DENY instant app;"
-                                    + " pkg: " + packageName + ", status: " + status);
-                            }
-                            return false;
-                        }
-                    }
-                    if (ps.getInstantApp(userId)) {
-                        if (DEBUG_INSTANT) {
-                            Slog.v(TAG, "DENY instant app installed;"
-                                    + " pkg: " + packageName);
-                        }
-                        return false;
-                    }
-                }
-            }
-        }
-        // We've exhausted all ways to deny ephemeral application; let the system look for them.
-        return true;
+        return computer(true).isInstantAppResolutionAllowedBody(
+            intent, resolvedActivities, userId,
+            skipPackageCheck);
     }
 
     private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -7059,25 +9452,14 @@
     @GuardedBy("mLock")
     private boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
             String resolvedType, int flags) {
-        return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm(
-                intent, userId, resolvedType, flags);
+        return computer(true).isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+                resolvedType, flags);
     }
 
     private boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId,
             String resolvedType, int flags) {
-        PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId);
-        //TODO(b/158003772): Remove double query
-        List<PersistentPreferredActivity> pprefs = ppir != null
-                ? ppir.queryIntent(intent, resolvedType,
-                (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
-                userId)
-                : new ArrayList<>();
-        for (PersistentPreferredActivity ppa : pprefs) {
-            if (ppa.mIsSetByDpm) {
-                return true;
-            }
-        }
-        return false;
+        return computer(true).isPersistentPreferredActivitySetByDpm(intent, userId,
+                resolvedType, flags);
     }
 
     @GuardedBy("mLock")
@@ -7401,21 +9783,13 @@
     }
 
     private UserInfo getProfileParent(int userId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mUserManager.getProfileParent(userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return computer(true).getProfileParent(userId);
     }
 
     private List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,
             String resolvedType, int userId) {
-        CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId);
-        if (resolver != null) {
-            return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);
-        }
-        return null;
+        return computer(true).getMatchingCrossProfileIntentFilters(intent,
+                resolvedType, userId);
     }
 
     @Override
@@ -7436,27 +9810,14 @@
      * instant, returns {@code null}.
      */
     private String getInstantAppPackageName(int callingUid) {
-        synchronized (mLock) {
-            // If the caller is an isolated app use the owner's uid for the lookup.
-            if (Process.isIsolated(callingUid)) {
-                callingUid = mIsolatedOwners.get(callingUid);
-            }
-            final int appId = UserHandle.getAppId(callingUid);
-            final Object obj = mSettings.getSettingLPr(appId);
-            if (obj instanceof PackageSetting) {
-                final PackageSetting ps = (PackageSetting) obj;
-                final boolean isInstantApp = ps.getInstantApp(UserHandle.getUserId(callingUid));
-                return isInstantApp ? ps.pkg.getPackageName() : null;
-            }
-        }
-        return null;
+        // SNAPSHOT
+        return computer(false).getInstantAppPackageName(callingUid);
     }
 
     private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, int userId) {
-        return queryIntentActivitiesInternal(
-                intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(),
-                userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
+        return computer(true).queryIntentActivitiesInternal(intent,
+                resolvedType, flags, userId);
     }
 
     // Collect the results of queryIntentActivitiesInternalBody into a single class
@@ -7479,306 +9840,27 @@
     private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) {
-        if (!mUserManager.exists(userId)) return Collections.emptyList();
-        final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                false /* requireFullPermission */, false /* checkShell */,
-                "query intent activities");
-        final String pkgName = intent.getPackage();
-        ComponentName comp = intent.getComponent();
-        if (comp == null) {
-            if (intent.getSelector() != null) {
-                intent = intent.getSelector();
-                comp = intent.getComponent();
-            }
-        }
-
-        flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
-                comp != null || pkgName != null /*onlyExposedExplicitly*/,
-                isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
-                        flags));
-        if (comp != null) {
-            final List<ResolveInfo> list = new ArrayList<>(1);
-            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
-            if (ai != null) {
-                // When specifying an explicit component, we prevent the activity from being
-                // used when either 1) the calling package is normal and the activity is within
-                // an ephemeral application or 2) the calling package is ephemeral and the
-                // activity is not visible to ephemeral applications.
-                final boolean matchInstantApp =
-                        (flags & PackageManager.MATCH_INSTANT) != 0;
-                final boolean matchVisibleToInstantAppOnly =
-                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
-                final boolean matchExplicitlyVisibleOnly =
-                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
-                final boolean isCallerInstantApp =
-                        instantAppPkgName != null;
-                final boolean isTargetSameInstantApp =
-                        comp.getPackageName().equals(instantAppPkgName);
-                final boolean isTargetInstantApp =
-                        (ai.applicationInfo.privateFlags
-                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-                final boolean isTargetVisibleToInstantApp =
-                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-                final boolean isTargetExplicitlyVisibleToInstantApp =
-                        isTargetVisibleToInstantApp
-                        && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
-                final boolean isTargetHiddenFromInstantApp =
-                        !isTargetVisibleToInstantApp
-                        || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
-                final boolean blockInstantResolution =
-                        !isTargetSameInstantApp
-                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
-                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
-                                        && isTargetHiddenFromInstantApp));
-                final boolean blockNormalResolution =
-                        !resolveForStart && !isTargetInstantApp && !isCallerInstantApp
-                                && shouldFilterApplicationLocked(
-                                getPackageSettingInternal(ai.applicationInfo.packageName,
-                                        Process.SYSTEM_UID), filterCallingUid, userId);
-                if (!blockInstantResolution && !blockNormalResolution) {
-                    final ResolveInfo ri = new ResolveInfo();
-                    ri.activityInfo = ai;
-                    list.add(ri);
-                }
-            }
-
-            List<ResolveInfo> result = applyPostResolutionFilter(
-                    list, instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart,
-                    userId, intent);
-            return result;
-        }
-
-        QueryIntentActivitiesResult lockedResult =
-                queryIntentActivitiesInternalBody(
-                    intent, resolvedType, flags, filterCallingUid, userId, resolveForStart,
-                    allowDynamicSplits, pkgName, instantAppPkgName);
-        if (lockedResult.answer != null) {
-            return lockedResult.answer;
-        }
-
-        if (lockedResult.addInstant) {
-            String callingPkgName = getInstantAppPackageName(filterCallingUid);
-            boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
-            lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent,
-                    resolvedType, flags, userId, resolveForStart, isRequesterInstantApp);
-        }
-        if (lockedResult.sortResult) {
-            Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER);
-        }
-        return applyPostResolutionFilter(
-                lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid,
-                resolveForStart, userId, intent);
+        return computer(true).queryIntentActivitiesInternal(intent,
+                resolvedType, flags, privateResolveFlags,
+                filterCallingUid, userId, resolveForStart, allowDynamicSplits);
     }
 
     private @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
             Intent intent, String resolvedType, int flags, int filterCallingUid, int userId,
             boolean resolveForStart, boolean allowDynamicSplits, String pkgName,
             String instantAppPkgName) {
-        // reader
-        synchronized (mLock) {
-            boolean sortResult = false;
-            boolean addInstant = false;
-            List<ResolveInfo> result = null;
-            if (pkgName == null) {
-                List<CrossProfileIntentFilter> matchingFilters =
-                        getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
-                // Check for results that need to skip the current profile.
-                ResolveInfo xpResolveInfo  = querySkipCurrentProfileIntents(matchingFilters, intent,
-                        resolvedType, flags, userId);
-                if (xpResolveInfo != null) {
-                    List<ResolveInfo> xpResult = new ArrayList<>(1);
-                    xpResult.add(xpResolveInfo);
-                    return new QueryIntentActivitiesResult(
-                            applyPostResolutionFilter(
-                                    filterIfNotSystemUser(xpResult, userId), instantAppPkgName,
-                                    allowDynamicSplits, filterCallingUid, resolveForStart, userId,
-                                    intent));
-                }
-
-                // Check for results in the current profile.
-                result = filterIfNotSystemUser(mComponentResolver.queryActivities(
-                        intent, resolvedType, flags, userId), userId);
-                addInstant = isInstantAppResolutionAllowed(intent, result, userId,
-                        false /*skipPackageCheck*/);
-                // Check for cross profile results.
-                boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
-                xpResolveInfo = queryCrossProfileIntents(
-                        matchingFilters, intent, resolvedType, flags, userId,
-                        hasNonNegativePriorityResult);
-                if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) {
-                    boolean isVisibleToUser = filterIfNotSystemUser(
-                            Collections.singletonList(xpResolveInfo), userId).size() > 0;
-                    if (isVisibleToUser) {
-                        result.add(xpResolveInfo);
-                        sortResult = true;
-                    }
-                }
-                if (intent.hasWebURI()) {
-                    CrossProfileDomainInfo xpDomainInfo = null;
-                    final UserInfo parent = getProfileParent(userId);
-                    if (parent != null) {
-                        xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
-                                flags, userId, parent.id);
-                    }
-                    if (xpDomainInfo != null) {
-                        if (xpResolveInfo != null) {
-                            // If we didn't remove it, the cross-profile ResolveInfo would be twice
-                            // in the result.
-                            result.remove(xpResolveInfo);
-                        }
-                        if (result.size() == 0 && !addInstant) {
-                            // No result in current profile, but found candidate in parent user.
-                            // And we are not going to add emphemeral app, so we can return the
-                            // result straight away.
-                            result.add(xpDomainInfo.resolveInfo);
-                            return new QueryIntentActivitiesResult(
-                                    applyPostResolutionFilter(result, instantAppPkgName,
-                                            allowDynamicSplits, filterCallingUid, resolveForStart,
-                                            userId, intent));
-                        }
-                    } else if (result.size() <= 1 && !addInstant) {
-                        // No result in parent user and <= 1 result in current profile, and we
-                        // are not going to add emphemeral app, so we can return the result without
-                        // further processing.
-                        return new QueryIntentActivitiesResult(
-                                applyPostResolutionFilter(result, instantAppPkgName,
-                                allowDynamicSplits, filterCallingUid, resolveForStart, userId,
-                                intent));
-                    }
-                    // We have more than one candidate (combining results from current and parent
-                    // profile), so we need filtering and sorting.
-                    result = filterCandidatesWithDomainPreferredActivitiesLPr(
-                            intent, flags, result, xpDomainInfo, userId);
-                    sortResult = true;
-                }
-            } else {
-                final PackageSetting setting =
-                        getPackageSettingInternal(pkgName, Process.SYSTEM_UID);
-                result = null;
-                if (setting != null && setting.pkg != null && (resolveForStart
-                        || !shouldFilterApplicationLocked(setting, filterCallingUid, userId))) {
-                    result = filterIfNotSystemUser(mComponentResolver.queryActivities(
-                            intent, resolvedType, flags, setting.pkg.getActivities(), userId),
-                            userId);
-                }
-                if (result == null || result.size() == 0) {
-                    // the caller wants to resolve for a particular package; however, there
-                    // were no installed results, so, try to find an ephemeral result
-                    addInstant = isInstantAppResolutionAllowed(
-                                    intent, null /*result*/, userId, true /*skipPackageCheck*/);
-                    if (result == null) {
-                        result = new ArrayList<>();
-                    }
-                }
-            }
-            return new QueryIntentActivitiesResult(sortResult, addInstant, result);
-        }
+        return computer(true).queryIntentActivitiesInternalBody(
+            intent, resolvedType, flags, filterCallingUid, userId,
+            resolveForStart, allowDynamicSplits, pkgName,
+            instantAppPkgName);
     }
 
     private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent,
             String resolvedType, int flags, int userId, boolean resolveForStart,
             boolean isRequesterInstantApp) {
-        // first, check to see if we've got an instant app already installed
-        final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
-        ResolveInfo localInstantApp = null;
-        boolean blockResolution = false;
-        if (!alreadyResolvedLocally) {
-            final List<ResolveInfo> instantApps = mComponentResolver.queryActivities(
-                    intent,
-                    resolvedType,
-                    flags
-                        | PackageManager.GET_RESOLVED_FILTER
-                        | PackageManager.MATCH_INSTANT
-                        | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
-                    userId);
-            for (int i = instantApps.size() - 1; i >= 0; --i) {
-                final ResolveInfo info = instantApps.get(i);
-                final String packageName = info.activityInfo.packageName;
-                final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps.getInstantApp(userId)) {
-                    final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                    final int status = (int)(packedStatus >> 32);
-                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-                        // there's a local instant application installed, but, the user has
-                        // chosen to never use it; skip resolution and don't acknowledge
-                        // an instant application is even available
-                        if (DEBUG_INSTANT) {
-                            Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName);
-                        }
-                        blockResolution = true;
-                        break;
-                    } else {
-                        // we have a locally installed instant application; skip resolution
-                        // but acknowledge there's an instant application available
-                        if (DEBUG_INSTANT) {
-                            Slog.v(TAG, "Found installed instant app; pkg: " + packageName);
-                        }
-                        localInstantApp = info;
-                        break;
-                    }
-                }
-            }
-        }
-        // no app installed, let's see if one's available
-        AuxiliaryResolveInfo auxiliaryResponse = null;
-        if (!blockResolution) {
-            if (localInstantApp == null) {
-                // we don't have an instant app locally, resolve externally
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
-                String token = UUID.randomUUID().toString();
-                InstantAppDigest digest = InstantAppResolver.parseDigest(intent);
-                final InstantAppRequest requestObject = new InstantAppRequest(null /*responseObj*/,
-                        intent /*origIntent*/, resolvedType, null /*callingPackage*/,
-                        null /*callingFeatureId*/, isRequesterInstantApp, userId,
-                        null /*verificationBundle*/, resolveForStart,
-                        digest.getDigestPrefixSecure(), token);
-                auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
-                        mInstantAppResolverConnection, requestObject);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            } else {
-                // we have an instant application locally, but, we can't admit that since
-                // callers shouldn't be able to determine prior browsing. create a placeholder
-                // auxiliary response so the downstream code behaves as if there's an
-                // instant application available externally. when it comes time to start
-                // the instant application, we'll do the right thing.
-                final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo;
-                auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */,
-                                        ai.packageName, ai.longVersionCode, null /* splitName */);
-            }
-        }
-        if (intent.isWebIntent() && auxiliaryResponse == null) {
-            return result;
-        }
-        final PackageSetting ps = mSettings.getPackageLPr(mInstantAppInstallerActivity.packageName);
-        if (ps == null
-                || !ps.readUserState(userId).isEnabled(mInstantAppInstallerActivity, 0)) {
-            return result;
-        }
-        final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
-        ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
-                mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId);
-        ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
-                | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
-        // add a non-generic filter
-        ephemeralInstaller.filter = new IntentFilter();
-        if (intent.getAction() != null) {
-            ephemeralInstaller.filter.addAction(intent.getAction());
-        }
-        if (intent.getData() != null && intent.getData().getPath() != null) {
-            ephemeralInstaller.filter.addDataPath(
-                    intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
-        }
-        ephemeralInstaller.isInstantAppAvailable = true;
-        // make sure this resolver is the default
-        ephemeralInstaller.isDefault = true;
-        ephemeralInstaller.auxiliaryInfo = auxiliaryResponse;
-        if (DEBUG_INSTANT) {
-            Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
-        }
-
-        result.add(ephemeralInstaller);
-        return result;
+        return computer(true).maybeAddInstantAppInstaller(result, intent,
+                resolvedType, flags, userId, resolveForStart,
+                isRequesterInstantApp);
     }
 
     private static class CrossProfileDomainInfo {
@@ -7790,48 +9872,8 @@
 
     private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
             String resolvedType, int flags, int sourceUserId, int parentUserId) {
-        if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
-                sourceUserId)) {
-            return null;
-        }
-        List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
-                resolvedType, flags, parentUserId);
-
-        if (resultTargetUser == null || resultTargetUser.isEmpty()) {
-            return null;
-        }
-        CrossProfileDomainInfo result = null;
-        int size = resultTargetUser.size();
-        for (int i = 0; i < size; i++) {
-            ResolveInfo riTargetUser = resultTargetUser.get(i);
-            // Intent filter verification is only for filters that specify a host. So don't return
-            // those that handle all web uris.
-            if (riTargetUser.handleAllWebDataURI) {
-                continue;
-            }
-            String packageName = riTargetUser.activityInfo.packageName;
-            PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (ps == null) {
-                continue;
-            }
-            long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
-            int status = (int)(verificationState >> 32);
-            if (result == null) {
-                result = new CrossProfileDomainInfo();
-                result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
-                        sourceUserId, parentUserId);
-                result.bestDomainVerificationStatus = status;
-            } else {
-                result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
-                        result.bestDomainVerificationStatus);
-            }
-        }
-        // Don't consider matches with status NEVER across profiles.
-        if (result != null && result.bestDomainVerificationStatus
-                == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-            return null;
-        }
-        return result;
+        return computer(true).getCrossProfileDomainPreferredLpr(intent,
+                resolvedType, flags, sourceUserId, parentUserId);
     }
 
     /**
@@ -7839,23 +9881,11 @@
      * INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse.
      */
     private int bestDomainVerificationStatus(int status1, int status2) {
-        if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-            return status2;
-        }
-        if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-            return status1;
-        }
-        return (int) MathUtils.max(status1, status2);
+        return computer(true).bestDomainVerificationStatus(status1, status2);
     }
 
     private boolean isUserEnabled(int userId) {
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            UserInfo userInfo = mUserManager.getUserInfo(userId);
-            return userInfo != null && userInfo.isEnabled();
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
+        return computer(true).isUserEnabled(userId);
     }
 
     /**
@@ -7864,16 +9894,7 @@
      * @return filtered list
      */
     private List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId) {
-        if (userId == UserHandle.USER_SYSTEM) {
-            return resolveInfos;
-        }
-        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
-            ResolveInfo info = resolveInfos.get(i);
-            if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
-                resolveInfos.remove(i);
-            }
-        }
-        return resolveInfos;
+        return computer(true).filterIfNotSystemUser(resolveInfos, userId);
     }
 
     /**
@@ -7890,87 +9911,9 @@
     private List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
             String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
             boolean resolveForStart, int userId, Intent intent) {
-        final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId);
-        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
-            final ResolveInfo info = resolveInfos.get(i);
-            // remove locally resolved instant app web results when disabled
-            if (info.isInstantAppAvailable && blockInstant) {
-                resolveInfos.remove(i);
-                continue;
-            }
-            // allow activities that are defined in the provided package
-            if (allowDynamicSplits
-                    && info.activityInfo != null
-                    && info.activityInfo.splitName != null
-                    && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
-                            info.activityInfo.splitName)) {
-                if (mInstantAppInstallerActivity == null) {
-                    if (DEBUG_INSTALL) {
-                        Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
-                    }
-                    resolveInfos.remove(i);
-                    continue;
-                }
-                if (blockInstant && isInstantApp(info.activityInfo.packageName, userId)) {
-                    resolveInfos.remove(i);
-                    continue;
-                }
-                // requested activity is defined in a split that hasn't been installed yet.
-                // add the installer to the resolve list
-                if (DEBUG_INSTALL) {
-                    Slog.v(TAG, "Adding installer to the ResolveInfo list");
-                }
-                final ResolveInfo installerInfo = new ResolveInfo(
-                        mInstantAppInstallerInfo);
-                final ComponentName installFailureActivity = findInstallFailureActivity(
-                        info.activityInfo.packageName,  filterCallingUid, userId);
-                installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
-                        installFailureActivity,
-                        info.activityInfo.packageName,
-                        info.activityInfo.applicationInfo.longVersionCode,
-                        info.activityInfo.splitName);
-                // add a non-generic filter
-                installerInfo.filter = new IntentFilter();
-
-                // This resolve info may appear in the chooser UI, so let us make it
-                // look as the one it replaces as far as the user is concerned which
-                // requires loading the correct label and icon for the resolve info.
-                installerInfo.resolvePackageName = info.getComponentInfo().packageName;
-                installerInfo.labelRes = info.resolveLabelResId();
-                installerInfo.icon = info.resolveIconResId();
-                installerInfo.isInstantAppAvailable = true;
-                resolveInfos.set(i, installerInfo);
-                continue;
-            }
-            if (ephemeralPkgName == null) {
-                // caller is a full app
-                SettingBase callingSetting =
-                        mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid));
-                PackageSetting resolvedSetting =
-                        getPackageSettingInternal(info.activityInfo.packageName, 0);
-                if (resolveForStart
-                        || !mAppsFilter.shouldFilterApplication(
-                                filterCallingUid, callingSetting, resolvedSetting, userId)) {
-                    continue;
-                }
-            } else if (ephemeralPkgName.equals(info.activityInfo.packageName)) {
-                // caller is same app; don't need to apply any other filtering
-                continue;
-            } else if (resolveForStart
-                    && (intent.isWebIntent()
-                            || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0)
-                    && intent.getPackage() == null
-                    && intent.getComponent() == null) {
-                // ephemeral apps can launch other ephemeral apps indirectly
-                continue;
-            } else if (((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)
-                    && !info.activityInfo.applicationInfo.isInstantApp()) {
-                // allow activities that have been explicitly exposed to ephemeral apps
-                continue;
-            }
-            resolveInfos.remove(i);
-        }
-        return resolveInfos;
+        return computer(true).applyPostResolutionFilter(resolveInfos,
+                ephemeralPkgName, allowDynamicSplits, filterCallingUid,
+                resolveForStart, userId, intent);
     }
 
     /**
@@ -7982,24 +9925,8 @@
      */
     private @Nullable ComponentName findInstallFailureActivity(
             String packageName, int filterCallingUid, int userId) {
-        final Intent failureActivityIntent = new Intent(Intent.ACTION_INSTALL_FAILURE);
-        failureActivityIntent.setPackage(packageName);
-        // IMPORTANT: disallow dynamic splits to avoid an infinite loop
-        final List<ResolveInfo> result = queryIntentActivitiesInternal(
-                failureActivityIntent, null /*resolvedType*/, 0 /*flags*/,
-                0 /*privateResolveFlags*/, filterCallingUid, userId, false /*resolveForStart*/,
-                false /*allowDynamicSplits*/);
-        final int NR = result.size();
-        if (NR > 0) {
-            for (int i = 0; i < NR; i++) {
-                final ResolveInfo info = result.get(i);
-                if (info.activityInfo.splitName != null) {
-                    continue;
-                }
-                return new ComponentName(packageName, info.activityInfo.name);
-            }
-        }
-        return null;
+        return computer(true).findInstallFailureActivity(
+            packageName, filterCallingUid, userId);
     }
 
     /**
@@ -8007,180 +9934,23 @@
      * @return if the list contains a resolve info with non-negative priority
      */
     private boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos) {
-        return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0;
+        return computer(true).hasNonNegativePriority(resolveInfos);
     }
 
     private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
             int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
             int userId) {
-        final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
-
-        if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
-            Slog.v(TAG, "Filtering results with preferred activities. Candidates count: " +
-                    candidates.size());
-        }
-
-        final ArrayList<ResolveInfo> result =
-                filterCandidatesWithDomainPreferredActivitiesLPrBody(
-                    intent, matchFlags, candidates, xpDomainInfo, userId, debug);
-
-        if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
-            Slog.v(TAG, "Filtered results with preferred activities. New candidates count: "
-                    + result.size());
-            for (ResolveInfo info : result) {
-                Slog.v(TAG, "  + " + info.activityInfo);
-            }
-        }
-        return result;
+        return computer(true).filterCandidatesWithDomainPreferredActivitiesLPr(intent,
+                matchFlags, candidates, xpDomainInfo,
+                userId);
     }
 
     private ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
             Intent intent, int matchFlags, List<ResolveInfo> candidates,
             CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
-        synchronized (mLock) {
-            final ArrayList<ResolveInfo> result = new ArrayList<>();
-            final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
-            final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
-            final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>();
-            final ArrayList<ResolveInfo> neverList = new ArrayList<>();
-            final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
-            final int count = candidates.size();
-            // First, try to use linked apps. Partition the candidates into four lists:
-            // one for the final results, one for the "do not use ever", one for "undefined status"
-            // and finally one for "browser app type".
-            for (int n=0; n<count; n++) {
-                ResolveInfo info = candidates.get(n);
-                String packageName = info.activityInfo.packageName;
-                PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    // Add to the special match all list (Browser use case)
-                    if (info.handleAllWebDataURI) {
-                        matchAllList.add(info);
-                        continue;
-                    }
-                    // Try to get the status from User settings first
-                    long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                    int status = (int)(packedStatus >> 32);
-                    int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
-                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + always: " + info.activityInfo.packageName
-                                    + " : linkgen=" + linkGeneration);
-                        }
-
-                        if (!intent.hasCategory(CATEGORY_BROWSABLE)
-                                || !intent.hasCategory(CATEGORY_DEFAULT)) {
-                            undefinedList.add(info);
-                            continue;
-                        }
-
-                        // Use link-enabled generation as preferredOrder, i.e.
-                        // prefer newly-enabled over earlier-enabled.
-                        info.preferredOrder = linkGeneration;
-                        alwaysList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + never: " + info.activityInfo.packageName);
-                        }
-                        neverList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + always-ask: " + info.activityInfo.packageName);
-                        }
-                        alwaysAskList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
-                            status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + ask: " + info.activityInfo.packageName);
-                        }
-                        undefinedList.add(info);
-                    }
-                }
-            }
-
-            // We'll want to include browser possibilities in a few cases
-            boolean includeBrowser = false;
-
-            // First try to add the "always" resolution(s) for the current user, if any
-            if (alwaysList.size() > 0) {
-                result.addAll(alwaysList);
-            } else {
-                // Add all undefined apps as we want them to appear in the disambiguation dialog.
-                result.addAll(undefinedList);
-                // Maybe add one for the other profile.
-                if (xpDomainInfo != null && (
-                        xpDomainInfo.bestDomainVerificationStatus
-                        != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
-                    result.add(xpDomainInfo.resolveInfo);
-                }
-                includeBrowser = true;
-            }
-
-            // The presence of any 'always ask' alternatives means we'll also offer browsers.
-            // If there were 'always' entries their preferred order has been set, so we also
-            // back that off to make the alternatives equivalent
-            if (alwaysAskList.size() > 0) {
-                for (ResolveInfo i : result) {
-                    i.preferredOrder = 0;
-                }
-                result.addAll(alwaysAskList);
-                includeBrowser = true;
-            }
-
-            if (includeBrowser) {
-                // Also add browsers (all of them or only the default one)
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.v(TAG, "   ...including browsers in candidate set");
-                }
-                if ((matchFlags & MATCH_ALL) != 0) {
-                    result.addAll(matchAllList);
-                } else {
-                    // Browser/generic handling case.  If there's a default browser, go straight
-                    // to that (but only if there is no other higher-priority match).
-                    final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(
-                            userId);
-                    int maxMatchPrio = 0;
-                    ResolveInfo defaultBrowserMatch = null;
-                    final int numCandidates = matchAllList.size();
-                    for (int n = 0; n < numCandidates; n++) {
-                        ResolveInfo info = matchAllList.get(n);
-                        // track the highest overall match priority...
-                        if (info.priority > maxMatchPrio) {
-                            maxMatchPrio = info.priority;
-                        }
-                        // ...and the highest-priority default browser match
-                        if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
-                            if (defaultBrowserMatch == null
-                                    || (defaultBrowserMatch.priority < info.priority)) {
-                                if (debug) {
-                                    Slog.v(TAG, "Considering default browser match " + info);
-                                }
-                                defaultBrowserMatch = info;
-                            }
-                        }
-                    }
-                    if (defaultBrowserMatch != null
-                            && defaultBrowserMatch.priority >= maxMatchPrio
-                            && !TextUtils.isEmpty(defaultBrowserPackageName))
-                    {
-                        if (debug) {
-                            Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
-                        }
-                        result.add(defaultBrowserMatch);
-                    } else {
-                        result.addAll(matchAllList);
-                    }
-                }
-
-                // If there is nothing selected, add all candidates and remove the ones that the user
-                // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
-                if (result.size() == 0) {
-                    result.addAll(candidates);
-                    result.removeAll(neverList);
-                }
-            }
-            return result;
-        }
+        return computer(true).filterCandidatesWithDomainPreferredActivitiesLPrBody(
+            intent, matchFlags, candidates,
+            xpDomainInfo, userId, debug);
     }
 
     // Returns a packed value as a long:
@@ -8188,66 +9958,24 @@
     // high 'int'-sized word: link status: undefined/ask/never/always.
     // low 'int'-sized word: relative priority among 'always' results.
     private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
-        long result = ps.getDomainVerificationStatusForUser(userId);
-        // if none available, get the status
-        if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
-            if (ps.getIntentFilterVerificationInfo() != null) {
-                result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
-            }
-        }
-        return result;
+        return computer(true).getDomainVerificationStatusLPr(ps, userId);
     }
 
     private ResolveInfo querySkipCurrentProfileIntents(
             List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
             int flags, int sourceUserId) {
-        if (matchingFilters != null) {
-            int size = matchingFilters.size();
-            for (int i = 0; i < size; i ++) {
-                CrossProfileIntentFilter filter = matchingFilters.get(i);
-                if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
-                    // Checking if there are activities in the target user that can handle the
-                    // intent.
-                    ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
-                            resolvedType, flags, sourceUserId);
-                    if (resolveInfo != null) {
-                        return resolveInfo;
-                    }
-                }
-            }
-        }
-        return null;
+        return computer(true).querySkipCurrentProfileIntents(
+            matchingFilters, intent, resolvedType,
+            flags, sourceUserId);
     }
 
     // Return matching ResolveInfo in target user if any.
     private ResolveInfo queryCrossProfileIntents(
             List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
             int flags, int sourceUserId, boolean matchInCurrentProfile) {
-        if (matchingFilters != null) {
-            // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
-            // match the same intent. For performance reasons, it is better not to
-            // run queryIntent twice for the same userId
-            SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
-            int size = matchingFilters.size();
-            for (int i = 0; i < size; i++) {
-                CrossProfileIntentFilter filter = matchingFilters.get(i);
-                int targetUserId = filter.getTargetUserId();
-                boolean skipCurrentProfile =
-                        (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
-                boolean skipCurrentProfileIfNoMatchFound =
-                        (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
-                if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
-                        && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
-                    // Checking if there are activities in the target user that can handle the
-                    // intent.
-                    ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
-                            resolvedType, flags, sourceUserId);
-                    if (resolveInfo != null) return resolveInfo;
-                    alreadyTriedUserIds.put(targetUserId, true);
-                }
-            }
-        }
-        return null;
+        return computer(true).queryCrossProfileIntents(
+            matchingFilters, intent, resolvedType,
+            flags, sourceUserId, matchInCurrentProfile);
     }
 
     /**
@@ -8257,54 +9985,14 @@
      */
     private ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, Intent intent,
             String resolvedType, int flags, int sourceUserId) {
-        int targetUserId = filter.getTargetUserId();
-        List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
-                resolvedType, flags, targetUserId);
-        if (resultTargetUser != null && isUserEnabled(targetUserId)) {
-            // If all the matches in the target profile are suspended, return null.
-            for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
-                if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags
-                        & ApplicationInfo.FLAG_SUSPENDED) == 0) {
-                    return createForwardingResolveInfoUnchecked(filter, sourceUserId,
-                            targetUserId);
-                }
-            }
-        }
-        return null;
+        return computer(true).createForwardingResolveInfo(filter, intent,
+                resolvedType, flags, sourceUserId);
     }
 
     private ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter,
             int sourceUserId, int targetUserId) {
-        ResolveInfo forwardingResolveInfo = new ResolveInfo();
-        final long ident = Binder.clearCallingIdentity();
-        boolean targetIsProfile;
-        try {
-            targetIsProfile = mUserManager.getUserInfo(targetUserId).isManagedProfile();
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        String className;
-        if (targetIsProfile) {
-            className = FORWARD_INTENT_TO_MANAGED_PROFILE;
-        } else {
-            className = FORWARD_INTENT_TO_PARENT;
-        }
-        ComponentName forwardingActivityComponentName = new ComponentName(
-                mAndroidApplication.packageName, className);
-        ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
-                sourceUserId);
-        if (!targetIsProfile) {
-            forwardingActivityInfo.showUserIcon = targetUserId;
-            forwardingResolveInfo.noResourceId = true;
-        }
-        forwardingResolveInfo.activityInfo = forwardingActivityInfo;
-        forwardingResolveInfo.priority = 0;
-        forwardingResolveInfo.preferredOrder = 0;
-        forwardingResolveInfo.match = 0;
-        forwardingResolveInfo.isDefault = true;
-        forwardingResolveInfo.filter = filter;
-        forwardingResolveInfo.targetUserId = targetUserId;
-        return forwardingResolveInfo;
+        return computer(true).createForwardingResolveInfoUnchecked(filter,
+                sourceUserId, targetUserId);
     }
 
     @Override
@@ -8623,144 +10311,23 @@
     private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
-        if (!mUserManager.exists(userId)) return Collections.emptyList();
-        enforceCrossUserOrProfilePermission(callingUid,
-                userId,
-                false /*requireFullPermission*/,
-                false /*checkShell*/,
-                "query intent receivers");
-        final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
-                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
-        ComponentName comp = intent.getComponent();
-        if (comp == null) {
-            if (intent.getSelector() != null) {
-                intent = intent.getSelector();
-                comp = intent.getComponent();
-            }
-        }
-        if (comp != null) {
-            final List<ResolveInfo> list = new ArrayList<>(1);
-            final ServiceInfo si = getServiceInfo(comp, flags, userId);
-            if (si != null) {
-                // When specifying an explicit component, we prevent the service from being
-                // used when either 1) the service is in an instant application and the
-                // caller is not the same instant application or 2) the calling package is
-                // ephemeral and the activity is not visible to ephemeral applications.
-                final boolean matchInstantApp =
-                        (flags & PackageManager.MATCH_INSTANT) != 0;
-                final boolean matchVisibleToInstantAppOnly =
-                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
-                final boolean isCallerInstantApp =
-                        instantAppPkgName != null;
-                final boolean isTargetSameInstantApp =
-                        comp.getPackageName().equals(instantAppPkgName);
-                final boolean isTargetInstantApp =
-                        (si.applicationInfo.privateFlags
-                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-                final boolean isTargetHiddenFromInstantApp =
-                        (si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
-                final boolean blockInstantResolution =
-                        !isTargetSameInstantApp
-                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
-                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
-                                        && isTargetHiddenFromInstantApp));
-
-                final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
-                        && shouldFilterApplicationLocked(
-                        getPackageSettingInternal(si.applicationInfo.packageName,
-                                Process.SYSTEM_UID), callingUid, userId);
-                if (!blockInstantResolution && !blockNormalResolution) {
-                    final ResolveInfo ri = new ResolveInfo();
-                    ri.serviceInfo = si;
-                    list.add(ri);
-                }
-            }
-            return list;
-        }
-
-        return queryIntentServicesInternalBody(intent, resolvedType, flags,
-                userId, callingUid, instantAppPkgName);
+        return computer(true).queryIntentServicesInternal(intent,
+                resolvedType, flags, userId, callingUid,
+                includeInstantApps);
     }
 
     private @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
             String resolvedType, int flags, int userId, int callingUid,
             String instantAppPkgName) {
-        // reader
-        synchronized (mLock) {
-            String pkgName = intent.getPackage();
-            if (pkgName == null) {
-                final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
-                        resolvedType, flags, userId);
-                if (resolveInfos == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostServiceResolutionFilter(
-                        resolveInfos, instantAppPkgName, userId, callingUid);
-            }
-            final AndroidPackage pkg = mPackages.get(pkgName);
-            if (pkg != null) {
-                final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
-                        resolvedType, flags, pkg.getServices(),
-                        userId);
-                if (resolveInfos == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostServiceResolutionFilter(
-                        resolveInfos, instantAppPkgName, userId, callingUid);
-            }
-            return Collections.emptyList();
-        }
+        return computer(true).queryIntentServicesInternalBody(intent,
+                resolvedType, flags, userId, callingUid,
+                instantAppPkgName);
     }
 
     private List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos,
             String instantAppPkgName, @UserIdInt int userId, int filterCallingUid) {
-        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
-            final ResolveInfo info = resolveInfos.get(i);
-            if (instantAppPkgName == null) {
-                SettingBase callingSetting =
-                        mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid));
-                PackageSetting resolvedSetting =
-                        getPackageSettingInternal(info.serviceInfo.packageName, 0);
-                if (!mAppsFilter.shouldFilterApplication(
-                        filterCallingUid, callingSetting, resolvedSetting, userId)) {
-                    continue;
-                }
-            }
-            final boolean isEphemeralApp = info.serviceInfo.applicationInfo.isInstantApp();
-            // allow services that are defined in the provided package
-            if (isEphemeralApp && instantAppPkgName.equals(info.serviceInfo.packageName)) {
-                if (info.serviceInfo.splitName != null
-                        && !ArrayUtils.contains(info.serviceInfo.applicationInfo.splitNames,
-                                info.serviceInfo.splitName)) {
-                    // requested service is defined in a split that hasn't been installed yet.
-                    // add the installer to the resolve list
-                    if (DEBUG_INSTANT) {
-                        Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
-                    }
-                    final ResolveInfo installerInfo = new ResolveInfo(
-                            mInstantAppInstallerInfo);
-                    installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
-                            null /* installFailureActivity */,
-                            info.serviceInfo.packageName,
-                            info.serviceInfo.applicationInfo.longVersionCode,
-                            info.serviceInfo.splitName);
-                    // add a non-generic filter
-                    installerInfo.filter = new IntentFilter();
-                    // load resources from the correct package
-                    installerInfo.resolvePackageName = info.getComponentInfo().packageName;
-                    resolveInfos.set(i, installerInfo);
-                }
-                continue;
-            }
-            // allow services that have been explicitly exposed to ephemeral apps
-            if (!isEphemeralApp
-                    && ((info.serviceInfo.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
-                continue;
-            }
-            resolveInfos.remove(i);
-        }
-        return resolveInfos;
+        return computer(true).applyPostServiceResolutionFilter(resolveInfos,
+                instantAppPkgName, userId, filterCallingUid);
     }
 
     @Override
@@ -8905,88 +10472,14 @@
 
     @Override
     public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return ParceledListSlice.emptyList();
-        }
-        if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
-        flags = updateFlagsForPackage(flags, userId);
-
-        enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
-                false /* checkShell */, "get installed packages");
-
-        return getInstalledPackagesBody(flags, userId, callingUid);
+        // SNAPSHOT
+        return computer(false).getInstalledPackages(flags, userId);
     }
 
     private ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
                                                                       int callingUid) {
-        // writer
-        synchronized (mLock) {
-            final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
-            final boolean listApex = (flags & MATCH_APEX) != 0;
-            final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0;
-
-            ArrayList<PackageInfo> list;
-            if (listUninstalled) {
-                list = new ArrayList<>(mSettings.getPackagesLocked().size());
-                for (PackageSetting ps : mSettings.getPackagesLocked().values()) {
-                    if (listFactory) {
-                        if (!ps.isSystem()) {
-                            continue;
-                        }
-                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
-                        if (psDisabled != null) {
-                            ps = psDisabled;
-                        }
-                    }
-                    if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
-                        continue;
-                    }
-                    if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                        continue;
-                    }
-                    final PackageInfo pi = generatePackageInfo(ps, flags, userId);
-                    if (pi != null) {
-                        list.add(pi);
-                    }
-                }
-            } else {
-                list = new ArrayList<>(mPackages.size());
-                for (AndroidPackage p : mPackages.values()) {
-                    PackageSetting ps = getPackageSetting(p.getPackageName());
-                    if (listFactory) {
-                        if (!p.isSystem()) {
-                            continue;
-                        }
-                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
-                        if (psDisabled != null) {
-                            ps = psDisabled;
-                        }
-                    }
-                    if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
-                        continue;
-                    }
-                    if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                        continue;
-                    }
-                    final PackageInfo pi = generatePackageInfo(ps, flags, userId);
-                    if (pi != null) {
-                        list.add(pi);
-                    }
-                }
-            }
-            if (listApex) {
-                if (listFactory) {
-                    list.addAll(mApexManager.getFactoryPackages());
-                } else {
-                    list.addAll(mApexManager.getActivePackages());
-                }
-                if (listUninstalled) {
-                    list.addAll(mApexManager.getInactivePackages());
-                }
-            }
-            return new ParceledListSlice<>(list);
-        }
+        return computer(true).getInstalledPackagesBody(flags, userId,
+                callingUid);
     }
 
     private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps,
@@ -9163,39 +10656,20 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                false /* checkShell */, "isInstantApp");
-
-        return isInstantAppInternal(packageName, userId, callingUid);
+        // SNAPSHOT
+        return computer(false).isInstantApp(packageName, userId);
     }
 
     private boolean isInstantAppInternal(String packageName, @UserIdInt int userId,
             int callingUid) {
-        if (HIDE_EPHEMERAL_APIS) {
-            return false;
-        }
-        return isInstantAppInternalBody(packageName, userId, callingUid);
+        return computer(true).isInstantAppInternal(packageName, userId,
+                callingUid);
     }
 
     private boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId,
             int callingUid) {
-        synchronized (mLock) {
-            if (Process.isIsolated(callingUid)) {
-                callingUid = mIsolatedOwners.get(callingUid);
-            }
-            final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            final boolean returnAllowed =
-                    ps != null
-                    && (isCallerSameApp(packageName, callingUid)
-                            || canViewInstantApps(callingUid, userId)
-                            || mInstantAppRegistry.isInstantAccessGranted(
-                                    userId, UserHandle.getAppId(callingUid), ps.appId));
-            if (returnAllowed) {
-                return ps.getInstantApp(userId);
-            }
-        }
-        return false;
+        return computer(true).isInstantAppInternalBody(packageName, userId,
+                callingUid);
     }
 
     @Override
@@ -9252,9 +10726,7 @@
     }
 
     private boolean isCallerSameApp(String packageName, int uid) {
-        AndroidPackage pkg = mPackages.get(packageName);
-        return pkg != null
-                && UserHandle.getAppId(uid) == pkg.getUid();
+        return computer(true).isCallerSameApp(packageName, uid);
     }
 
     @Override
@@ -10012,8 +11484,8 @@
      */
     void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell, String message) {
-        enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, false,
-                message);
+        computer(true).enforceCrossUserPermission(callingUid, userId,
+                requireFullPermission, checkShell, message);
     }
 
     /**
@@ -10029,23 +11501,9 @@
     private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             boolean requirePermissionWhenSameUser, String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
-                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (hasCrossUserPermission(
-                callingUid, callingUserId, userId, requireFullPermission,
-                requirePermissionWhenSameUser)) {
-            return;
-        }
-        String errorMessage = buildInvalidCrossUserPermissionMessage(
-                callingUid, userId, message, requireFullPermission);
-        Slog.w(TAG, errorMessage);
-        throw new SecurityException(errorMessage);
+        computer(true).enforceCrossUserPermission(callingUid, userId,
+                requireFullPermission, checkShell,
+                requirePermissionWhenSameUser, message);
     }
 
     /**
@@ -10064,62 +11522,24 @@
      */
     private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell, String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
-                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
-                /*requirePermissionWhenSameUser= */ false)) {
-            return;
-        }
-        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
-        if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
-                mContext,
-                android.Manifest.permission.INTERACT_ACROSS_PROFILES,
-                PermissionChecker.PID_UNKNOWN,
-                callingUid,
-                getPackage(callingUid).getPackageName())
-                == PermissionChecker.PERMISSION_GRANTED) {
-            return;
-        }
-        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
-                callingUid, userId, message, requireFullPermission, isSameProfileGroup);
-        Slog.w(TAG, errorMessage);
-        throw new SecurityException(errorMessage);
+        computer(true).enforceCrossUserOrProfilePermission(callingUid, userId,
+                requireFullPermission, checkShell, message);
     }
 
     private boolean hasCrossUserPermission(
             int callingUid, int callingUserId, int userId, boolean requireFullPermission,
             boolean requirePermissionWhenSameUser) {
-        if (!requirePermissionWhenSameUser && userId == callingUserId) {
-            return true;
-        }
-        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
-            return true;
-        }
-        if (requireFullPermission) {
-            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        }
-        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+        return computer(true).hasCrossUserPermission(
+            callingUid, callingUserId, userId, requireFullPermission,
+            requirePermissionWhenSameUser);
     }
 
     private boolean hasPermission(String permission) {
-        return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
+        return computer(true).hasPermission(permission);
     }
 
     private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return computer(true).isSameProfileGroup(callerUserId, userId);
     }
 
     private static String buildInvalidCrossUserPermissionMessage(int callingUid,
@@ -10757,7 +12177,7 @@
 
     @Nullable
     private SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) {
-        return getSharedLibraryInfo(name, version, mSharedLibraries, null);
+        return computer(true).getSharedLibraryInfoLPr(name, version);
     }
 
     @Nullable
@@ -12971,6 +14391,7 @@
                     mResolveComponentName = new ComponentName(
                             mAndroidApplication.packageName, mResolveActivity.name);
                 }
+                onChanged();
             }
         }
 
@@ -13107,6 +14528,7 @@
             mResolveInfo.preferredOrder = 0;
             mResolveInfo.match = 0;
             mResolveComponentName = mCustomResolverComponentName;
+            onChanged();
             Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " +
                     mResolveComponentName);
         }
@@ -13118,6 +14540,7 @@
                 Slog.d(TAG, "Clear ephemeral installer activity");
             }
             mInstantAppInstallerActivity = null;
+            onChanged();
             return;
         }
 
@@ -13137,6 +14560,7 @@
         mInstantAppInstallerInfo.isDefault = true;
         mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
                 | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
+        onChanged();
     }
 
     private void killApplication(String pkgName, @AppIdInt int appId, String reason) {
@@ -19184,84 +20608,18 @@
     }
 
     private String resolveExternalPackageNameLPr(AndroidPackage pkg) {
-        if (pkg.getStaticSharedLibName() != null) {
-            return pkg.getManifestPackageName();
-        }
-        return pkg.getPackageName();
+        return computer(true).resolveExternalPackageNameLPr(pkg);
     }
 
     @GuardedBy("mLock")
     private String resolveInternalPackageNameLPr(String packageName, long versionCode) {
-        final int callingUid = Binder.getCallingUid();
-        return resolveInternalPackageNameInternalLocked(packageName, versionCode,
-                callingUid);
+        return computer(true).resolveInternalPackageNameLPr(packageName, versionCode);
     }
 
     private String resolveInternalPackageNameInternalLocked(
             String packageName, long versionCode, int callingUid) {
-        // Handle renamed packages
-        String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
-        packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
-
-        // Is this a static library?
-        LongSparseArray<SharedLibraryInfo> versionedLib =
-                mStaticLibsByDeclaringPackage.get(packageName);
-        if (versionedLib == null || versionedLib.size() <= 0) {
-            return packageName;
-        }
-
-        // Figure out which lib versions the caller can see
-        LongSparseLongArray versionsCallerCanSee = null;
-        final int callingAppId = UserHandle.getAppId(callingUid);
-        if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
-                && callingAppId != Process.ROOT_UID) {
-            versionsCallerCanSee = new LongSparseLongArray();
-            String libName = versionedLib.valueAt(0).getName();
-            String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
-            if (uidPackages != null) {
-                for (String uidPackage : uidPackages) {
-                    PackageSetting ps = mSettings.getPackageLPr(uidPackage);
-                    final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName);
-                    if (libIdx >= 0) {
-                        final long libVersion = ps.usesStaticLibrariesVersions[libIdx];
-                        versionsCallerCanSee.append(libVersion, libVersion);
-                    }
-                }
-            }
-        }
-
-        // Caller can see nothing - done
-        if (versionsCallerCanSee != null && versionsCallerCanSee.size() <= 0) {
-            return packageName;
-        }
-
-        // Find the version the caller can see and the app version code
-        SharedLibraryInfo highestVersion = null;
-        final int versionCount = versionedLib.size();
-        for (int i = 0; i < versionCount; i++) {
-            SharedLibraryInfo libraryInfo = versionedLib.valueAt(i);
-            if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey(
-                    libraryInfo.getLongVersion()) < 0) {
-                continue;
-            }
-            final long libVersionCode = libraryInfo.getDeclaringPackage().getLongVersionCode();
-            if (versionCode != PackageManager.VERSION_CODE_HIGHEST) {
-                if (libVersionCode == versionCode) {
-                    return libraryInfo.getPackageName();
-                }
-            } else if (highestVersion == null) {
-                highestVersion = libraryInfo;
-            } else if (libVersionCode  > highestVersion
-                    .getDeclaringPackage().getLongVersionCode()) {
-                highestVersion = libraryInfo;
-            }
-        }
-
-        if (highestVersion != null) {
-            return highestVersion.getPackageName();
-        }
-
-        return packageName;
+        return computer(true).resolveInternalPackageNameInternalLocked(
+            packageName, versionCode, callingUid);
     }
 
     boolean isCallerVerifier(int callingUid) {
@@ -21311,38 +22669,11 @@
      * then reports the most likely home activity or null if there are more than one.
      */
     private ComponentName getDefaultHomeActivity(int userId) {
-        List<ResolveInfo> allHomeCandidates = new ArrayList<>();
-        ComponentName cn = getHomeActivitiesAsUser(allHomeCandidates, userId);
-        if (cn != null) {
-            return cn;
-        }
-        // TODO: This should not happen since there should always be a default package set for
-        //  ROLE_HOME in RoleManager. Continue with a warning log for now.
-        Slog.w(TAG, "Default package for ROLE_HOME is not set in RoleManager");
-
-        // Find the launcher with the highest priority and return that component if there are no
-        // other home activity with the same priority.
-        int lastPriority = Integer.MIN_VALUE;
-        ComponentName lastComponent = null;
-        final int size = allHomeCandidates.size();
-        for (int i = 0; i < size; i++) {
-            final ResolveInfo ri = allHomeCandidates.get(i);
-            if (ri.priority > lastPriority) {
-                lastComponent = ri.activityInfo.getComponentName();
-                lastPriority = ri.priority;
-            } else if (ri.priority == lastPriority) {
-                // Two components found with same priority.
-                lastComponent = null;
-            }
-        }
-        return lastComponent;
+        return computer(true).getDefaultHomeActivity(userId);
     }
 
     private Intent getHomeIntent() {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_HOME);
-        intent.addCategory(Intent.CATEGORY_DEFAULT);
-        return intent;
+        return computer(true).getHomeIntent();
     }
 
     private IntentFilter getHomeFilter() {
@@ -21354,31 +22685,8 @@
 
     ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
             int userId) {
-        Intent intent  = getHomeIntent();
-        List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
-                PackageManager.GET_META_DATA, userId);
-        allHomeCandidates.clear();
-        if (resolveInfos == null) {
-            return null;
-        }
-        allHomeCandidates.addAll(resolveInfos);
-
-        final String packageName = mDefaultAppProvider.getDefaultHome(userId);
-        if (packageName == null) {
-            return null;
-        }
-
-        int resolveInfosSize = resolveInfos.size();
-        for (int i = 0; i < resolveInfosSize; i++) {
-            ResolveInfo resolveInfo = resolveInfos.get(i);
-
-            if (resolveInfo.activityInfo != null && TextUtils.equals(
-                    resolveInfo.activityInfo.packageName, packageName)) {
-                return new ComponentName(resolveInfo.activityInfo.packageName,
-                        resolveInfo.activityInfo.name);
-            }
-        }
-        return null;
+        return computer(true).getHomeActivitiesAsUser(allHomeCandidates,
+                userId);
     }
 
     /** <b>must not hold {@link #mLock}</b> */
@@ -24959,23 +26267,13 @@
     }
 
     private AndroidPackage getPackage(String packageName) {
-        synchronized (mLock) {
-            packageName = resolveInternalPackageNameLPr(
-                    packageName, PackageManager.VERSION_CODE_HIGHEST);
-            return mPackages.get(packageName);
-        }
+        // SNAPSHOT
+        return computer(false).getPackage(packageName);
     }
 
     private AndroidPackage getPackage(int uid) {
-        synchronized (mLock) {
-            final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
-            AndroidPackage pkg = null;
-            final int numPackages = packageNames == null ? 0 : packageNames.length;
-            for (int i = 0; pkg == null && i < numPackages; i++) {
-                pkg = mPackages.get(packageNames[i]);
-            }
-            return pkg;
-        }
+        // SNAPSHOT
+        return computer(false).getPackage(uid);
     }
 
     private class PackageManagerInternalImpl extends PackageManagerInternal {
@@ -26226,15 +27524,12 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
-        return getPackageSettingInternal(packageName, Binder.getCallingUid());
+        // SNAPSHOT
+        return computer(false).getPackageSetting(packageName);
     }
 
     private PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
-        synchronized (mLock) {
-            packageName = resolveInternalPackageNameInternalLocked(
-                    packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
-            return mSettings.getPackageLPr(packageName);
-        }
+        return computer(true).getPackageSettingInternal(packageName, callingUid);
     }
 
     void forEachPackage(Consumer<AndroidPackage> actionLocked) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9347ce1..e20ed05 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3646,6 +3646,7 @@
 
         //...then external ones
         Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+        addedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
         // Also, add the UserHandle for mainline modules which can't use the @hide
         // EXTRA_USER_HANDLE.
@@ -4048,6 +4049,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+            removedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             // Also, add the UserHandle for mainline modules which can't use the @hide
             // EXTRA_USER_HANDLE.
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index db79ce4..638232d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -614,6 +614,7 @@
                             return pullRoleHolderLocked(atomTag, data);
                         }
                     case FrameworkStatsLog.DANGEROUS_PERMISSION_STATE:
+                        // fall-through - same call covers two cases
                     case FrameworkStatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED:
                         synchronized (mDangerousPermissionStateLock) {
                             return pullDangerousPermissionStateLocked(atomTag, data);
@@ -3014,7 +3015,7 @@
                     }
 
                     int numPerms = pkg.requestedPermissions.length;
-                    for (int permNum  = 0; permNum < numPerms; permNum++) {
+                    for (int permNum = 0; permNum < numPerms; permNum++) {
                         String permName = pkg.requestedPermissions[permNum];
 
                         PermissionInfo permissionInfo;
@@ -3027,10 +3028,6 @@
                             continue;
                         }
 
-                        if (permissionInfo.getProtection() != PROTECTION_DANGEROUS) {
-                            continue;
-                        }
-
                         if (permName.startsWith(COMMON_PERMISSION_PREFIX)) {
                             permName = permName.substring(COMMON_PERMISSION_PREFIX.length());
                         }
@@ -3042,15 +3039,17 @@
                                     (pkg.requestedPermissionsFlags[permNum]
                                             & REQUESTED_PERMISSION_GRANTED)
                                             != 0,
-                                    permissionFlags);
+                                    permissionFlags, permissionInfo.getProtection()
+                                            | permissionInfo.getProtectionFlags());
                         } else {
-                            // DangerousPermissionStateSampled atom.
+                            // DangeorusPermissionStateSampled atom.
                             e = FrameworkStatsLog.buildStatsEvent(atomTag, permName,
                                     pkg.applicationInfo.uid,
                                     (pkg.requestedPermissionsFlags[permNum]
                                             & REQUESTED_PERMISSION_GRANTED)
                                             != 0,
-                                    permissionFlags);
+                                    permissionFlags, permissionInfo.getProtection()
+                                            | permissionInfo.getProtectionFlags());
                         }
                         pulledData.add(e);
                     }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 033bfa6..865571e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -25,6 +25,7 @@
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.Context;
+import android.location.LocationManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -311,6 +312,19 @@
         mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion));
     }
 
+    boolean isGeoTimeZoneDetectionSupported() {
+        enforceManageTimeZoneDetectorPermission();
+
+        return isGeoLocationTimeZoneDetectionEnabled(mContext);
+    }
+
+    boolean isLocationEnabled() {
+        enforceManageTimeZoneDetectorPermission();
+
+        return mContext.getSystemService(LocationManager.class)
+                .isLocationEnabledForUser(mContext.getUser());
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 13b4456..b263030 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,10 +15,17 @@
  */
 package com.android.server.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_LOCATION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
 
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.os.ShellCommand;
@@ -43,6 +50,18 @@
         }
 
         switch (cmd) {
+            case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED:
+                return runIsAutoDetectionEnabled();
+            case SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED:
+                return runSetAutoDetectionEnabled();
+            case SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED:
+                return runIsGeoDetectionSupported();
+            case SHELL_COMMAND_IS_LOCATION_ENABLED:
+                return runIsLocationEnabled();
+            case SHELL_COMMAND_IS_GEO_DETECTION_ENABLED:
+                return runIsGeoDetectionEnabled();
+            case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
+                return runSetGeoDetectionEnabled();
             case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
                 return runSuggestGeolocationTimeZone();
             case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
@@ -55,6 +74,54 @@
         }
     }
 
+    private int runIsAutoDetectionEnabled() {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean enabled = mInterface.getCapabilitiesAndConfig()
+                .getConfiguration()
+                .isAutoDetectionEnabled();
+        pw.println(enabled);
+        return 0;
+    }
+
+    private int runIsGeoDetectionSupported() {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean enabled = mInterface.isGeoTimeZoneDetectionSupported();
+        pw.println(enabled);
+        return 0;
+    }
+
+    private int runIsLocationEnabled() {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean enabled = mInterface.isLocationEnabled();
+        pw.println(enabled);
+        return 0;
+    }
+
+    private int runIsGeoDetectionEnabled() {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean enabled = mInterface.getCapabilitiesAndConfig()
+                .getConfiguration()
+                .isGeoDetectionEnabled();
+        pw.println(enabled);
+        return 0;
+    }
+
+    private int runSetAutoDetectionEnabled() {
+        boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
+                .setAutoDetectionEnabled(enabled)
+                .build();
+        return mInterface.updateConfiguration(configuration) ? 0 : 1;
+    }
+
+    private int runSetGeoDetectionEnabled() {
+        boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
+                .setGeoDetectionEnabled(enabled)
+                .build();
+        return mInterface.updateConfiguration(configuration) ? 0 : 1;
+    }
+
     private int runSuggestGeolocationTimeZone() {
         return runSuggestTimeZone(
                 () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
@@ -96,6 +163,20 @@
         pw.println("Time Zone Detector (time_zone_detector) commands:");
         pw.println("  help");
         pw.println("    Print this help text.");
+        pw.printf("  %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
+        pw.println("    Prints true/false according to the automatic tz detection setting");
+        pw.printf("  %s true|false\n", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED);
+        pw.println("    Sets the automatic tz detection setting.");
+        pw.printf("  %s\n", SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
+        pw.println("    Prints true/false according to whether geolocation time zone detection is"
+                + " supported on this device");
+        pw.printf("  %s\n", SHELL_COMMAND_IS_LOCATION_ENABLED);
+        pw.println("    Prints true/false according to whether the master location toggle is"
+                + " enabled for the current user");
+        pw.printf("  %s\n", SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
+        pw.println("    Prints true/false according to the geolocation tz detection setting");
+        pw.printf("  %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED);
+        pw.println("    Sets the geolocation tz detection setting.");
         pw.printf("  %s <geolocation suggestion opts>\n",
                 SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
         pw.printf("  %s <manual suggestion opts>\n",
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index c060807..d8a145d9 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -36,6 +36,8 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -79,6 +81,7 @@
     @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
     @NonNull private final Dependencies mDeps;
 
+    @NonNull private final TelephonyManager mTelephonyManager;
     @NonNull private final SubscriptionManager mSubscriptionManager;
     @NonNull private final CarrierConfigManager mCarrierConfigManager;
 
@@ -106,6 +109,7 @@
         mCallback = Objects.requireNonNull(callback, "Missing callback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
 
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
 
@@ -139,7 +143,7 @@
      * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
      */
     public void handleSubscriptionsChanged() {
-        final Set<ParcelUuid> activeSubGroups = new ArraySet<>();
+        final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
         final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();
 
         final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
@@ -166,12 +170,22 @@
             // group.
             if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
                     && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
-                activeSubGroups.add(subInfo.getGroupUuid());
+                // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker
+
+                final TelephonyManager subIdSpecificTelephonyManager =
+                        mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
+
+                final ParcelUuid subGroup = subInfo.getGroupUuid();
+                final Set<String> pkgs =
+                        privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
+                pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
+
+                privilegedPackages.put(subGroup, pkgs);
             }
         }
 
         final TelephonySubscriptionSnapshot newSnapshot =
-                new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
+                new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages);
 
         // If snapshot was meaningfully updated, fire the callback
         if (!newSnapshot.equals(mCurrentSnapshot)) {
@@ -231,22 +245,40 @@
     /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
     public static class TelephonySubscriptionSnapshot {
         private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
-        private final Set<ParcelUuid> mActiveGroups;
+        private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
+
+        public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
+                new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap());
 
         @VisibleForTesting(visibility = Visibility.PRIVATE)
         TelephonySubscriptionSnapshot(
                 @NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
-                @NonNull Set<ParcelUuid> activeGroups) {
-            mSubIdToGroupMap = Collections.unmodifiableMap(
-                    Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
-            mActiveGroups = Collections.unmodifiableSet(
-                    Objects.requireNonNull(activeGroups, "activeGroups was null"));
+                @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
+            Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null");
+            Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
+
+            mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap);
+
+            final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
+            for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
+                unmodifiableInnerSets.put(
+                        entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
+            }
+            mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
         }
 
         /** Returns the active subscription groups */
         @NonNull
         public Set<ParcelUuid> getActiveSubscriptionGroups() {
-            return mActiveGroups;
+            return mPrivilegedPackages.keySet();
+        }
+
+        /** Checks if the provided package is carrier privileged for the specified sub group. */
+        public boolean packageHasPermissionsForSubscriptionGroup(
+                @NonNull ParcelUuid subGrp, @NonNull String packageName) {
+            final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
+
+            return privilegedPackages != null && privilegedPackages.contains(packageName);
         }
 
         /** Returns the Subscription Group for a given subId. */
@@ -273,7 +305,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mSubIdToGroupMap, mActiveGroups);
+            return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages);
         }
 
         @Override
@@ -285,7 +317,15 @@
             final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
 
             return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
-                    && mActiveGroups.equals(other.mActiveGroups);
+                    && mPrivilegedPackages.equals(other.mPrivilegedPackages);
+        }
+
+        @Override
+        public String toString() {
+            return "TelephonySubscriptionSnapshot{ "
+                    + "mSubIdToGroupMap=" + mSubIdToGroupMap
+                    + ", mPrivilegedPackages=" + mPrivilegedPackages
+                    + " }";
         }
     }
 
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index d51d16b..9d21b92 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -16,32 +16,69 @@
 
 package com.android.server.vcn;
 
+
 import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnGatewayConnectionConfig;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.util.Slog;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /**
  * Represents an single instance of a VCN.
  *
- * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
- * networks, network selection, and multi-homing.
+ * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
+ * including per-capability networks, network selection, and multi-homing.
  *
  * @hide
  */
 public class Vcn extends Handler {
     private static final String TAG = Vcn.class.getSimpleName();
 
+    private static final int MSG_EVENT_BASE = 0;
+    private static final int MSG_CMD_BASE = 100;
+
+    /**
+     * A carrier app updated the configuration.
+     *
+     * <p>Triggers update of config, re-evaluating all active and underlying networks.
+     *
+     * @param obj VcnConfig
+     */
+    private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
+
+    /**
+     * A NetworkRequest was added or updated.
+     *
+     * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
+     *
+     * @param obj NetworkRequest
+     */
+    private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
+
+    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
+    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
+
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final Dependencies mDeps;
+    @NonNull private final VcnNetworkRequestListener mRequestListener;
+
+    @NonNull
+    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
+            new HashMap<>();
 
     @NonNull private VcnConfig mConfig;
 
+    private boolean mIsRunning = true;
+
     public Vcn(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
@@ -58,31 +95,123 @@
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
+        mRequestListener = new VcnNetworkRequestListener();
 
         mConfig = Objects.requireNonNull(config, "Missing config");
+
+        // Register to receive cached and future NetworkRequests
+        mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
     }
 
     /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
     public void updateConfig(@NonNull VcnConfig config) {
         Objects.requireNonNull(config, "Missing config");
-        // TODO: Proxy to handler, and make config there.
+
+        sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
     }
 
-    /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */
-    public void teardown() {
-        // TODO: Proxy to handler, and teardown there.
+    /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
+    public void teardownAsynchronously() {
+        sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
     }
 
-    /** Notifies this Vcn instance of a new NetworkRequest */
-    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
-        Objects.requireNonNull(request, "Missing request");
+    private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
+        @Override
+        public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+            Objects.requireNonNull(request, "Missing request");
 
-        // TODO: Proxy to handler, and handle there.
+            sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, score, providerId, request));
+        }
     }
 
     @Override
     public void handleMessage(@NonNull Message msg) {
-        // TODO: Do something
+        if (!mIsRunning) {
+            return;
+        }
+
+        switch (msg.what) {
+            case MSG_EVENT_CONFIG_UPDATED:
+                handleConfigUpdated((VcnConfig) msg.obj);
+                break;
+            case MSG_EVENT_NETWORK_REQUESTED:
+                handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case MSG_CMD_TEARDOWN:
+                handleTeardown();
+                break;
+            default:
+                Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
+        }
+    }
+
+    private void handleConfigUpdated(@NonNull VcnConfig config) {
+        // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
+        Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode()));
+
+        mConfig = config;
+
+        // TODO: Reevaluate active VcnGatewayConnection(s)
+    }
+
+    private void handleTeardown() {
+        mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
+
+        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+            gatewayConnection.teardownAsynchronously();
+        }
+
+        mIsRunning = false;
+    }
+
+    private void handleNetworkRequested(
+            @NonNull NetworkRequest request, int score, int providerId) {
+        if (score > getNetworkScore()) {
+            Slog.v(getLogTag(),
+                    "Request " + request.requestId + " already satisfied by higher-scoring ("
+                            + score + ") network from provider " + providerId);
+            return;
+        }
+
+        // If preexisting VcnGatewayConnection(s) satisfy request, return
+        for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
+            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+                Slog.v(getLogTag(),
+                        "Request " + request.requestId
+                                + " satisfied by existing VcnGatewayConnection");
+                return;
+            }
+        }
+
+        // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
+        // up
+        for (VcnGatewayConnectionConfig gatewayConnectionConfig :
+                mConfig.getGatewayConnectionConfigs()) {
+            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+                Slog.v(
+                        getLogTag(),
+                        "Bringing up new VcnGatewayConnection for request " + request.requestId);
+
+                final VcnGatewayConnection vcnGatewayConnection =
+                        new VcnGatewayConnection(
+                                mVcnContext, mSubscriptionGroup, gatewayConnectionConfig);
+                mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
+            }
+        }
+    }
+
+    private boolean requestSatisfiedByGatewayConnectionConfig(
+            @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
+        final NetworkCapabilities configCaps = new NetworkCapabilities();
+        for (int cap : config.getAllExposedCapabilities()) {
+            configCaps.addCapability(cap);
+        }
+
+        return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
+    }
+
+    private String getLogTag() {
+        return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode());
     }
 
     /** Retrieves the network score for a VCN Network */
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 8ab52931..dba59bd 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -20,8 +20,6 @@
 import android.content.Context;
 import android.os.Looper;
 
-import com.android.server.VcnManagementService.VcnNetworkProvider;
-
 import java.util.Objects;
 
 /**
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 49c9b32..7ea8e04 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -65,8 +65,8 @@
                 mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
     }
 
-    /** Tears down this GatewayConnection, and any resources used */
-    public void teardown() {
+    /** Asynchronously tears down this GatewayConnection, and any resources used */
+    public void teardownAsynchronously() {
         mUnderlyingNetworkTracker.teardown();
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
new file mode 100644
index 0000000..7f5b23c
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -0,0 +1,108 @@
+/*
+ * 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.vcn;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.os.Looper;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
+ *
+ * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all
+ * active NetworkRequest(s), including ones that were filed prior to listener registration.
+ *
+ * @hide
+ */
+public class VcnNetworkProvider extends NetworkProvider {
+    private static final String TAG = VcnNetworkProvider.class.getSimpleName();
+
+    private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
+    private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();
+
+    public VcnNetworkProvider(Context context, Looper looper) {
+        super(context, looper, VcnNetworkProvider.class.getSimpleName());
+    }
+
+    // Package-private
+    void registerListener(@NonNull NetworkRequestListener listener) {
+        mListeners.add(listener);
+
+        // Send listener all cached requests
+        for (int i = 0; i < mRequests.size(); i++) {
+            notifyListenerForEvent(listener, mRequests.valueAt(i));
+        }
+    }
+
+    // Package-private
+    void unregisterListener(@NonNull NetworkRequestListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void notifyListenerForEvent(
+            @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) {
+        listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId);
+    }
+
+    @Override
+    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+        Slog.v(
+                TAG,
+                String.format(
+                        "Network requested: Request = %s, score = %d, providerId = %d",
+                        request, score, providerId));
+
+        final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
+        mRequests.put(request.requestId, entry);
+
+        // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
+        // Default Data Sub, or similar)
+        for (NetworkRequestListener listener : mListeners) {
+            notifyListenerForEvent(listener, entry);
+        }
+    }
+
+    @Override
+    public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+        mRequests.remove(request.requestId);
+    }
+
+    private static class NetworkRequestEntry {
+        public final NetworkRequest mRequest;
+        public final int mScore;
+        public final int mProviderId;
+
+        private NetworkRequestEntry(@NonNull NetworkRequest request, int score, int providerId) {
+            mRequest = Objects.requireNonNull(request, "Missing request");
+            mScore = score;
+            mProviderId = providerId;
+        }
+    }
+
+    // package-private
+    interface NetworkRequestListener {
+        void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5289f86..375c3e1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1618,7 +1618,8 @@
                     mService.getTransitionController().collectExistenceChange(r);
                 }
                 if (newTransition != null) {
-                    mService.getTransitionController().requestStartTransition(newTransition);
+                    mService.getTransitionController().requestStartTransition(newTransition,
+                            r.getTask());
                 } else {
                     // Make the collecting transition wait until this request is ready.
                     mService.getTransitionController().setReady(false);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 9ffedde..08e16c4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -285,6 +285,14 @@
     public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func);
 
     /**
+     * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
+     * SecurityException} or returns false with a logcat message depending on whether the app
+     * targets SDK level {@link android.os.Build.VERSION_CODES#S} or not.
+     */
+    public abstract boolean checkCanCloseSystemDialogs(int pid, int uid,
+            @Nullable String packageName);
+
+    /**
      * Called after the voice interaction service has changed.
      */
     public abstract void notifyActiveVoiceInteractionServiceChanged(ComponentName component);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9cbe277..9383922 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -147,6 +147,7 @@
 import android.app.admin.DevicePolicyCache;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
+import android.app.compat.CompatChanges;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -2900,6 +2901,86 @@
         }
     }
 
+    /**
+     * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
+     * SecurityException} or returns false with a logcat message depending on whether the app
+     * targets SDK level {@link android.os.Build.VERSION_CODES#S} or not.
+     */
+    private boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) {
+        final WindowProcessController process;
+        synchronized (mGlobalLock) {
+            process = mProcessMap.getProcess(pid);
+        }
+        if (packageName == null && process != null) {
+            // WindowProcessController.mInfo is final, so after the synchronized memory barrier
+            // above, process.mInfo can't change. As for reading mInfo.packageName,
+            // WindowProcessController doesn't own the ApplicationInfo object referenced by mInfo.
+            // ProcessRecord for example also holds a reference to that object, so protecting access
+            // to packageName with the WM lock would not be enough as we'd also need to synchronize
+            // on the AM lock if we are worried about races, but we can't synchronize on AM lock
+            // here. Hence, since this is only used for logging, we don't synchronize here.
+            packageName = process.mInfo.packageName;
+        }
+        String caller = "(pid=" + pid + ", uid=" + uid + ")";
+        if (packageName != null) {
+            caller = packageName + " " + caller;
+        }
+        if (!canCloseSystemDialogs(pid, uid, process)) {
+            // The app can't close system dialogs, throw only if it targets S+
+            if (CompatChanges.isChangeEnabled(
+                    ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, uid)) {
+                throw new SecurityException(
+                        "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+                                + " broadcast from " + caller + " requires "
+                                + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS + ".");
+            } else if (CompatChanges.isChangeEnabled(
+                    ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, uid)) {
+                Slog.e(TAG,
+                        "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+                                + " broadcast from " + caller + " requires "
+                                + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
+                                + ", dropping broadcast.");
+                return false;
+            } else {
+                Slog.w(TAG, Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+                        + " broadcast from " + caller + " will require "
+                        + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
+                        + " in future builds.");
+                return true;
+            }
+        }
+        return true;
+    }
+
+    private boolean canCloseSystemDialogs(int pid, int uid,
+            @Nullable WindowProcessController process) {
+        if (checkPermission(Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, pid, uid)
+                == PERMISSION_GRANTED) {
+            return true;
+        }
+        if (process != null) {
+            // Check if the instrumentation of the process has the permission. This covers the
+            // usual test started from the shell (which has the permission) case. This is needed
+            // for apps targeting SDK level < S but we are also allowing for targetSdk S+ as a
+            // convenience to avoid breaking a bunch of existing tests and asking them to adopt
+            // shell permissions to do this.
+            // Note that these getters all read from volatile fields in WindowProcessController, so
+            // no need to lock.
+            int sourceUid = process.getInstrumentationSourceUid();
+            if (process.isInstrumenting() && sourceUid != -1 && checkPermission(
+                    Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, sourceUid)
+                    == PERMISSION_GRANTED) {
+                return true;
+            }
+            // This is the notification trampoline use-case for example, where apps use Intent.ACSD
+            // to close the shade prior to starting an activity.
+            if (process.canCloseSystemDialogsByToken()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     static void enforceTaskPermission(String func) {
         if (checkCallingPermission(MANAGE_ACTIVITY_TASKS) == PackageManager.PERMISSION_GRANTED) {
             return;
@@ -5177,6 +5258,12 @@
         }
 
         @Override
+        public boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) {
+            return ActivityTaskManagerService.this.checkCanCloseSystemDialogs(pid, uid,
+                    packageName);
+        }
+
+        @Override
         public void notifyActiveVoiceInteractionServiceChanged(ComponentName component) {
             synchronized (mGlobalLock) {
                 mActiveVoiceInteractionServiceComponent = component;
@@ -5676,9 +5763,12 @@
         @Override
         public void closeSystemDialogs(String reason) {
             enforceNotIsolatedCaller("closeSystemDialogs");
-
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
+            if (!checkCanCloseSystemDialogs(pid, uid, null)) {
+                return;
+            }
+
             final long origId = Binder.clearCallingIdentity();
             try {
                 synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 9d291b1..73a6efd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2207,6 +2207,7 @@
         }
 
         if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
+            if (mService.getTransitionController().getTransitionPlayer() != null) return;
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
             final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 5952164..eeb7fac 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -24,11 +24,13 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
+import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
@@ -131,14 +133,19 @@
                         .all()
                         .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
                                 TYPE_NOTIFICATION_SHADE)
-                        .build());
+                        .build())
+                        .addFeature(new Feature.Builder(wmService.mPolicy,
+                                "OneHandedBackgroundPanel",
+                                FEATURE_ONE_HANDED_BACKGROUND_PANEL)
+                                .upTo(TYPE_WALLPAPER)
+                                .build())
+                        .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
+                                FEATURE_ONE_HANDED)
+                                .all()
+                                .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
+                                .build());
             }
             rootHierarchy
-                    .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
-                            FEATURE_ONE_HANDED)
-                            .all()
-                            .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
-                            .build())
                     .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification",
                             FEATURE_FULLSCREEN_MAGNIFICATION)
                             .all()
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index ff5b356..40e7a8e 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Build.IS_DEBUGGABLE;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
@@ -552,6 +553,11 @@
                 // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
                 t.setAlpha(animationLeash, 1 /* alpha */);
                 t.hide(animationLeash);
+
+                // TODO(b/175954493): Remove this after finding root cause.
+                if (IS_DEBUGGABLE) {
+                    animationLeash.setDebugRelease(true);
+                }
             }
             ProtoLog.i(WM_DEBUG_IME,
                     "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aa88657..1c41978 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6102,13 +6102,13 @@
         mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
 
         ActivityRecord lastResumed = null;
-        final Task lastFocusedStack = taskDisplayArea.getLastFocusedRootTask();
-        if (lastFocusedStack != null && lastFocusedStack != this) {
+        final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
+        if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTask()) {
             // So, why aren't we using prev here??? See the param comment on the method. prev
             // doesn't represent the last resumed activity. However, the last focus stack does if
             // it isn't null.
-            lastResumed = lastFocusedStack.getResumedActivity();
-            if (userLeaving && inMultiWindowMode() && lastFocusedStack.shouldBeVisible(next)) {
+            lastResumed = lastFocusedRootTask.getResumedActivity();
+            if (userLeaving && inMultiWindowMode() && lastFocusedRootTask.shouldBeVisible(next)) {
                 // The user isn't leaving if this stack is the multi-window mode and the last
                 // focused stack should still be visible.
                 if(DEBUG_USER_LEAVING) Slog.i(TAG_USER_LEAVING, "Overriding userLeaving to false"
@@ -6262,10 +6262,10 @@
             // Launcher is already visible in this case. If we don't add it to opening
             // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
             // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
-            final boolean lastActivityTranslucent = lastFocusedStack != null
-                    && (lastFocusedStack.inMultiWindowMode()
-                    || (lastFocusedStack.mLastPausedActivity != null
-                    && !lastFocusedStack.mLastPausedActivity.occludesParent()));
+            final boolean lastActivityTranslucent = lastFocusedRootTask != null
+                    && (lastFocusedRootTask.inMultiWindowMode()
+                    || (lastFocusedRootTask.mLastPausedActivity != null
+                    && !lastFocusedRootTask.mLastPausedActivity.occludesParent()));
 
             // This activity is now becoming visible.
             if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
@@ -6276,7 +6276,7 @@
             next.startLaunchTickingLocked();
 
             ActivityRecord lastResumedActivity =
-                    lastFocusedStack == null ? null : lastFocusedStack.getResumedActivity();
+                    lastFocusedRootTask == null ? null : lastFocusedRootTask.getResumedActivity();
             final ActivityState lastState = next.getState();
 
             mAtmService.updateCpuStats();
@@ -6373,8 +6373,8 @@
                 Slog.i(TAG, "Restarting because process died: " + next);
                 if (!next.hasBeenLaunched) {
                     next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastFocusedStack != null
-                        && lastFocusedStack.isTopRootTaskInDisplayArea()) {
+                } else  if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+                        && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
                     next.showStartingWindow(null /* prev */, false /* newTask */,
                             false /* taskSwitch */);
                 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index be78d7a..b37e3c4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -37,6 +37,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -567,6 +568,10 @@
                 tmpList.add(wc);
             }
             for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
+                if (!p.isAttached() || !changes.get(p).hasChanged(p)) {
+                    // Again, we're skipping no-ops
+                    break;
+                }
                 if (participants.contains(p)) {
                     topParent = p;
                     break;
@@ -695,6 +700,7 @@
             final TransitionInfo.Change change = new TransitionInfo.Change(
                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
                             : null, target.getSurfaceControl());
+            // TODO(shell-transitions): Use leash for non-organized windows.
             if (info.mParent != null) {
                 change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
             }
@@ -704,6 +710,12 @@
             change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left,
                     target.getBounds().top - target.getParent().getBounds().top);
             change.setFlags(info.getChangeFlags(target));
+            final Task task = target.asTask();
+            if (task != null) {
+                final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
+                task.fillTaskInfo(tinfo);
+                change.setTaskInfo(tinfo);
+            }
             out.addChange(change);
         }
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 2f5d10a..0fe0afa 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -167,7 +168,8 @@
             // Make the collecting transition wait until this request is ready.
             mCollectingTransition.setReady(false);
         } else {
-            newTransition = requestStartTransition(createTransition(type, flags));
+            newTransition = requestStartTransition(createTransition(type, flags),
+                    trigger != null ? trigger.asTask() : null);
         }
         if (trigger != null) {
             if (isExistenceType(type)) {
@@ -181,11 +183,16 @@
 
     /** Asks the transition player (shell) to start a created but not yet started transition. */
     @NonNull
-    Transition requestStartTransition(@NonNull Transition transition) {
+    Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask) {
         try {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Requesting StartTransition: %s", transition);
-            mTransitionPlayer.requestStartTransition(transition.mType, transition);
+            ActivityManager.RunningTaskInfo info = null;
+            if (startTask != null) {
+                info = new ActivityManager.RunningTaskInfo();
+                startTask.fillTaskInfo(info);
+            }
+            mTransitionPlayer.requestStartTransition(transition.mType, transition, info);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
             transition.start();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4eeae6c..0b60deb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2332,15 +2332,9 @@
                 }
             }
 
-            // We may be deferring layout passes at the moment, but since the client is interested
-            // in the new out values right now we need to force a layout.
-            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
-
+            // Create surfaceControl before surface placement otherwise layout will be skipped
+            // (because WS.isGoneForLayout() is true when there is no surface.
             if (shouldRelayout) {
-                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
-
-                result = win.relayoutVisibleWindow(result, attrChanges);
-
                 try {
                     result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                 } catch (Exception e) {
@@ -2352,6 +2346,17 @@
                     Binder.restoreCallingIdentity(origId);
                     return 0;
                 }
+            }
+
+            // We may be deferring layout passes at the moment, but since the client is interested
+            // in the new out values right now we need to force a layout.
+            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
+
+            if (shouldRelayout) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
+
+                result = win.relayoutVisibleWindow(result, attrChanges);
+
                 if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                     focusMayChange = true;
                 }
@@ -3238,6 +3243,11 @@
 
     @Override
     public void closeSystemDialogs(String reason) {
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid, null)) {
+            return;
+        }
         synchronized (mGlobalLock) {
             mRoot.closeSystemDialogs(reason);
         }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5f2b628..b0e67ce 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -19,8 +19,8 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
+import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -163,6 +163,11 @@
         try {
             synchronized (mGlobalLock) {
                 Transition transition = Transition.fromBinder(transitionToken);
+                // In cases where transition is already provided, the "readiness lifecycle" of the
+                // transition is determined outside of this transaction. However, if this is a
+                // direct call from shell, the entire transition lifecycle is contained in the
+                // provided transaction and thus we can setReady immediately after apply.
+                boolean needsSetReady = transition == null && t != null;
                 if (transition == null) {
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
@@ -174,6 +179,9 @@
                     t = new WindowContainerTransaction();
                 }
                 applyTransaction(t, -1 /*syncId*/, transition);
+                if (needsSetReady) {
+                    transition.setReady();
+                }
                 return transition;
             }
         } finally {
@@ -258,14 +266,21 @@
                 }
                 if (transition != null) {
                     transition.collect(wc);
-                    if (hop.isReparent() && hop.getNewParent() != null) {
-                        final WindowContainer parentWc =
-                                WindowContainer.fromBinder(hop.getNewParent());
-                        if (parentWc == null) {
-                            Slog.e(TAG, "Can't resolve parent window from token");
-                            continue;
+                    if (hop.isReparent()) {
+                        if (wc.getParent() != null) {
+                            // Collect the current parent. It's visibility may change as a result
+                            // of this reparenting.
+                            transition.collect(wc.getParent());
                         }
-                        transition.collect(parentWc);
+                        if (hop.getNewParent() != null) {
+                            final WindowContainer parentWc =
+                                    WindowContainer.fromBinder(hop.getNewParent());
+                            if (parentWc == null) {
+                                Slog.e(TAG, "Can't resolve parent window from token");
+                                continue;
+                            }
+                            transition.collect(parentWc);
+                        }
                     }
                 }
                 effects |= sanitizeAndApplyHierarchyOp(wc, hop);
@@ -307,6 +322,7 @@
             if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
                 // Already calls ensureActivityConfig
                 mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
             } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
                 final PooledConsumer f = PooledLambda.obtainConsumer(
                         ActivityRecord::ensureActivityConfiguration,
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8aa154b..663d91e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -23,6 +23,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
@@ -161,6 +162,8 @@
     private volatile boolean mDebugging;
     // Active instrumentation running in process?
     private volatile boolean mInstrumenting;
+    // If there is active instrumentation, this is the source
+    private volatile int mInstrumentationSourceUid = -1;
     // Active instrumentation with background activity starts privilege running in process?
     private volatile boolean mInstrumentingWithBackgroundActivityStartPrivileges;
     // This process it perceptible by the user.
@@ -623,9 +626,16 @@
         mBoundClientUids = boundClientUids;
     }
 
-    public void setInstrumenting(boolean instrumenting,
+    /**
+     * Set instrumentation-related info.
+     *
+     * If {@code instrumenting} is {@code false}, {@code sourceUid} has to be -1.
+     */
+    public void setInstrumenting(boolean instrumenting, int sourceUid,
             boolean hasBackgroundActivityStartPrivileges) {
+        checkArgument(instrumenting || sourceUid == -1);
         mInstrumenting = instrumenting;
+        mInstrumentationSourceUid = sourceUid;
         mInstrumentingWithBackgroundActivityStartPrivileges = hasBackgroundActivityStartPrivileges;
     }
 
@@ -633,6 +643,11 @@
         return mInstrumenting;
     }
 
+    /** Returns the uid of the active instrumentation source if there is one, otherwise -1. */
+    int getInstrumentationSourceUid() {
+        return mInstrumentationSourceUid;
+    }
+
     public void setPerceptible(boolean perceptible) {
         mPerceptible = perceptible;
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fff7f80..4d15ced 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -680,7 +680,9 @@
             // Load preinstalled system fonts for system server, so that WindowManagerService, etc
             // can start using Typeface. Note that fonts are required not only for text rendering,
             // but also for some text operations (e.g. TextUtils.makeSafeForPresentation()).
-            Typeface.loadPreinstalledSystemFontMap();
+            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+                Typeface.loadPreinstalledSystemFontMap();
+            }
 
             // Attach JVMTI agent if this is a debuggable build and the system property is set.
             if (Build.IS_DEBUGGABLE) {
diff --git a/services/musicrecognition/OWNERS b/services/musicrecognition/OWNERS
new file mode 100644
index 0000000..58f5d40
--- /dev/null
+++ b/services/musicrecognition/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 830636
+
+joannechung@google.com
+oni@google.com
+volnov@google.com
+
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
index 3531512..0cb729d 100644
--- a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.musicrecognition;
 
+import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_AUDIO_UNAVAILABLE;
 import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_KILLED;
 import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE;
 import static android.media.musicrecognition.MusicRecognitionManager.RecognitionFailureCode;
@@ -64,10 +65,6 @@
     @GuardedBy("mLock")
     private RemoteMusicRecognitionService mRemoteService;
 
-    private MusicRecognitionServiceCallback mRemoteServiceCallback =
-            new MusicRecognitionServiceCallback();
-    private IMusicRecognitionManagerCallback mCallback;
-
     MusicRecognitionManagerPerUserService(
             @NonNull MusicRecognitionManagerService primary,
             @NonNull Object lock, int userId) {
@@ -100,7 +97,8 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private RemoteMusicRecognitionService ensureRemoteServiceLocked() {
+    private RemoteMusicRecognitionService ensureRemoteServiceLocked(
+            IMusicRecognitionManagerCallback clientCallback) {
         if (mRemoteService == null) {
             final String serviceName = getComponentNameLocked();
             if (serviceName == null) {
@@ -113,7 +111,8 @@
 
             mRemoteService = new RemoteMusicRecognitionService(getContext(),
                     serviceComponent, mUserId, this,
-                    mRemoteServiceCallback, mMaster.isBindInstantServiceAllowed(),
+                    new MusicRecognitionServiceCallback(clientCallback),
+                    mMaster.isBindInstantServiceAllowed(),
                     mMaster.verbose);
         }
 
@@ -130,13 +129,14 @@
             @NonNull IBinder callback) {
         int maxAudioLengthSeconds = Math.min(recognitionRequest.getMaxAudioLengthSeconds(),
                 MAX_STREAMING_SECONDS);
-        mCallback = IMusicRecognitionManagerCallback.Stub.asInterface(callback);
+        IMusicRecognitionManagerCallback clientCallback =
+                IMusicRecognitionManagerCallback.Stub.asInterface(callback);
         AudioRecord audioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds);
 
-        mRemoteService = ensureRemoteServiceLocked();
+        mRemoteService = ensureRemoteServiceLocked(clientCallback);
         if (mRemoteService == null) {
             try {
-                mCallback.onRecognitionFailed(
+                clientCallback.onRecognitionFailed(
                         RECOGNITION_FAILED_SERVICE_UNAVAILABLE);
             } catch (RemoteException e) {
                 // Ignored.
@@ -147,7 +147,8 @@
         Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
         if (clientPipe == null) {
             try {
-                mCallback.onAudioStreamClosed();
+                clientCallback.onRecognitionFailed(
+                        RECOGNITION_FAILED_AUDIO_UNAVAILABLE);
             } catch (RemoteException ignored) {
                 // Ignored.
             }
@@ -192,11 +193,10 @@
             } finally {
                 audioRecord.release();
                 try {
-                    mCallback.onAudioStreamClosed();
+                    clientCallback.onAudioStreamClosed();
                 } catch (RemoteException ignored) {
                     // Ignored.
                 }
-
             }
         });
         // Send the pipe down to the lookup service while we write to it asynchronously.
@@ -207,13 +207,20 @@
      * Callback invoked by {@link android.service.musicrecognition.MusicRecognitionService} to pass
      * back the music search result.
      */
-    private final class MusicRecognitionServiceCallback extends
+    final class MusicRecognitionServiceCallback extends
             IMusicRecognitionServiceCallback.Stub {
+
+        private final IMusicRecognitionManagerCallback mClientCallback;
+
+        private MusicRecognitionServiceCallback(IMusicRecognitionManagerCallback clientCallback) {
+            mClientCallback = clientCallback;
+        }
+
         @Override
         public void onRecognitionSucceeded(MediaMetadata result, Bundle extras) {
             try {
                 sanitizeBundle(extras);
-                mCallback.onRecognitionSucceeded(result, extras);
+                mClientCallback.onRecognitionSucceeded(result, extras);
             } catch (RemoteException ignored) {
                 // Ignored.
             }
@@ -223,18 +230,23 @@
         @Override
         public void onRecognitionFailed(@RecognitionFailureCode int failureCode) {
             try {
-                mCallback.onRecognitionFailed(failureCode);
+                mClientCallback.onRecognitionFailed(failureCode);
             } catch (RemoteException ignored) {
                 // Ignored.
             }
             destroyService();
         }
+
+        private IMusicRecognitionManagerCallback getClientCallback() {
+            return mClientCallback;
+        }
     }
 
     @Override
     public void onServiceDied(@NonNull RemoteMusicRecognitionService service) {
         try {
-            mCallback.onRecognitionFailed(RECOGNITION_FAILED_SERVICE_KILLED);
+            service.getServerCallback().getClientCallback().onRecognitionFailed(
+                    RECOGNITION_FAILED_SERVICE_KILLED);
         } catch (RemoteException e) {
             // Ignored.
         }
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java
index 9123daf..38f43138 100644
--- a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java
@@ -22,7 +22,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.musicrecognition.IMusicRecognitionManager;
 import android.media.musicrecognition.IMusicRecognitionManagerCallback;
 import android.media.musicrecognition.RecognitionRequest;
@@ -32,7 +34,9 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 
@@ -113,9 +117,11 @@
             enforceCaller("beginRecognition");
 
             synchronized (mLock) {
+                int userId = UserHandle.getCallingUserId();
                 final MusicRecognitionManagerPerUserService service = getServiceForUserLocked(
-                        UserHandle.getCallingUserId());
-                if (service != null) {
+                        userId);
+                if (service != null && (isDefaultServiceLocked(userId)
+                        || isCalledByServiceAppLocked("beginRecognition"))) {
                     service.beginRecognitionLocked(recognitionRequest, callback);
                 } else {
                     try {
@@ -139,5 +145,55 @@
                     MusicRecognitionManagerService.this).exec(this, in, out, err, args, callback,
                     resultReceiver);
         }
+
+        /** True if the currently set handler service is not overridden by the shell. */
+        @GuardedBy("mLock")
+        private boolean isDefaultServiceLocked(int userId) {
+            final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId);
+            if (defaultServiceName == null) {
+                return false;
+            }
+
+            final String currentServiceName = mServiceNameResolver.getServiceName(userId);
+            return defaultServiceName.equals(currentServiceName);
+        }
+
+        /** True if the caller of the api is the same app which hosts the default service. */
+        @GuardedBy("mLock")
+        private boolean isCalledByServiceAppLocked(@NonNull String methodName) {
+            final int userId = UserHandle.getCallingUserId();
+            final int callingUid = Binder.getCallingUid();
+            final String serviceName = mServiceNameResolver.getServiceName(userId);
+            if (serviceName == null) {
+                Slog.e(TAG, methodName + ": called by UID " + callingUid
+                        + ", but there's no service set for user " + userId);
+                return false;
+            }
+
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            if (serviceComponent == null) {
+                Slog.w(TAG, methodName + ": invalid service name: " + serviceName);
+                return false;
+            }
+
+            final String servicePackageName = serviceComponent.getPackageName();
+
+            final PackageManager pm = getContext().getPackageManager();
+            final int serviceUid;
+            try {
+                serviceUid = pm.getPackageUidAsUser(servicePackageName,
+                        UserHandle.getCallingUserId());
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.w(TAG, methodName + ": could not verify UID for " + serviceName);
+                return false;
+            }
+            if (callingUid != serviceUid) {
+                Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is "
+                        + serviceUid);
+                return false;
+            }
+
+            return true;
+        }
     }
 }
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
index 4814a82..6c7d673 100644
--- a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
@@ -21,13 +21,13 @@
 import android.content.Context;
 import android.media.AudioFormat;
 import android.media.musicrecognition.IMusicRecognitionService;
-import android.media.musicrecognition.IMusicRecognitionServiceCallback;
 import android.media.musicrecognition.MusicRecognitionService;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.text.format.DateUtils;
 
 import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+import com.android.server.musicrecognition.MusicRecognitionManagerPerUserService.MusicRecognitionServiceCallback;
 
 /** Remote connection to an instance of {@link MusicRecognitionService}. */
 public class RemoteMusicRecognitionService extends
@@ -39,11 +39,12 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 40 * DateUtils.SECOND_IN_MILLIS;
 
     // Allows the remote service to send back a result.
-    private final IMusicRecognitionServiceCallback mServerCallback;
+    private final MusicRecognitionServiceCallback
+            mServerCallback;
 
     public RemoteMusicRecognitionService(Context context, ComponentName serviceName,
             int userId, MusicRecognitionManagerPerUserService perUserService,
-            IMusicRecognitionServiceCallback callback,
+            MusicRecognitionServiceCallback callback,
             boolean bindInstantServiceAllowed, boolean verbose) {
         super(context, MusicRecognitionService.ACTION_MUSIC_SEARCH_LOOKUP, serviceName, userId,
                 perUserService,
@@ -66,6 +67,10 @@
         return TIMEOUT_IDLE_BIND_MILLIS;
     }
 
+    MusicRecognitionServiceCallback getServerCallback() {
+        return mServerCallback;
+    }
+
     /**
      * Required, but empty since we don't need to notify the callback implementation of the request
      * results.
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 2a29674..7036ccf 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -13,7 +13,7 @@
         ":services.net-sources",
     ],
     static_libs: [
-        "netd_aidl_interfaces-platform-java",
+        "netd-client",
         "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 78bcc13..4740df5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -383,27 +383,6 @@
     }
 
     @Test
-    public void testWouldBeReadyWithConnectivityLocked() {
-        final ConnectivityController controller = spy(new ConnectivityController(mService));
-        final JobStatus red = createJobStatus(createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
-
-        doReturn(false).when(controller).isNetworkAvailable(any());
-        assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
-
-        doReturn(true).when(controller).isNetworkAvailable(any());
-        doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(any(),
-                eq(JobStatus.CONSTRAINT_CONNECTIVITY));
-        assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
-
-        doReturn(true).when(controller).isNetworkAvailable(any());
-        doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(any(),
-                eq(JobStatus.CONSTRAINT_CONNECTIVITY));
-        assertTrue(controller.wouldBeReadyWithConnectivityLocked(red));
-    }
-
-    @Test
     public void testEvaluateStateLocked_JobWithoutConnectivity() {
         final ConnectivityController controller = new ConnectivityController(mService);
         final JobStatus red = createJobStatus(createJob().setMinimumLatency(1));
@@ -417,7 +396,9 @@
     @Test
     public void testEvaluateStateLocked_JobWouldBeReady() {
         final ConnectivityController controller = spy(new ConnectivityController(mService));
-        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        doReturn(true).when(controller)
+                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+        doReturn(true).when(controller).isNetworkAvailable(any());
         final JobStatus red = createJobStatus(createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -455,7 +436,8 @@
     @Test
     public void testEvaluateStateLocked_JobWouldNotBeReady() {
         final ConnectivityController controller = spy(new ConnectivityController(mService));
-        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        doReturn(false).when(controller)
+                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
         final JobStatus red = createJobStatus(createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -523,21 +505,26 @@
                 .setAppIdleWhitelist(eq(12345), anyBoolean());
 
         // Both jobs would still be ready. Exception should not be revoked.
-        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        doReturn(true).when(controller)
+                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+        doReturn(true).when(controller).isNetworkAvailable(any());
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, never())
                 .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
 
         // One job is still ready. Exception should not be revoked.
-        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(eq(redOne));
-        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(eq(redTwo));
+        doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(
+                eq(redOne), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+        doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(
+                eq(redTwo), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, never())
                 .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
         assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
 
         // Both jobs are not ready. Exception should be revoked.
-        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        doReturn(false).when(controller)
+                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, times(1))
                 .setAppIdleWhitelist(eq(UID_RED), eq(false));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 05f1ed8..1a65894 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -136,6 +136,8 @@
     @Mock
     private JobSchedulerService mJobSchedulerService;
     @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
     private UsageStatsManagerInternal mUsageStatsManager;
 
     private JobStore mJobStore;
@@ -172,7 +174,7 @@
         doReturn(mUsageStatsManager)
                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
         // Used in JobStatus.
-        doReturn(mock(PackageManagerInternal.class))
+        doReturn(mPackageManagerInternal)
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
         // Used in QuotaController.Handler.
         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
@@ -2377,6 +2379,7 @@
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 30 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 27 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 10 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 12 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 10 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS);
@@ -2414,6 +2417,7 @@
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
         assertEquals(27 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
+        assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitSpecialAdditionMs());
         assertEquals(12 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
         assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -2452,6 +2456,7 @@
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, -1);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1);
@@ -2486,6 +2491,7 @@
         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
         assertEquals(0, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
+        assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs());
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
         assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs());
         assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -2518,6 +2524,7 @@
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
@@ -2542,6 +2549,7 @@
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
+        assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs());
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -3498,6 +3506,37 @@
     }
 
     @Test
+    public void testGetRemainingEJExecutionTimeLocked_SpecialApp() {
+        doReturn(new String[]{SOURCE_PACKAGE}).when(mPackageManagerInternal)
+                .getKnownPackageNames(eq(PackageManagerInternal.PACKAGE_VERIFIER), anyInt());
+        mQuotaController.onSystemServicesReady();
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5),
+                true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
+
+        final long[] limits = mQuotaController.getEJLimitsMs();
+        for (int i = 0; i < limits.length; ++i) {
+            setStandbyBucket(i);
+            assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
+                    i == NEVER_INDEX ? 0
+                            : (limits[i] + mQuotaController.getEjLimitSpecialAdditionMs()
+                                    - 5 * MINUTE_IN_MILLIS),
+                    mQuotaController.getRemainingEJExecutionTimeLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE));
+        }
+    }
+
+    @Test
     public void testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge() {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final long[] limits = mQuotaController.getEJLimitsMs();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
index 9d84715..d64c1b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -711,25 +711,31 @@
                 .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
 
         // Test evaluating something before the current deadline.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any());
         doReturn(true).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
         mTimeController.evaluateStateLocked(jobEarliest);
         inOrder.verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
                         any(), any(), any());
-        // Job goes back to not being ready. Middle is still true, so use that alarm.
+        // Job goes back to not being ready. Middle is still true, but we don't check and actively
+        // defer alarm.
         doReturn(false).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
         mTimeController.evaluateStateLocked(jobEarliest);
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(),
                         eq(TAG_DELAY), any(), any(), any());
-        // Turn middle off. Latest is true, so use that alarm.
+        // Turn middle off. Latest is true, but we don't check and actively defer alarm.
         doReturn(false).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
         mTimeController.evaluateStateLocked(jobMiddle);
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(),
                         eq(TAG_DELAY), any(), any(), any());
     }
 
@@ -768,25 +774,32 @@
                 .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
 
         // Test evaluating something before the current deadline.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
         doReturn(true).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
         mTimeController.evaluateStateLocked(jobEarliest);
         inOrder.verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
                         eq(TAG_DEADLINE), any(), any(), any());
-        // Job goes back to not being ready. Middle is still true, so use that alarm.
+        // Job goes back to not being ready. Middle is still true, but we don't check and actively
+        // defer alarm.
         doReturn(false).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
         mTimeController.evaluateStateLocked(jobEarliest);
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(),
                         eq(TAG_DEADLINE), any(), any(), any());
-        // Turn middle off. Latest is true, so use that alarm.
+        // Turn middle off. Latest is true, but we don't check and actively defer alarm.
         doReturn(false).when(mTimeController)
                 .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
         mTimeController.evaluateStateLocked(jobMiddle);
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(),
                         eq(TAG_DEADLINE), any(), any(), any());
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index df7a445..84bfc9b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -19,10 +19,8 @@
 import static android.app.AppOpsManager.OP_FINE_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-import static android.location.Criteria.ACCURACY_COARSE;
-import static android.location.Criteria.ACCURACY_FINE;
-import static android.location.Criteria.POWER_HIGH;
 import static android.location.LocationRequest.PASSIVE_INTERVAL;
+import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
 import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
 
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
@@ -66,7 +64,8 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
@@ -81,7 +80,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.location.injector.FakeUserInfoHelper;
@@ -115,8 +113,13 @@
     private static final int OTHER_USER = CURRENT_USER + 10;
 
     private static final String NAME = "test";
-    private static final ProviderProperties PROPERTIES = new ProviderProperties(false, false, false,
-            false, true, true, true, POWER_HIGH, ACCURACY_FINE);
+    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
+            .setHasAltitudeSupport(true)
+            .setHasSpeedSupport(true)
+            .setHasBearingSupport(true)
+            .setPowerUsage(POWER_USAGE_HIGH)
+            .setAccuracy(ProviderProperties.ACCURACY_FINE)
+            .build();
     private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
             "mypackage",
             "attribution");
@@ -189,8 +192,14 @@
         assertThat(mManager.getIdentity()).isEqualTo(IDENTITY);
         assertThat(mManager.hasProvider()).isTrue();
 
-        ProviderProperties newProperties = new ProviderProperties(true, true, true,
-                true, false, false, false, POWER_HIGH, ACCURACY_COARSE);
+        ProviderProperties newProperties = new ProviderProperties.Builder()
+                .setHasNetworkRequirement(true)
+                .setHasSatelliteRequirement(true)
+                .setHasCellRequirement(true)
+                .setHasMonetaryCost(true)
+                .setPowerUsage(POWER_USAGE_HIGH)
+                .setAccuracy(ProviderProperties.ACCURACY_COARSE)
+                .build();
         mProvider.setProperties(newProperties);
         assertThat(mManager.getProperties()).isEqualTo(newProperties);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
index daa8a22..99846c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.server.location.provider;
 
-import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST;
+import static android.location.provider.ProviderProperties.ACCURACY_FINE;
+import static android.location.provider.ProviderProperties.POWER_USAGE_LOW;
+import static android.location.provider.ProviderRequest.EMPTY_REQUEST;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -24,17 +26,16 @@
 import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertThrows;
 
-import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationResult;
-import android.location.ProviderProperties;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.server.location.test.FakeProvider;
 import com.android.server.location.test.ProviderListenerCapture;
 
@@ -62,16 +63,14 @@
 
         mRealMock = mock(FakeProvider.FakeProviderInterface.class);
         mRealProvider = new FakeProvider(mRealMock);
-        mMockProvider = new MockLocationProvider(new ProviderProperties(
-                false,
-                false,
-                false,
-                false,
-                true,
-                true,
-                true,
-                Criteria.POWER_LOW,
-                Criteria.ACCURACY_FINE),
+        mMockProvider = new MockLocationProvider(
+                new ProviderProperties.Builder()
+                        .setHasAltitudeSupport(true)
+                        .setHasSpeedSupport(true)
+                        .setHasBearingSupport(true)
+                        .setPowerUsage(POWER_USAGE_LOW)
+                        .setAccuracy(ACCURACY_FINE)
+                        .build(),
                 CallerIdentity.forTest(0, 1, "testpackage", "test"));
 
         mProvider = new MockableLocationProvider(lock);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
index 1eb0386..775bdd5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
@@ -16,9 +16,9 @@
 
 package com.android.server.location.test;
 
+import android.location.provider.ProviderRequest;
 import android.os.Bundle;
 
-import com.android.internal.location.ProviderRequest;
 import com.android.server.location.provider.AbstractLocationProvider;
 
 import java.io.FileDescriptor;
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 3f65a46..a70c510 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 import static org.testng.Assert.assertThrows;
 
+import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -35,6 +36,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.server.LocalServices;
 
@@ -44,6 +46,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.HashSet;
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 public class PlatformCompatTest {
     private static final String PACKAGE_NAME = "my.package";
@@ -70,6 +75,8 @@
                 new PackageManager.NameNotFoundException());
         when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt()))
             .thenReturn(-1);
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+            .thenThrow(new PackageManager.NameNotFoundException());
         mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
         mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
         // Assume userdebug/eng non-final build
@@ -125,6 +132,38 @@
     }
 
     @Test
+    public void testOverrideAtInstallTime() throws Exception {
+        mCompatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithId(1L)
+                .addDisabledChangeWithId(2L)
+                .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.O, 3L)
+                .build();
+        mCompatConfig.forceNonDebuggableFinalForTest(true);
+        mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
+
+        // Before adding overrides.
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isTrue();
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isFalse();
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isTrue();
+
+        // Add overrides.
+        Set<Long> enabled = new HashSet<>();
+        enabled.add(2L);
+        Set<Long> disabled = new HashSet<>();
+        disabled.add(1L);
+        disabled.add(3L);
+        ChangeConfig changeConfig = new ChangeConfig(enabled, disabled);
+        CompatibilityChangeConfig compatibilityChangeConfig =
+                new CompatibilityChangeConfig(changeConfig);
+        mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, PACKAGE_NAME);
+
+        // After adding overrides.
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isFalse();
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isTrue();
+        assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isFalse();
+    }
+
+    @Test
     public void testRegisterListenerToSameIdThrows() throws Exception {
         // Registering a listener to change 1 is successful.
         mPlatformCompat.registerListener(1, mListener1);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1c7da3b..c1b1133 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -116,7 +116,9 @@
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.internal.util.collections.Sets;
@@ -206,6 +208,16 @@
     private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
     private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
 
+    @BeforeClass
+    public static void setUpClass() {
+        Notification.DevFlags.sForceDefaults = true;
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        Notification.DevFlags.sForceDefaults = false;
+    }
+
     @Before
     public void setUp() throws Exception {
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 1d2dcae..6068fdf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -33,6 +34,7 @@
 import android.os.UserHandle;
 import android.test.mock.MockContext;
 import android.util.ArrayMap;
+import android.util.DisplayMetrics;
 import android.util.ExceptionUtils;
 
 import androidx.annotation.NonNull;
@@ -174,6 +176,11 @@
         binder = new MockBinder();
         resources = mock(Resources.class);
         spiedContext = mock(Context.class);
+
+        // Set up density for notification building
+        DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+        displayMetrics.density = 2.25f;
+        when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index 23365f7..5cff208 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -923,6 +924,74 @@
         assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
+    @Test
+    public void stateRecording() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controllerImpl.initialize(testEnvironment, mTestCallback);
+
+        {
+            LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+            assertNull(state.getLastSuggestion());
+            assertTrue(state.getPrimaryProviderStates().isEmpty());
+            assertTrue(state.getSecondaryProviderStates().isEmpty());
+        }
+
+        // State recording and simulate some provider behavior that will show up in the state
+        // recording.
+        controllerImpl.setProviderStateRecordingEnabled(true);
+
+        // Simulate an uncertain event from the primary. This will start the secondary.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+        {
+            LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+            assertNull(state.getLastSuggestion());
+            List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
+                    state.getPrimaryProviderStates();
+            assertEquals(1, primaryProviderStates.size());
+            assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN,
+                    primaryProviderStates.get(0).stateEnum);
+            List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
+                    state.getSecondaryProviderStates();
+            assertEquals(1, secondaryProviderStates.size());
+            assertEquals(PROVIDER_STATE_STARTED_INITIALIZING,
+                    secondaryProviderStates.get(0).stateEnum);
+        }
+
+        // Simulate an uncertain event from the primary. This will start the secondary.
+        mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+        {
+            LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+            assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
+                    state.getLastSuggestion().getZoneIds());
+            List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
+                    state.getPrimaryProviderStates();
+            assertEquals(1, primaryProviderStates.size());
+            assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum);
+            List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
+                    state.getSecondaryProviderStates();
+            assertEquals(2, secondaryProviderStates.size());
+            assertEquals(PROVIDER_STATE_STARTED_CERTAIN, secondaryProviderStates.get(1).stateEnum);
+        }
+
+        controllerImpl.setProviderStateRecordingEnabled(false);
+        {
+            LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+            assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
+                    state.getLastSuggestion().getZoneIds());
+            assertTrue(state.getPrimaryProviderStates().isEmpty());
+            assertTrue(state.getSecondaryProviderStates().isEmpty());
+        }
+    }
+
     private static void assertUncertaintyTimeoutSet(
             LocationTimeZoneProviderController.Environment environment,
             LocationTimeZoneProviderController controller) {
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
index 49c67ea..cb292db 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
@@ -18,6 +18,7 @@
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
@@ -49,6 +50,7 @@
 
 import java.time.Duration;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -174,6 +176,48 @@
         assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY));
     }
 
+    @Test
+    public void stateRecording() {
+        String providerName = "primary";
+        TestLocationTimeZoneProvider provider =
+                new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+        provider.setStateChangeRecordingEnabled(true);
+
+        // initialize()
+        provider.initialize(mProviderListener);
+        provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
+
+        // startUpdates()
+        ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
+        Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+        Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+        provider.startUpdates(config, arbitraryInitializationTimeout,
+                arbitraryInitializationTimeoutFuzz);
+        provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING);
+
+        // Simulate a suggestion event being received.
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .setTimeZoneIds(Arrays.asList("Europe/London"))
+                .build();
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
+        provider.simulateProviderEventReceived(event);
+        provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
+
+        // Simulate an uncertain event being received.
+        event = TimeZoneProviderEvent.createUncertainEvent();
+        provider.simulateProviderEventReceived(event);
+        provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
+
+        // stopUpdates()
+        provider.stopUpdates();
+        provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
+
+        // destroy()
+        provider.destroy();
+        provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED);
+    }
+
     /** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */
     private static class TestProviderListener implements ProviderListener {
 
@@ -257,5 +301,11 @@
         void assertOnDestroyCalled() {
             assertTrue(mOnDestroyCalled);
         }
+
+        void assertLatestRecordedState(@ProviderState.ProviderStateEnum int expectedStateEnum) {
+            List<ProviderState> recordedStates = getRecordedStates();
+            assertEquals(expectedStateEnum,
+                    recordedStates.get(recordedStates.size() - 1).stateEnum);
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e8045e0..fee848b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -720,6 +720,7 @@
         }
         // caller is instrumenting with background activity starts privileges
         callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
+                callerIsInstrumentingWithBackgroundActivityStartPrivileges ? Process.SHELL_UID : -1,
                 callerIsInstrumentingWithBackgroundActivityStartPrivileges);
         // callingUid is the device owner
         doReturn(isCallingUidDeviceOwner).when(mAtm).isDeviceOwner(callingUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 3306e31..2f4c8e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -15,7 +15,6 @@
  */
 
 package com.android.server.wm;
-
 import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -31,6 +30,7 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
+import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
@@ -103,6 +103,7 @@
         mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer");
         mDisplayContent = mock(DisplayContent.class);
         doReturn(true).when(mDisplayContent).isTrusted();
+        mDisplayContent.isDefaultDisplay = true;
         mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks",
                 FEATURE_DEFAULT_TASK_CONTAINER);
         mTaskDisplayAreaList = new ArrayList<>();
@@ -183,13 +184,34 @@
         final DisplayAreaPolicyBuilder.Result defaultPolicy =
                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
                         mRoot, mImeContainer);
-        final List<Feature> features = defaultPolicy.getFeatures();
-        boolean hasOneHandedFeature = false;
-        for (int i = 0; i < features.size(); i++) {
-            hasOneHandedFeature |= features.get(i).getId() == FEATURE_ONE_HANDED;
-        }
+        if (mDisplayContent.isDefaultDisplay) {
+            final List<Feature> features = defaultPolicy.getFeatures();
+            boolean hasOneHandedFeature = false;
+            for (Feature feature : features) {
+                hasOneHandedFeature |= feature.getId() == FEATURE_ONE_HANDED;
+            }
 
-        assertThat(hasOneHandedFeature).isTrue();
+            assertThat(hasOneHandedFeature).isTrue();
+        }
+    }
+
+    @Test
+    public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() {
+        final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
+                resourcesWithProvider(""));
+        final DisplayAreaPolicyBuilder.Result defaultPolicy =
+                (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
+                        mRoot, mImeContainer);
+        if (mDisplayContent.isDefaultDisplay) {
+            final List<Feature> features = defaultPolicy.getFeatures();
+            boolean hasOneHandedBackgroundFeature = false;
+            for (Feature feature : features) {
+                hasOneHandedBackgroundFeature |=
+                        feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL;
+            }
+
+            assertThat(hasOneHandedBackgroundFeature).isTrue();
+        }
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 74248a9..401ace03c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -77,6 +77,7 @@
         changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+        fillChangeMap(changes, newTask);
         // End states.
         closing.mVisibleRequested = false;
         opening.mVisibleRequested = true;
@@ -141,6 +142,7 @@
         changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+        fillChangeMap(changes, newTask);
         // End states.
         closing.mVisibleRequested = false;
         opening.mVisibleRequested = true;
@@ -189,6 +191,8 @@
         changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+        fillChangeMap(changes, tda);
+
         // End states.
         showing.mVisibleRequested = true;
         showing2.mVisibleRequested = true;
@@ -338,4 +342,12 @@
         assertEquals(FLAG_SHOW_WALLPAPER, info.getChange(
                 tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags());
     }
+
+    /** Fill the change map with all the parents of top. Change maps are usually fully populated */
+    private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
+            WindowContainer top) {
+        for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) {
+            changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        }
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index d585b23..afa35fe 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -175,7 +175,10 @@
     // Delay for debouncing USB disconnects.
     // We often get rapid connect/disconnect events when enabling USB functions,
     // which need debouncing.
-    private static final int UPDATE_DELAY = 1000;
+    private static final int DEVICE_STATE_UPDATE_DELAY = 3000;
+
+    // Delay for debouncing USB disconnects on Type-C ports in host mode
+    private static final int HOST_STATE_UPDATE_DELAY = 1000;
 
     // Timeout for entering USB request mode.
     // Request is cancelled if host does not configure device within 10 seconds.
@@ -636,7 +639,7 @@
             msg.arg1 = connected;
             msg.arg2 = configured;
             // debounce disconnects to avoid problems bringing up USB tethering
-            sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
+            sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0);
         }
 
         public void updateHostState(UsbPort port, UsbPortStatus status) {
@@ -651,7 +654,7 @@
             removeMessages(MSG_UPDATE_PORT_STATE);
             Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args);
             // debounce rapid transitions of connect/disconnect on type-c ports
-            sendMessageDelayed(msg, UPDATE_DELAY);
+            sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
         }
 
         private void setAdbEnabled(boolean enable) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 56345c0..7e019cc 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -743,6 +743,14 @@
     public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
 
     /**
+     * Flag specifying whether Cross SIM over IMS should be available for carrier.
+     * When {@code false} the carrier does not support cross SIM calling.
+     * When {@code true} the carrier does support cross sim calling, where available
+     */
+    public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL =
+            "carrier_cross_sim_ims_available_bool";
+
+    /**
      * Specifies a map from dialstrings to replacements for roaming network service numbers which
      * cannot be replaced on the carrier side.
      * <p>
@@ -1497,6 +1505,31 @@
             "wfc_carrier_name_override_by_pnn_bool";
 
     /**
+     * Value for {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING} cnfig.
+     * specifies SPN format of displaying carrier name only.
+     *
+     */
+    public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0;
+
+    /**
+     * Value for {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING} cnfig.
+     * specifies SPN format of displaying carrier name along with "Cross-SIM calling".
+     */
+    public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1;
+
+    /**
+     * Indexes of SPN format strings in crossSimSpnFormats.
+     *
+     * <p>Available options are:
+     * <ul>
+     * <li>  {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY}: %s</li>
+     * <li>  {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING}: %s Cross-SIM Calling</li>
+     * </ul>
+     * %s will be filled with carrier name
+     */
+    public static final String KEY_CROSS_SIM_SPN_FORMAT_INT = "cross_sim_spn_format_int";
+
+    /**
      * Override the SPN Display Condition 2 integer bits (lsb). B2, B1 is the last two bits of the
      * spn display condition coding.
      *
@@ -2373,7 +2406,8 @@
             "show_blocking_pay_phone_option_bool";
 
     /**
-     * Flag specifying whether the carrier will use the WFC home network mode in roaming network.
+     * Flag specifying whether the carrier will use the
+     * WFC home network mode in roaming network.
      * {@code false} - roaming preference can be selected separately from the home preference.
      * {@code true}  - roaming preference is the same as home preference and
      *                 {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value.
@@ -4621,6 +4655,7 @@
         sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
         sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false);
@@ -4794,6 +4829,7 @@
         sDefaults.putBoolean(KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
         sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
         sDefaults.putBoolean(KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL, false);
+        sDefaults.putInt(KEY_CROSS_SIM_SPN_FORMAT_INT, 1);
         sDefaults.putInt(KEY_SPN_DISPLAY_CONDITION_OVERRIDE_INT, -1);
         sDefaults.putStringArray(KEY_SPDI_OVERRIDE_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_PNN_OVERRIDE_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 60389e1..a6e870f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5586,6 +5586,10 @@
      */
     @Deprecated
     public void listen(PhoneStateListener listener, int events) {
+        if (!listener.isExecutorSet()) {
+            throw new IllegalStateException("PhoneStateListener should be created on a thread "
+                    + "with Looper.myLooper() != null");
+        }
         boolean notifyNow = getITelephony() != null;
         mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
         if (mTelephonyRegistryMgr != null) {
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index e217e9a..ed1f3dd 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.DataFailureCause;
@@ -276,9 +277,11 @@
     }
 
     /**
-     * @return The network suggested data retry duration in milliseconds. {@code Long.MAX_VALUE}
-     * indicates data retry should not occur. {@link #RETRY_DURATION_UNDEFINED} indicates network
-     * did not suggest any retry duration.
+     * @return The network suggested data retry duration in milliseconds as specified in
+     * 3GPP TS 24.302 section 8.2.9.1.  The APN associated to this data call will be throttled for
+     * the specified duration unless {@link DataServiceCallback#onApnUnthrottled} is called.
+     * {@code Long.MAX_VALUE} indicates data retry should not occur.
+     * {@link #RETRY_DURATION_UNDEFINED} indicates network did not suggest any retry duration.
      */
     public long getRetryDurationMillis() {
         return mSuggestedRetryTime;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 52bf15f..f56c19b 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -250,7 +250,11 @@
     }
 
     /**
-     * Indicates that the specified APN is no longer throttled.
+     * The APN is throttled for the duration specified in
+     * {@link DataCallResponse#getRetryDurationMillis}.  Calling this method unthrottles that
+     * APN.
+     * <p/>
+     * see: {@link DataCallResponse#getRetryDurationMillis}
      *
      * @param apn Access Point Name defined by the carrier.
      */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index d587f1e..6bf2c85 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -19,8 +19,21 @@
 import android.app.Instrumentation
 import androidx.test.uiautomator.UiDevice
 
-class ImeAppAutoFocusHelper(instr: Instrumentation) : ImeAppHelper(instr, "ImeAppAutoFocus") {
+class ImeAppAutoFocusHelper @JvmOverloads constructor(
+    instr: Instrumentation,
+    private val rotation: Int,
+    private val imePackageName: String = IME_PACKAGE
+) : ImeAppHelper(instr, "ImeAppAutoFocus") {
     override fun openIME(device: UiDevice) {
         // do nothing (the app is focused automatically)
     }
+
+    override fun open() {
+        val expectedPackage = if (rotation.isRotated()) {
+            imePackageName
+        } else {
+            packageName
+        }
+        launcherStrategy.launch(appName, expectedPackage)
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index b341e62..412a3c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -61,10 +61,11 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = ImeAppAutoFocusHelper(instrumentation)
 
             return FlickerTestRunnerFactory(instrumentation)
                 .buildTest { configuration ->
+                    val testApp = ImeAppAutoFocusHelper(instrumentation,
+                        configuration.startRotation)
                     withTag { buildTestTag("imeToAppAutoOpen", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 51a4ca8..60a798f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -61,10 +61,11 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = ImeAppAutoFocusHelper(instrumentation)
 
             return FlickerTestRunnerFactory(instrumentation)
                 .buildTest { configuration ->
+                    val testApp = ImeAppAutoFocusHelper(instrumentation,
+                        configuration.startRotation)
                     withTestName {
                         buildTestTag("imeToHomeAutoOpen", testApp, configuration)
                     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index c7114da..d184273 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -66,11 +66,12 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = ImeAppAutoFocusHelper(instrumentation)
             val testAppComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
 
             return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
                     .buildTest { configuration ->
+                        val testApp = ImeAppAutoFocusHelper(instrumentation,
+                            configuration.startRotation)
                         withTestName { buildTestTag("reOpenImeAutoFocus", testApp, configuration) }
                         repeat { configuration.repetitions }
                         setup {
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index b47be97..cd4cfcf 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -39,6 +40,7 @@
 import android.net.NetworkInfo;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,7 +48,9 @@
 import com.android.internal.R;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.AdditionalAnswers;
@@ -82,6 +86,7 @@
 
     @Mock Context mCtx;
     @Mock Resources mResources;
+    @Mock DisplayMetrics mDisplayMetrics;
     @Mock PackageManager mPm;
     @Mock TelephonyManager mTelephonyManager;
     @Mock NotificationManager mNotificationManager;
@@ -93,6 +98,17 @@
 
     NetworkNotificationManager mManager;
 
+
+    @BeforeClass
+    public static void setUpClass() {
+        Notification.DevFlags.sForceDefaults = true;
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        Notification.DevFlags.sForceDefaults = false;
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -103,6 +119,7 @@
         mCellNai.networkInfo = mNetworkInfo;
         mVpnNai.networkCapabilities = VPN_CAPABILITIES;
         mVpnNai.networkInfo = mNetworkInfo;
+        mDisplayMetrics.density = 2.275f;
         doReturn(true).when(mVpnNai).isVPN();
         when(mCtx.getResources()).thenReturn(mResources);
         when(mCtx.getPackageManager()).thenReturn(mPm);
@@ -114,6 +131,7 @@
                 .thenReturn(mNotificationManager);
         when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
         when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
 
         mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
     }
@@ -136,15 +154,15 @@
     public void testTitleOfPrivateDnsBroken() {
         // Test the title of mobile data.
         verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet);
-        reset(mResources);
+        clearInvocations(mResources);
 
         // Test the title of wifi.
         verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet);
-        reset(mResources);
+        clearInvocations(mResources);
 
         // Test the title of other networks.
         verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet);
-        reset(mResources);
+        clearInvocations(mResources);
     }
 
     @Test
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 77944de..c1ef350 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -18,12 +18,17 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -33,12 +38,15 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnConfigTest {
+    private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName();
     private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
             Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
 
+    private final Context mContext = mock(Context.class);
+
     // Public visibility for VcnManagementServiceTest
-    public static VcnConfig buildTestConfig() {
-        VcnConfig.Builder builder = new VcnConfig.Builder();
+    public static VcnConfig buildTestConfig(@NonNull Context context) {
+        VcnConfig.Builder builder = new VcnConfig.Builder(context);
 
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
             builder.addGatewayConnectionConfig(gatewayConnectionConfig);
@@ -47,10 +55,24 @@
         return builder.build();
     }
 
+    @Before
+    public void setUp() throws Exception {
+        doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
+    }
+
+    @Test
+    public void testBuilderConstructorRequiresContext() {
+        try {
+            new VcnConfig.Builder(null);
+            fail("Expected exception due to null context");
+        } catch (NullPointerException e) {
+        }
+    }
+
     @Test
     public void testBuilderRequiresGatewayConnectionConfig() {
         try {
-            new VcnConfig.Builder().build();
+            new VcnConfig.Builder(mContext).build();
             fail("Expected exception due to no VcnGatewayConnectionConfigs provided");
         } catch (IllegalArgumentException e) {
         }
@@ -58,21 +80,22 @@
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnConfig config = buildTestConfig();
+        final VcnConfig config = buildTestConfig(mContext);
 
+        assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
         assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
     }
 
     @Test
     public void testPersistableBundle() {
-        final VcnConfig config = buildTestConfig();
+        final VcnConfig config = buildTestConfig(mContext);
 
         assertEquals(config, new VcnConfig(config.toPersistableBundle()));
     }
 
     @Test
     public void testParceling() {
-        final VcnConfig config = buildTestConfig();
+        final VcnConfig config = buildTestConfig(mContext);
 
         Parcel parcel = Parcel.obtain();
         config.writeToParcel(parcel, 0);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 1cc9532..696110f 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -16,16 +16,23 @@
 
 package com.android.server;
 
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.vcn.VcnConfig;
@@ -42,29 +49,47 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.vcn.TelephonySubscriptionTracker;
+import com.android.server.vcn.Vcn;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.io.FileNotFoundException;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 /** Tests for {@link VcnManagementService}. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnManagementServiceTest {
+    private static final String TEST_PACKAGE_NAME =
+            VcnManagementServiceTest.class.getPackage().getName();
     private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
     private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
-    private static final VcnConfig TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig();
+    private static final VcnConfig TEST_VCN_CONFIG;
+    private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+
+    static {
+        final Context mockConfigContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();
+
+        TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext);
+    }
+
     private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
             Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
 
+    private static final int TEST_SUBSCRIPTION_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
             new SubscriptionInfo(
-                    1 /* id */,
+                    TEST_SUBSCRIPTION_ID /* id */,
                     "" /* iccId */,
                     0 /* simSlotIndex */,
                     "Carrier" /* displayName */,
@@ -92,22 +117,48 @@
     private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
     private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
     private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class);
-    private final VcnManagementService mVcnMgmtSvc;
+    private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class);
+    private final VcnContext mVcnContext = mock(VcnContext.class);
     private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
             mock(PersistableBundleUtils.LockingReadWriteHelper.class);
+    private final TelephonySubscriptionTracker mSubscriptionTracker =
+            mock(TelephonySubscriptionTracker.class);
+
+    private final VcnManagementService mVcnMgmtSvc;
 
     public VcnManagementServiceTest() throws Exception {
         setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
         setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
         setupSystemService(
                 mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
+        setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
+
+        doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
         doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
-        doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid();
+        doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
+        doReturn(mVcnContext)
+                .when(mMockDeps)
+                .newVcnContext(
+                        eq(mMockContext),
+                        eq(mTestLooper.getLooper()),
+                        any(VcnNetworkProvider.class));
+        doReturn(mSubscriptionTracker)
+                .when(mMockDeps)
+                .newTelephonySubscriptionTracker(
+                        eq(mMockContext),
+                        eq(mTestLooper.getLooper()),
+                        any(TelephonySubscriptionTrackerCallback.class));
         doReturn(mConfigReadWriteHelper)
                 .when(mMockDeps)
                 .newPersistableBundleLockingReadWriteHelper(any());
 
+        // Setup VCN instance generation
+        doAnswer((invocation) -> {
+            // Mock-within a doAnswer is safe, because it doesn't actually run nested.
+            return mock(Vcn.class);
+        }).when(mMockDeps).newVcn(any(), any(), any());
+
         final PersistableBundle bundle =
                 PersistableBundleUtils.fromMap(
                         TEST_VCN_CONFIG_MAP,
@@ -117,6 +168,9 @@
 
         setupMockedCarrierPrivilege(true);
         mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
+
+        // Make sure the profiles are loaded.
+        mTestLooper.dispatchAll();
     }
 
     private void setupSystemService(Object service, String name, Class<?> serviceClass) {
@@ -137,8 +191,8 @@
     public void testSystemReady() throws Exception {
         mVcnMgmtSvc.systemReady();
 
-        verify(mConnMgr)
-                .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
+        verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
+        verify(mSubscriptionTracker).register();
     }
 
     @Test
@@ -171,12 +225,110 @@
         verify(mConfigReadWriteHelper).readFromDisk();
     }
 
+    private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
+        final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
+        doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
+
+        final Set<String> privilegedPackages =
+                (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
+                        ? Collections.emptySet()
+                        : Collections.singleton(TEST_PACKAGE_NAME);
+        doReturn(true)
+                .when(snapshot)
+                .packageHasPermissionsForSubscriptionGroup(
+                        argThat(val -> activeSubscriptionGroups.contains(val)),
+                        eq(TEST_PACKAGE_NAME));
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        cb.onNewSnapshot(snapshot);
+    }
+
+    private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
+        final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor =
+                ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class);
+        verify(mMockDeps)
+                .newTelephonySubscriptionTracker(
+                        eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture());
+        return captor.getValue();
+    }
+
+    private Vcn startAndGetVcnInstance(ParcelUuid uuid) {
+        mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        return mVcnMgmtSvc.getAllVcns().get(uuid);
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
+        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
+        verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        triggerSubscriptionTrackerCallback(Collections.emptySet());
+
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
+            throws Exception {
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCallback(Collections.emptySet());
+
+        // Simulate new SIM loaded right during teardown delay.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
+
+        // Verify that even after the full timeout duration, the VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn, never()).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCallback(Collections.emptySet());
+
+        // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
+        // vcnInstance.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Verify that new instance was different, and the old one was torn down
+        assertTrue(oldInstance != newInstance);
+        verify(oldInstance).teardownAsynchronously();
+
+        // Verify that even after the full timeout duration, the new VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(newInstance, never()).teardownAsynchronously();
+    }
+
     @Test
     public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
         doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
 
         try {
-            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
+            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
             fail("Expected IllegalStateException exception for system server");
         } catch (IllegalStateException expected) {
         }
@@ -184,12 +336,12 @@
 
     @Test
     public void testSetVcnConfigRequiresSystemUser() throws Exception {
-        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
+        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
                 .when(mMockDeps)
                 .getBinderCallingUid();
 
         try {
-            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
+            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
             fail("Expected security exception for non system user");
         } catch (SecurityException expected) {
         }
@@ -200,16 +352,25 @@
         setupMockedCarrierPrivilege(false);
 
         try {
-            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
+            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
             fail("Expected security exception for missing carrier privileges");
         } catch (SecurityException expected) {
         }
     }
 
     @Test
+    public void testSetVcnConfigMismatchedPackages() throws Exception {
+        try {
+            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
+            fail("Expected exception due to mismatched packages in config and method call");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testSetVcnConfig() throws Exception {
         // Use a different UUID to simulate a new VCN config.
-        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG);
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
         assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
         verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
     }
@@ -227,7 +388,7 @@
 
     @Test
     public void testClearVcnConfigRequiresSystemUser() throws Exception {
-        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
+        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
                 .when(mMockDeps)
                 .getBinderCallingUid();
 
@@ -255,4 +416,26 @@
         assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
         verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
     }
+
+    @Test
+    public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception {
+        // Use a different UUID to simulate a new VCN config.
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
+        final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
+        assertEquals(1, vcnInstances.size());
+        assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
+        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
+
+        // Verify Vcn is started
+        verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG));
+
+        // Verify Vcn is updated if it was previously started
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
+
+        // Verify Vcn is stopped if it was already started
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        verify(vcnInstance).teardownAsynchronously();
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 17b8f64..528f240 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -30,6 +30,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -37,6 +38,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonMap;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +54,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
@@ -63,6 +69,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -71,12 +78,16 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TelephonySubscriptionTrackerTest {
+    private static final String PACKAGE_NAME =
+            TelephonySubscriptionTrackerTest.class.getPackage().getName();
     private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
     private static final int TEST_SIM_SLOT_INDEX = 1;
     private static final int TEST_SUBSCRIPTION_ID_1 = 2;
     private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
     private static final int TEST_SUBSCRIPTION_ID_2 = 3;
     private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
+    private static final Map<ParcelUuid, Set<String>> TEST_PRIVILEGED_PACKAGES =
+            Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME));
     private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
 
     static {
@@ -91,6 +102,7 @@
     @NonNull private final Handler mHandler;
     @NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
 
+    @NonNull private final TelephonyManager mTelephonyManager;
     @NonNull private final SubscriptionManager mSubscriptionManager;
     @NonNull private final CarrierConfigManager mCarrierConfigManager;
 
@@ -103,9 +115,15 @@
         mHandler = new Handler(mTestLooper.getLooper());
         mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
 
+        mTelephonyManager = mock(TelephonyManager.class);
         mSubscriptionManager = mock(SubscriptionManager.class);
         mCarrierConfigManager = mock(CarrierConfigManager.class);
 
+        doReturn(Context.TELEPHONY_SERVICE)
+                .when(mContext)
+                .getSystemServiceName(TelephonyManager.class);
+        doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+
         doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
                 .when(mContext)
                 .getSystemServiceName(SubscriptionManager.class);
@@ -140,6 +158,9 @@
         doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
                 .when(mSubscriptionManager)
                 .getAllSubscriptionInfoList();
+
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME));
     }
 
     private IntentFilter getIntentFilter() {
@@ -167,13 +188,15 @@
         return intent;
     }
 
-    private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) {
-        return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups);
+    private TelephonySubscriptionSnapshot buildExpectedSnapshot(
+            Map<ParcelUuid, Set<String>> privilegedPackages) {
+        return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages);
     }
 
     private TelephonySubscriptionSnapshot buildExpectedSnapshot(
-            Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) {
-        return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups);
+            Map<Integer, ParcelUuid> subIdToGroupMap,
+            Map<ParcelUuid, Set<String>> privilegedPackages) {
+        return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages);
     }
 
     private void verifyNoActiveSubscriptions() {
@@ -186,6 +209,10 @@
                 Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
     }
 
+    private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) {
+        doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges();
+    }
+
     @Test
     public void testRegister() throws Exception {
         verify(mContext)
@@ -223,15 +250,30 @@
     }
 
     @Test
-    public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception {
+    public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages()
+            throws Exception {
+        setupReadySubIds();
+        setPrivilegedPackagesForMock(Collections.emptyList());
+
+        final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+        listener.onSubscriptionsChanged();
+        mTestLooper.dispatchAll();
+
+        final Map<ParcelUuid, Set<String>> privilegedPackages =
+                Collections.singletonMap(TEST_PARCEL_UUID, new ArraySet<>());
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(privilegedPackages)));
+    }
+
+    @Test
+    public void testOnSubscriptionsChangedFired_WithReadySubidsAndPrivilegedPackages()
+            throws Exception {
         setupReadySubIds();
 
         final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
         listener.onSubscriptionsChanged();
         mTestLooper.dispatchAll();
 
-        final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
-        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
     }
 
     @Test
@@ -239,8 +281,7 @@
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
         mTestLooper.dispatchAll();
 
-        final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
-        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
     }
 
     @Test
@@ -253,8 +294,7 @@
         mTestLooper.dispatchAll();
 
         // Expect an empty snapshot
-        verify(mCallback).onNewSnapshot(
-                eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
     }
 
     @Test
@@ -281,41 +321,57 @@
 
     @Test
     public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
-        final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
-
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
         mTestLooper.dispatchAll();
-        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
         assertNotNull(
                 mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
 
         doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
         mTestLooper.dispatchAll();
-        verify(mCallback).onNewSnapshot(
-                eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
     }
 
     @Test
     public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
-        final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
-
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
         mTestLooper.dispatchAll();
-        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
         assertNotNull(
                 mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
 
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
         mTestLooper.dispatchAll();
-        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet())));
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap())));
         assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
     }
 
     @Test
+    public void testChangingPrivilegedPackagesAfterValidTriggersCallbacks() throws Exception {
+        setupReadySubIds();
+
+        // Setup initial "valid" state
+        final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+        listener.onSubscriptionsChanged();
+        mTestLooper.dispatchAll();
+
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
+
+        // Simulate a loss of carrier privileges
+        setPrivilegedPackagesForMock(Collections.emptyList());
+        listener.onSubscriptionsChanged();
+        mTestLooper.dispatchAll();
+
+        verify(mCallback)
+                .onNewSnapshot(
+                        eq(buildExpectedSnapshot(singletonMap(TEST_PARCEL_UUID, emptySet()))));
+    }
+
+    @Test
     public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
         final TelephonySubscriptionSnapshot snapshot =
-                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
+                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
 
         assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1));
         assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
@@ -324,7 +380,7 @@
     @Test
     public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
         final TelephonySubscriptionSnapshot snapshot =
-                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
+                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
 
         assertEquals(
                 new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),
diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
new file mode 100644
index 0000000..c2c6200
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.vcn;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for TelephonySubscriptionTracker */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnNetworkProviderTest {
+    private static final int TEST_SCORE_UNSATISFIED = 0;
+    private static final int TEST_SCORE_HIGH = 100;
+    private static final int TEST_PROVIDER_ID = 1;
+    private static final int TEST_LEGACY_TYPE = ConnectivityManager.TYPE_MOBILE;
+    private static final NetworkRequest.Type TEST_REQUEST_TYPE = NetworkRequest.Type.REQUEST;
+
+    @NonNull private final Context mContext;
+    @NonNull private final TestLooper mTestLooper;
+
+    @NonNull private VcnNetworkProvider mVcnNetworkProvider;
+    @NonNull private NetworkRequestListener mListener;
+
+    public VcnNetworkProviderTest() {
+        mContext = mock(Context.class);
+        mTestLooper = new TestLooper();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper());
+        mListener = mock(NetworkRequestListener.class);
+    }
+
+    @Test
+    public void testRequestsPassedToRegisteredListeners() throws Exception {
+        mVcnNetworkProvider.registerListener(mListener);
+
+        final NetworkRequest request = mock(NetworkRequest.class);
+        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verify(mListener).onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+    }
+
+    @Test
+    public void testRequestsPassedToRegisteredListeners_satisfiedByHighScoringProvider()
+            throws Exception {
+        mVcnNetworkProvider.registerListener(mListener);
+
+        final NetworkRequest request = mock(NetworkRequest.class);
+        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID);
+        verify(mListener).onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID);
+    }
+
+    @Test
+    public void testUnregisterListener() throws Exception {
+        mVcnNetworkProvider.registerListener(mListener);
+        mVcnNetworkProvider.unregisterListener(mListener);
+
+        final NetworkRequest request = mock(NetworkRequest.class);
+        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void testCachedRequestsPassedOnRegister() throws Exception {
+        final List<NetworkRequest> requests = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            final NetworkRequest request =
+                    new NetworkRequest(
+                            new NetworkCapabilities(),
+                            TEST_LEGACY_TYPE,
+                            i /* requestId */,
+                            TEST_REQUEST_TYPE);
+
+            requests.add(request);
+            mVcnNetworkProvider.onNetworkRequested(request, i, i + 1);
+        }
+
+        mVcnNetworkProvider.registerListener(mListener);
+        for (int i = 0; i < requests.size(); i++) {
+            final NetworkRequest request = requests.get(i);
+            verify(mListener).onNetworkRequested(request, i, i + 1);
+        }
+        verifyNoMoreInteractions(mListener);
+    }
+}