Merge "Add unit tests for DisposableBroadcastReceiverAsUser"
diff --git a/Android.bp b/Android.bp
index c0a2abb..3d0188a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -108,7 +108,7 @@
         ":android.security.legacykeystore-java-source",
         ":android.security.maintenance-java-source",
         ":android.security.metrics-java-source",
-        ":android.system.keystore2-V1-java-source",
+        ":android.system.keystore2-V3-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
diff --git a/OWNERS b/OWNERS
index d4d1936..09a721f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
 narayan@google.com #{LAST_RESORT_SUGGESTION}
 ogunwale@google.com #{LAST_RESORT_SUGGESTION}
 roosa@google.com #{LAST_RESORT_SUGGESTION}
+smoreland@google.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@android.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@google.com #{LAST_RESORT_SUGGESTION}
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 0c65b99..2d3201a 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -36,7 +36,7 @@
  * Note android.app.job is the better package to put this class, but we can't move it there
  * because that'd break robolectric. Grr.
  *
- * @hide 
+ * @hide
  */
 public class JobSchedulerImpl extends JobScheduler {
     IJobScheduler mBinder;
@@ -109,6 +109,15 @@
     }
 
     @Override
+    public int getPendingJobReason(int jobId) {
+        try {
+            return mBinder.getPendingJobReason(jobId);
+        } catch (RemoteException e) {
+            return PENDING_JOB_REASON_UNDEFINED;
+        }
+    }
+
+    @Override
     public boolean canRunLongJobs() {
         try {
             return mBinder.canRunLongJobs(mContext.getOpPackageName());
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index a3390b7..96494ec 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.app.Notification;
 import android.app.job.JobWorkItem;
 
 /**
@@ -104,4 +105,17 @@
      */
     void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
             long transferredDownloadBytes, long transferredUploadBytes);
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's
+     * lifecycle.
+     * This is required for all user-initiated job and optional for other jobs.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param notificationId The ID for this notification, as per
+     *                       {@link android.app.NotificationManager#notify(int, Notification)}.
+     * @param notification The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     */
+    void setNotification(int jobId, int notificationId,
+            in Notification notification, int jobEndNotificationPolicy);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index d2be32e..bf29dc9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -33,6 +33,7 @@
     void cancelAll();
     ParceledListSlice getAllPendingJobs();
     JobInfo getPendingJob(int jobId);
+    int getPendingJobReason(int jobId);
     boolean canRunLongJobs(String packageName);
     boolean hasRunLongJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 13b6652..659db9f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,10 +23,14 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -133,6 +137,132 @@
      */
     public static final int RESULT_SUCCESS = 1;
 
+    /** The job doesn't exist. */
+    public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
+    /** The job is currently running and is therefore not pending. */
+    public static final int PENDING_JOB_REASON_EXECUTING = -1;
+    /**
+     * There is no known reason why the job is pending.
+     * If additional reasons are added on newer Android versions, the system may return this reason
+     * to apps whose target SDK is not high enough to expect that reason.
+     */
+    public static final int PENDING_JOB_REASON_UNDEFINED = 0;
+    /**
+     * The app is in a state that prevents the job from running
+     * (eg. the {@link JobService} component is disabled).
+     */
+    public static final int PENDING_JOB_REASON_APP = 1;
+    /**
+     * The current standby bucket prevents the job from running.
+     *
+     * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+     */
+    public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
+    /**
+     * The app is restricted from running in the background.
+     *
+     * @see ActivityManager#isBackgroundRestricted()
+     * @see PackageManager#isInstantApp()
+     */
+    public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
+    /**
+     * The requested battery-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
+    /**
+     * The requested charging constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresCharging(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
+    /**
+     * The requested connectivity constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
+    /**
+     * The requested content trigger constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
+    /**
+     * The requested idle constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+    /**
+     * The minimum latency has not transpired.
+     *
+     * @see JobInfo.Builder#setMinimumLatency(long)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
+    /**
+     * The system's estimate of when the app will be launched is far away enough to warrant delaying
+     * this job.
+     *
+     * @see JobInfo#isPrefetch()
+     * @see JobInfo.Builder#setPrefetch(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
+    /**
+     * The requested storage-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
+    /**
+     * The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
+     * thermal status, etc.).
+     */
+    public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
+    /**
+     * JobScheduler thinks it can defer this job to a more optimal running time.
+     */
+    public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
+    /**
+     * The app has consumed all of its current quota.
+     *
+     * @see UsageStatsManager#getAppStandbyBucket()
+     * @see JobParameters#STOP_REASON_QUOTA
+     */
+    public static final int PENDING_JOB_REASON_QUOTA = 14;
+    /**
+     * JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
+     * to defer this job.
+     */
+    public static final int PENDING_JOB_REASON_USER = 15;
+
+    /** @hide */
+    @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
+            PENDING_JOB_REASON_UNDEFINED,
+            PENDING_JOB_REASON_APP,
+            PENDING_JOB_REASON_APP_STANDBY,
+            PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
+            PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+            PENDING_JOB_REASON_CONSTRAINT_CHARGING,
+            PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
+            PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
+            PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
+            PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
+            PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
+            PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+            PENDING_JOB_REASON_DEVICE_STATE,
+            PENDING_JOB_REASON_EXECUTING,
+            PENDING_JOB_REASON_INVALID_JOB_ID,
+            PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
+            PENDING_JOB_REASON_QUOTA,
+            PENDING_JOB_REASON_USER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PendingJobReason {
+    }
+
     /**
      * Schedule a job to be executed.  Will replace any currently scheduled job with the same
      * ID with the new information in the {@link JobInfo}.  If a job with the given ID is currently
@@ -250,6 +380,15 @@
     public abstract @Nullable JobInfo getPendingJob(int jobId);
 
     /**
+     * Returns a reason why the job is pending and not currently executing. If there are multiple
+     * reasons why a job may be pending, this will only return one of them.
+     */
+    @PendingJobReason
+    public int getPendingJobReason(int jobId) {
+        return PENDING_JOB_REASON_UNDEFINED;
+    }
+
+    /**
      * Returns {@code true} if the calling app currently holds the
      * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
      */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index bad641c..e88e979 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -19,13 +19,18 @@
 import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
 
 import android.annotation.BytesLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
 import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.IBinder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
  * <p>This is the base class that handles asynchronous requests that were previously scheduled. You
@@ -63,6 +68,32 @@
     public static final String PERMISSION_BIND =
             "android.permission.BIND_JOB_SERVICE";
 
+    /**
+     * Detach the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will remain shown even after JobScheduler stops the job.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
+    /**
+     * Cancel and remove the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will be removed from the notification shade.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = {
+            JOB_END_NOTIFICATION_POLICY_DETACH,
+            JOB_END_NOTIFICATION_POLICY_REMOVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JobEndNotificationPolicy {
+    }
+
     private JobServiceEngine mEngine;
 
     /** @hide */
@@ -84,9 +115,9 @@
                 public long getTransferredDownloadBytes(@NonNull JobParameters params,
                         @Nullable JobWorkItem item) {
                     if (item == null) {
-                        return JobService.this.getTransferredDownloadBytes();
+                        return JobService.this.getTransferredDownloadBytes(params);
                     } else {
-                        return JobService.this.getTransferredDownloadBytes(item);
+                        return JobService.this.getTransferredDownloadBytes(params, item);
                     }
                 }
 
@@ -95,9 +126,9 @@
                 public long getTransferredUploadBytes(@NonNull JobParameters params,
                         @Nullable JobWorkItem item) {
                     if (item == null) {
-                        return JobService.this.getTransferredUploadBytes();
+                        return JobService.this.getTransferredUploadBytes(params);
                     } else {
-                        return JobService.this.getTransferredUploadBytes(item);
+                        return JobService.this.getTransferredUploadBytes(params, item);
                     }
                 }
             };
@@ -274,7 +305,7 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredDownloadBytes() {
+    public long getTransferredDownloadBytes(@NonNull JobParameters params) {
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
             // non-data transfer jobs.
@@ -298,7 +329,7 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredUploadBytes() {
+    public long getTransferredUploadBytes(@NonNull JobParameters params) {
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
             // non-data transfer jobs.
@@ -324,9 +355,10 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+    public long getTransferredDownloadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
         if (item == null) {
-            return getTransferredDownloadBytes();
+            return getTransferredDownloadBytes(params);
         }
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -353,9 +385,10 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+    public long getTransferredUploadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
         if (item == null) {
-            return getTransferredUploadBytes();
+            return getTransferredUploadBytes(params);
         }
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -364,4 +397,37 @@
         }
         return 0;
     }
+
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's lifecycle.
+     * This is required for all user-initiated jobs
+     * (scheduled via {link JobInfo.Builder#setUserInitiated(boolean)}) and optional for
+     * other jobs. If the app does not call this method for a required notification within
+     * 10 seconds after {@link #onStartJob(JobParameters)} is called,
+     * the system will trigger an ANR and stop this job.
+     *
+     * <p>
+     * Note that certain types of jobs
+     * (e.g. {@link JobInfo.Builder#setDataTransfer data transfer jobs}) may require the
+     * notification to have certain characteristics and their documentation will state
+     * any such requirements.
+     *
+     * <p>
+     * JobScheduler will not remember this notification after the job has finished running,
+     * so apps must call this every time the job is started (if required or desired).
+     *
+     * @param params                   The parameters identifying this job, as supplied to
+     *                                 the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param notificationId           The ID for this notification, as per
+     *                                 {@link android.app.NotificationManager#notify(int,
+     *                                 Notification)}.
+     * @param notification             The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     * @hide
+     */
+    public final void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy);
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 83296a6..53e452f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -21,6 +21,7 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
 import android.compat.Compatibility;
 import android.content.Intent;
@@ -73,6 +74,8 @@
     private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
     /** Message that the client wants to update JobScheduler of the estimated transfer size. */
     private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
+    /** Message that the client wants to give JobScheduler a notification to tie to the job. */
+    private static final int MSG_SET_NOTIFICATION = 7;
 
     private final IJobService mBinder;
 
@@ -250,6 +253,24 @@
                     args.recycle();
                     break;
                 }
+                case MSG_SET_NOTIFICATION: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final Notification notification = (Notification) args.arg2;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.setNotification(params.getJobId(),
+                                    args.argi1, notification, args.argi2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error providing notification: binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG, "setNotification() called for a nonexistent job.");
+                    }
+                    args.recycle();
+                    break;
+                }
                 default:
                     Log.e(TAG, "Unrecognised message received.");
                     break;
@@ -432,4 +453,27 @@
         args.argl2 = uploadBytes;
         mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
     }
-}
\ No newline at end of file
+
+    /**
+     * Give JobScheduler a notification to tie to this job's lifecycle.
+     *
+     * @hide
+     * @see JobService#setNotification(JobParameters, int, Notification, int)
+     */
+    public void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        if (notification == null) {
+            throw new NullPointerException("notification");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = notification;
+        args.argi1 = notificationId;
+        args.argi2 = jobEndNotificationPolicy;
+        mHandler.obtainMessage(MSG_SET_NOTIFICATION, args).sendToTarget();
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index f9dd0b3..47f6890 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1175,7 +1175,7 @@
 
             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
                     && restriction.isJobRestricted(jobStatus)) {
-                jsc.cancelExecutingJobLocked(restriction.getReason(),
+                jsc.cancelExecutingJobLocked(restriction.getStopReason(),
                         restriction.getInternalReason(),
                         JobParameters.getInternalReasonCodeDescription(
                                 restriction.getInternalReason()));
@@ -1208,7 +1208,7 @@
                 final JobRestriction restriction = mService.checkIfRestricted(running);
                 if (restriction != null) {
                     final int internalReasonCode = restriction.getInternalReason();
-                    serviceContext.cancelExecutingJobLocked(restriction.getReason(),
+                    serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
                             internalReasonCode,
                             "restricted due to "
                                     + JobParameters.getInternalReasonCodeDescription(
@@ -1324,6 +1324,7 @@
                 mActivePkgStats.add(
                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
                         packageStats);
+                mService.resetPendingJobReasonCache(jobStatus);
             }
             if (mService.getPendingJobQueue().remove(jobStatus)) {
                 mService.mJobPackageTracker.noteNonpending(jobStatus);
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 ee08f85..9fb2af7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -363,6 +363,9 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
 
+    @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
+    private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+
     /**
      * Named indices into standby bucket arrays, for clarity in referring to
      * specific buckets' bookkeeping.
@@ -1357,6 +1360,134 @@
         }
     }
 
+    @JobScheduler.PendingJobReason
+    private int getPendingJobReason(int uid, int jobId) {
+        int reason;
+        // Some apps may attempt to query this frequently, so cache the reason under a separate lock
+        // so that the rest of JS processing isn't negatively impacted.
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason != null) {
+                reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+                if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
+                    return reason;
+                }
+            }
+        }
+        synchronized (mLock) {
+            reason = getPendingJobReasonLocked(uid, jobId);
+            if (DEBUG) {
+                Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+            }
+        }
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason == null) {
+                jobIdToReason = new SparseIntArray();
+                mPendingJobReasonCache.put(uid, jobIdToReason);
+            }
+            jobIdToReason.put(jobId, reason);
+        }
+        return reason;
+    }
+
+    @JobScheduler.PendingJobReason
+    @GuardedBy("mLock")
+    private int getPendingJobReasonLocked(int uid, int jobId) {
+        // Very similar code to isReadyToBeExecutedLocked.
+
+        JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+        if (job == null) {
+            // Job doesn't exist.
+            return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+        }
+
+        if (isCurrentlyRunningLocked(job)) {
+            return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+        }
+
+        final boolean jobReady = job.isReady();
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " ready=" + jobReady);
+        }
+
+        if (!jobReady) {
+            return job.getPendingJobReason();
+        }
+
+        final boolean userStarted = areUsersStartedLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " userStarted=" + userStarted);
+        }
+        if (!userStarted) {
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+
+        final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " backingUp=" + backingUp);
+        }
+
+        if (backingUp) {
+            // TODO: Should we make a special reason for this?
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        JobRestriction restriction = checkIfRestricted(job);
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " restriction=" + restriction);
+        }
+        if (restriction != null) {
+            return restriction.getPendingReason();
+        }
+
+        // The following can be a little more expensive (especially jobActive, since we need to
+        // go through the array of all potentially active jobs), so we are doing them
+        // later...  but still before checking with the package manager!
+        final boolean jobPending = mPendingJobQueue.contains(job);
+
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " pending=" + jobPending);
+        }
+
+        if (jobPending) {
+            // We haven't started the job for some reason. Presumably, there are too many jobs
+            // running.
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+
+        final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " active=" + jobActive);
+        }
+        if (jobActive) {
+            return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+        }
+
+        // Validate that the defined package+service is still present & viable.
+        final boolean componentUsable = isComponentUsable(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " componentUsable=" + componentUsable);
+        }
+        if (!componentUsable) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
     public JobInfo getPendingJob(int uid, int jobId) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
@@ -1389,6 +1520,9 @@
         synchronized (mLock) {
             mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
         }
+        synchronized (mPendingJobReasonCache) {
+            mPendingJobReasonCache.clear();
+        }
     }
 
     private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
@@ -1874,6 +2008,8 @@
         jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
         final boolean update = lastJob != null;
         mJobs.add(jobStatus);
+        // Clear potentially cached INVALID_JOB_ID reason.
+        resetPendingJobReasonCache(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 StateController controller = mControllers.get(i);
@@ -1895,6 +2031,13 @@
         // Deal with any remaining work items in the old job.
         jobStatus.stopTrackingJobLocked(incomingJob);
 
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasonCache != null) {
+                reasonCache.delete(jobStatus.getJobId());
+            }
+        }
+
         // Remove from store as well as controllers.
         final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
         if (!removed) {
@@ -1917,6 +2060,16 @@
         return removed;
     }
 
+    /** Remove the pending job reason for this job from the cache. */
+    void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
+        synchronized (mPendingJobReasonCache) {
+            final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasons != null) {
+                reasons.delete(jobStatus.getJobId());
+            }
+        }
+    }
+
     /** Return {@code true} if the specified job is currently executing. */
     @GuardedBy("mLock")
     public boolean isCurrentlyRunningLocked(JobStatus job) {
@@ -2210,11 +2363,20 @@
     public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
         if (changedJobs == null) {
             mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                mPendingJobReasonCache.clear();
+            }
         } else if (changedJobs.size() > 0) {
             synchronized (mLock) {
                 mChangedJobList.addAll(changedJobs);
             }
             mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                for (int i = changedJobs.size() - 1; i >= 0; --i) {
+                    final JobStatus job = changedJobs.valueAt(i);
+                    resetPendingJobReasonCache(job);
+                }
+            }
         }
     }
 
@@ -2568,6 +2730,23 @@
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
                 }
+                final int numRunnableJobs = runnableJobs.size();
+                if (numRunnableJobs > 0) {
+                    synchronized (mPendingJobReasonCache) {
+                        for (int i = 0; i < numRunnableJobs; ++i) {
+                            final JobStatus job = runnableJobs.get(i);
+                            SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+                            if (reasons == null) {
+                                reasons = new SparseIntArray();
+                                mPendingJobReasonCache.put(job.getUid(), reasons);
+                            }
+                            // We're force batching these jobs, so consider it an optimization
+                            // policy reason.
+                            reasons.put(job.getJobId(),
+                                    JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+                        }
+                    }
+                }
             }
 
             // Be ready for next time
@@ -3370,6 +3549,18 @@
         }
 
         @Override
+        public int getPendingJobReason(int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public JobInfo getPendingJob(int jobId) throws RemoteException {
             final int uid = Binder.getCallingUid();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9aa6b1c..b20eedc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -17,6 +17,8 @@
 package com.android.server.job;
 
 import static android.app.job.JobInfo.getPriorityString;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
 
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -24,6 +26,7 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
 import android.app.job.JobInfo;
@@ -58,6 +61,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
+import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.tare.EconomicPolicy;
 import com.android.server.tare.EconomyManagerInternal;
 import com.android.server.tare.JobSchedulerEconomicPolicy;
@@ -117,6 +121,7 @@
     private final EconomyManagerInternal mEconomyManagerInternal;
     private final JobPackageTracker mJobPackageTracker;
     private final PowerManager mPowerManager;
+    private final NotificationManagerInternal mNotificationManagerInternal;
     private PowerManager.WakeLock mWakeLock;
 
     // Execution state.
@@ -169,6 +174,11 @@
     /** The absolute maximum amount of time the job can run */
     private long mMaxExecutionTimeMillis;
 
+    private int mNotificationId;
+    private Notification mNotification;
+    private int mNotificationPid;
+    private int mNotificationJobStopPolicy;
+
     /**
      * The stop reason for a pending cancel. If there's not pending cancel, then the value should be
      * {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -235,6 +245,12 @@
                 long downloadBytes, long uploadBytes) {
             doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
         }
+
+        @Override
+        public void setNotification(int jobId, int notificationId,
+                Notification notification, int jobEndNotificationPolicy) {
+            doSetNotification(this, jobId, notificationId, notification, jobEndNotificationPolicy);
+        }
     }
 
     JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -244,6 +260,7 @@
         mService = service;
         mBatteryStats = batteryStats;
         mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
+        mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
         mJobPackageTracker = tracker;
         mCallbackHandler = new JobServiceHandler(looper);
         mJobConcurrencyManager = concurrencyManager;
@@ -593,6 +610,49 @@
         }
     }
 
+    private void doSetNotification(JobCallback cb, int jodId, int notificationId,
+            Notification notification, int jobEndNotificationPolicy) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                if (!verifyCallerLocked(cb)) {
+                    return;
+                }
+                if (callingUid != mRunningJob.getUid()) {
+                    Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID...");
+                    throw new SecurityException("Can't post notification on behalf of another app");
+                }
+                if (notification == null) {
+                    throw new NullPointerException("notification");
+                }
+                if (notification.getSmallIcon() == null) {
+                    throw new IllegalArgumentException("small icon required");
+                }
+                final String callingPkgName = mRunningJob.getServiceComponent().getPackageName();
+                if (null == mNotificationManagerInternal.getNotificationChannel(
+                        callingPkgName, callingUid, notification.getChannelId())) {
+                    throw new IllegalArgumentException("invalid notification channel");
+                }
+                if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH
+                        && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) {
+                    throw new IllegalArgumentException("invalid job end notification policy");
+                }
+                // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
+                mNotificationManagerInternal.enqueueNotification(
+                        callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null,
+                        notificationId, notification, UserHandle.getUserId(callingUid));
+                mNotificationId = notificationId;
+                mNotification = notification;
+                mNotificationPid = callingPid;
+                mNotificationJobStopPolicy = jobEndNotificationPolicy;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
             @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
         // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
@@ -1116,6 +1176,13 @@
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                     String.valueOf(mRunningJob.getJobId()));
         }
+        if (mNotification != null
+                && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+            final String callingPkgName = completedJob.getServiceComponent().getPackageName();
+            mNotificationManagerInternal.cancelNotification(
+                    callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid,
+                    /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid()));
+        }
         if (mWakeLock != null) {
             mWakeLock.release();
         }
@@ -1133,6 +1200,7 @@
         mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
         mPendingInternalStopReason = 0;
         mPendingDebugStopReason = null;
+        mNotification = null;
         removeOpTimeOutLocked();
         mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
@@ -1157,6 +1225,7 @@
     private void scheduleOpTimeOutLocked() {
         removeOpTimeOutLocked();
 
+        // TODO(260848384): enforce setNotification timeout for user-initiated jobs
         final long timeoutMillis;
         switch (mVerb) {
             case VERB_EXECUTING:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 145ac52..a1153e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -91,8 +91,11 @@
 
     /** Threshold to adjust how often we want to write to the db. */
     private static final long JOB_PERSIST_DELAY = 2000L;
-    private static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+    @VisibleForTesting
+    static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
     private static final int ALL_UIDS = -1;
+    @VisibleForTesting
+    static final int INVALID_UID = -2;
 
     final Object mLock;
     final Object mWriteScheduleLock;    // used solely for invariants around write scheduling
@@ -529,6 +532,25 @@
         return values;
     }
 
+    @VisibleForTesting
+    static int extractUidFromJobFileName(@NonNull File file) {
+        final String fileName = file.getName();
+        if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) {
+            try {
+                final int subEnd = fileName.length() - 4; // -4 for ".xml"
+                final int uid = Integer.parseInt(
+                        fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd));
+                if (uid < 0) {
+                    return INVALID_UID;
+                }
+                return uid;
+            } catch (Exception e) {
+                Slog.e(TAG, "Unexpected file name format", e);
+            }
+        }
+        return INVALID_UID;
+    }
+
     /**
      * Runnable that writes {@link #mJobSet} out to xml.
      * NOTE: This Runnable locks on mLock
@@ -543,6 +565,42 @@
 
             private void prepare() {
                 mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+                if (mUseSplitFiles) {
+                    // Put the set of changed UIDs in the copy list so that we update each file,
+                    // especially if we've dropped all jobs for that UID.
+                    if (mPendingJobWriteUids.get(ALL_UIDS)) {
+                        // ALL_UIDS is only used when we switch file splitting policy or for tests,
+                        // so going through the file list here shouldn't be
+                        // a large performance hit on user devices.
+
+                        final File[] files;
+                        try {
+                            files = mJobFileDirectory.listFiles();
+                        } catch (SecurityException e) {
+                            Slog.wtf(TAG, "Not allowed to read job file directory", e);
+                            return;
+                        }
+                        if (files == null) {
+                            Slog.wtfStack(TAG, "Couldn't get job file list");
+                        } else {
+                            for (File file : files) {
+                                final int uid = extractUidFromJobFileName(file);
+                                if (uid != INVALID_UID) {
+                                    mJobStoreCopy.put(uid, new ArrayList<>());
+                                }
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i < mPendingJobWriteUids.size(); ++i) {
+                            mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>());
+                        }
+                    }
+                } else {
+                    // Single file mode.
+                    // Put the catchall UID in the copy list so that we update the single file,
+                    // especially if we've dropped all persisted jobs.
+                    mJobStoreCopy.put(ALL_UIDS, new ArrayList<>());
+                }
             }
 
             @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index da20684..af8e727 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -32,6 +32,7 @@
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
@@ -106,6 +107,13 @@
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
     static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
 
+    private static final int IMPLICIT_CONSTRAINTS = 0
+            | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
+            | CONSTRAINT_DEVICE_NOT_DOZING
+            | CONSTRAINT_FLEXIBLE
+            | CONSTRAINT_TARE_WEALTH
+            | CONSTRAINT_WITHIN_QUOTA;
+
     // The following set of dynamic constraints are for specific use cases (as explained in their
     // relative naming and comments). Right now, they apply different constraints, which is fine,
     // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
@@ -1613,6 +1621,101 @@
         }
     }
 
+    /**
+     * If {@link #isReady()} returns false, this will return a single reason why the job isn't
+     * ready. If {@link #isReady()} returns true, this will return
+     * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+     */
+    @JobScheduler.PendingJobReason
+    public int getPendingJobReason() {
+        final int unsatisfiedConstraints = ~satisfiedConstraints
+                & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+        if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
+            // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
+            // the app is background restricted, or because we're restricting background work
+            // in battery saver. Assume that background restriction is the reason apps that
+            // jobs are not ready, and battery saver otherwise.
+            // This has the benefit of being consistent for background restricted apps
+            // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+            // battery saver state.
+            if (mIsUserBgRestricted) {
+                return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+            }
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
+        }
+        if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
+        }
+        if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
+        }
+        if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+        }
+        if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+        }
+        if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+        if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+        }
+        if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+
+        if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+            Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+            // The user hasn't officially launched this app.
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+        if (serviceProcessName != null) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        if (!isReady()) {
+            Slog.wtf(TAG, "Unknown reason job isn't ready");
+        }
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
     /** @return whether or not the @param constraint is satisfied */
     public boolean isConstraintSatisfied(int constraint) {
         return (satisfiedConstraints&constraint) != 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 4067541..7aab67a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
@@ -28,20 +29,23 @@
  * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
  * should be scheduled or not based on the state of the system/device.
  * Every restriction is associated with exactly one stop reason, which could be retrieved using
- * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
+ * {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
+ * (and the internal reason via {@link #getInternalReason()}).
  * Note, that this is not taken into account for the jobs that have
  * {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
  */
 public abstract class JobRestriction {
 
     final JobSchedulerService mService;
-    private final int mReason;
+    private final int mStopReason;
+    private final int mPendingReason;
     private final int mInternalReason;
 
-    JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
-            int internalReason) {
+    protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
+            @JobScheduler.PendingJobReason int pendingReason, int internalReason) {
         mService = service;
-        mReason = reason;
+        mPendingReason = pendingReason;
+        mStopReason = stopReason;
         mInternalReason = internalReason;
     }
 
@@ -70,10 +74,15 @@
     public void dumpConstants(ProtoOutputStream proto) {
     }
 
-    /** @return reason code for the Restriction. */
+    @JobScheduler.PendingJobReason
+    public final int getPendingReason() {
+        return mPendingReason;
+    }
+
+    /** @return stop reason code for the Restriction. */
     @JobParameters.StopReason
-    public final int getReason() {
-        return mReason;
+    public final int getStopReason() {
+        return mStopReason;
     }
 
     public final int getInternalReason() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index ca2fd60..830031e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.os.PowerManager;
 import android.os.PowerManager.OnThermalStatusChangedListener;
 import android.util.IndentingPrintWriter;
@@ -42,6 +43,7 @@
 
     public ThermalStatusRestriction(JobSchedulerService service) {
         super(service, JobParameters.STOP_REASON_DEVICE_STATE,
+                JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
     }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 0f34e42..434b60d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8485,7 +8485,26 @@
     method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
     method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
     method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
+    method public int getPendingJobReason(int);
     method public abstract int schedule(@NonNull android.app.job.JobInfo);
+    field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
+    field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
+    field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
+    field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
+    field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
+    field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
+    field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
+    field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
+    field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
+    field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
   }
@@ -10347,6 +10366,7 @@
     field @Deprecated @RequiresPermission("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS") public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+    field public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
     field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
     field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -10595,6 +10615,7 @@
     field public static final String EXTRA_UID = "android.intent.extra.UID";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+    field public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
     field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11669,7 +11690,7 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
-    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
+    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
     method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
     method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -20181,6 +20202,15 @@
 
 }
 
+package android.location.altitude {
+
+  public final class AltitudeConverter {
+    ctor public AltitudeConverter();
+    method @WorkerThread public void addMslAltitude(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+  }
+
+}
+
 package android.location.provider {
 
   public final class ProviderProperties implements android.os.Parcelable {
@@ -38126,6 +38156,7 @@
     field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
     field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
     field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+    field public static final int RETRY_AFTER_NEXT_REBOOT = 4; // 0x4
     field public static final int RETRY_NEVER = 1; // 0x1
     field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
     field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b9182bc..3efb0c6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -419,10 +419,38 @@
   }
 
   public final class DeviceConfig {
+    field public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
     field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
     field public static final String NAMESPACE_APP_CLONING = "app_cloning";
     field public static final String NAMESPACE_APP_STANDBY = "app_standby";
+    field public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
+    field public static final String NAMESPACE_CONFIGURATION = "configuration";
+    field public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER = "connectivity_thermal_power_manager";
+    field public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
+    field public static final String NAMESPACE_DEVICE_POLICY_MANAGER = "device_policy_manager";
+    field public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+    field public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS = "intelligence_content_suggestions";
+    field public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+    field public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
+    field public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
+    field public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
+    field public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE = "remote_key_provisioning_native";
+    field public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
+    field public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
+    field public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
+    field public static final String NAMESPACE_TARE = "tare";
+    field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
+    field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+    field public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native";
+    field public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
+    field public static final String NAMESPACE_WEAR = "wear";
+    field public static final String NAMESPACE_WIDGET = "widget";
+    field public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
+  }
+
+  public static class DeviceConfig.Properties {
+    ctor public DeviceConfig.Properties(@NonNull String, @Nullable java.util.Map<java.lang.String,java.lang.String>);
   }
 
   public final class Settings {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3d5139b..2e2ee23 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3010,6 +3010,7 @@
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
     method public int getDefaultActivityPolicy();
     method public int getDefaultNavigationPolicy();
+    method public int getDefaultRecentsPolicy();
     method public int getDevicePolicy(int);
     method public int getLockState();
     method @Nullable public String getName();
@@ -3026,6 +3027,7 @@
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
+    field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
   }
 
   public static final class VirtualDeviceParams.Builder {
@@ -3036,6 +3038,7 @@
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
@@ -10704,7 +10707,7 @@
   }
 
   public final class DeviceConfig {
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+    method public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
@@ -10712,17 +10715,22 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
+    method @NonNull public static java.util.List<java.lang.String> getPublicNamespaces();
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
     field public static final String NAMESPACE_ADSERVICES = "adservices";
     field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
+    field public static final String NAMESPACE_ANDROID = "android";
     field public static final String NAMESPACE_APPSEARCH = "appsearch";
     field public static final String NAMESPACE_APP_COMPAT = "app_compat";
+    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
     field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
     field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
     field public static final String NAMESPACE_AUTOFILL = "autofill";
@@ -10734,13 +10742,16 @@
     field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
     field public static final String NAMESPACE_CLIPBOARD = "clipboard";
     field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
+    field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
     field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
     field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
     field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
     field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
     field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
+    field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
     field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
     field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
+    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
     field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
     field public static final String NAMESPACE_LOCATION = "location";
     field public static final String NAMESPACE_MEDIA = "media";
@@ -10762,6 +10773,7 @@
     field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
     field public static final String NAMESPACE_SCHEDULER = "scheduler";
     field public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
+    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
     field public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
     field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
     field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 25c4652..43d4530 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1511,13 +1511,33 @@
     method public static boolean isEncodingLinearPcm(int);
   }
 
+  public final class AudioHalVersionInfo implements java.lang.Comparable<android.media.AudioHalVersionInfo> android.os.Parcelable {
+    method public int compareTo(@NonNull android.media.AudioHalVersionInfo);
+    method public int describeContents();
+    method public int getHalType();
+    method public int getMajorVersion();
+    method public int getMinorVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.media.AudioHalVersionInfo AIDL_1_0;
+    field public static final int AUDIO_HAL_TYPE_AIDL = 1; // 0x1
+    field public static final int AUDIO_HAL_TYPE_HIDL = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioHalVersionInfo> CREATOR;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_2_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_4_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_5_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_6_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_1;
+    field @NonNull public static final java.util.List<android.media.AudioHalVersionInfo> VERSIONS;
+  }
+
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
     method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
     method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
-    method @Nullable public static String getHalVersion();
+    method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
     method public int getStreamMinVolumeInt(int);
@@ -1719,7 +1739,6 @@
   public class Build {
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
-    method public static boolean isSecure();
     field public static final boolean IS_EMULATOR;
   }
 
@@ -2191,17 +2210,6 @@
     field public static final android.net.Uri CORP_CONTENT_URI;
   }
 
-  public final class DeviceConfig {
-    field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
-    field public static final String NAMESPACE_ANDROID = "android";
-    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
-    field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
-    field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
-    field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
-    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
-    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
-  }
-
   public interface InputMethodManagerDeviceConfig {
     field public static final String KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS = "hide_ime_when_no_editor_focus";
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a4a12d7..128e3de 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -424,6 +424,7 @@
         "android/os/IInterface.java",
         "android/os/Binder.java",
         "android/os/IBinder.java",
+        "android/os/Parcelable.java",
     ],
 }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2dccd4d..31cbe28 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6717,6 +6717,13 @@
             ii = null;
         }
 
+        final IActivityManager mgr = ActivityManager.getService();
+        try {
+            mgr.finishAttachApplication(mStartSeq);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
         final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
         mConfigurationController.updateLocaleListFromAppContext(appContext);
 
@@ -6785,13 +6792,6 @@
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
 
-        final IActivityManager mgr = ActivityManager.getService();
-        try {
-            mgr.finishAttachApplication(mStartSeq);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-
         // Wait for debugger after we have notified the system to finish attach application
         if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
             // XXX should have option to change the port.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 1cbe910..be77f2b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -147,6 +147,19 @@
      */
     public static final int POLICY_TYPE_SENSORS = 0;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = "RECENTS_POLICY_",
+            value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface RecentsPolicy {}
+
+    /**
+     * If set, activities launched on this virtual device are allowed to appear in the host device
+     * of the recently launched activities list.
+     */
+    public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1 << 0;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -161,6 +174,8 @@
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
     @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -173,7 +188,8 @@
             @ActivityPolicy int defaultActivityPolicy,
             @Nullable String name,
             @NonNull SparseIntArray devicePolicies,
-            @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
+            @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+            @RecentsPolicy int defaultRecentsPolicy) {
         mLockState = lockState;
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -188,6 +204,7 @@
         mName = name;
         mDevicePolicies = Objects.requireNonNull(devicePolicies);
         mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     @SuppressWarnings("unchecked")
@@ -204,6 +221,7 @@
         mDevicePolicies = parcel.readSparseIntArray();
         mVirtualSensorConfigs = new ArrayList<>();
         parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+        mDefaultRecentsPolicy = parcel.readInt();
     }
 
     /**
@@ -328,6 +346,16 @@
         return mVirtualSensorConfigs;
     }
 
+    /**
+     * Returns the policy of how to handle activities in recents.
+     *
+     * @see RecentsPolicy
+     */
+    @RecentsPolicy
+    public int getDefaultRecentsPolicy() {
+        return mDefaultRecentsPolicy;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -346,6 +374,7 @@
         dest.writeString8(mName);
         dest.writeSparseIntArray(mDevicePolicies);
         dest.writeTypedList(mVirtualSensorConfigs);
+        dest.writeInt(mDefaultRecentsPolicy);
     }
 
     @Override
@@ -377,15 +406,17 @@
                 && Objects.equals(mAllowedActivities, that.mAllowedActivities)
                 && Objects.equals(mBlockedActivities, that.mBlockedActivities)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
-                && Objects.equals(mName, that.mName);
+                && Objects.equals(mName, that.mName)
+                && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
     }
 
     @Override
     public int hashCode() {
         int hashCode = Objects.hash(
                 mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
-                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy,  mAllowedActivities,
-                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
+                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
+                mDefaultRecentsPolicy);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -407,6 +438,7 @@
                 + " mDefaultActivityPolicy=" + mDefaultActivityPolicy
                 + " mName=" + mName
                 + " mDevicePolicies=" + mDevicePolicies
+                + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
                 + ")";
     }
 
@@ -442,6 +474,7 @@
         @Nullable private String mName;
         @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
         @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+        private int mDefaultRecentsPolicy;
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -647,6 +680,17 @@
         }
 
         /**
+         * Sets the policy to indicate how activities are handled in recents.
+         *
+         * @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
+         */
+        @NonNull
+        public Builder setDefaultRecentsPolicy(@RecentsPolicy int defaultRecentsPolicy) {
+            mDefaultRecentsPolicy = defaultRecentsPolicy;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
          *
          * @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -684,7 +728,8 @@
                     mDefaultActivityPolicy,
                     mName,
                     mDevicePolicies,
-                    mVirtualSensorConfigs);
+                    mVirtualSensorConfigs,
+                    mDefaultRecentsPolicy);
         }
     }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 34c4ef3..86a672f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5107,6 +5107,21 @@
     public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
 
     /**
+     * Activity Action: Starts a note-taking activity that can be used to create a note. This action
+     * can be used to start an activity on the lock screen. Activity should ensure to appropriately
+     * handle privacy sensitive data and features when launched on the lock screen. See
+     * {@link android.app.KeyguardManager} for lock screen checks.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
+
+    /**
+     * A boolean extra used with {@link #ACTION_CREATE_NOTE} indicating whether the launched
+     * note-taking activity should show a UI that is suitable to use with stylus input.
+     */
+    public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
+
+    /**
      * Broadcast Action: Sent to the integrity component when a package
      * needs to be verified. The data contains the package URI along with other relevant
      * information.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c79f99d..1f01ae9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -867,10 +867,15 @@
      */
     public void checkInstallConstraints(@NonNull List<String> packageNames,
             @NonNull InstallConstraints constraints,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<InstallConstraintsResult> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
         try {
             var remoteCallback = new RemoteCallback(b -> {
-                callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+                executor.execute(() -> {
+                    callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+                });
             });
             mInstaller.checkInstallConstraints(
                     mInstallerPackageName, packageNames, constraints, remoteCallback);
@@ -3675,7 +3680,7 @@
     }
 
     /**
-     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
      */
     @DataClass(genParcelable = true, genHiddenConstructor = true)
     public static final class InstallConstraintsResult implements Parcelable {
@@ -3783,7 +3788,7 @@
     /**
      * A class to encapsulate constraints for installation.
      *
-     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it
      * specifies the conditions to check against for the packages in question. This can be used
      * by app stores to deliver auto updates without disrupting the user experience (referred as
      * gentle update) - for example, an app store might hold off updates when it find out the
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4c9ae37..def0cbd 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -366,7 +366,7 @@
      */
     public static final int getCallingUidOrWtf() {
         if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
-            Log.wtf(TAG,
+            Log.wtfStack(TAG,
                     "Thread is not in a binder transaction, "
                             + "and the calling identity has not been "
                             + "explicitly set with clearCallingIdentity");
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 44a1fa5..249f486 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1446,28 +1446,6 @@
         return IS_DEBUGGABLE;
     }
 
-
-    /**
-     * Returns true if the device is running a secure build, such as "user" or "userdebug".
-     *
-     * Secure builds drop adbd privileges by default, though debuggable builds still allow users
-     * to gain root access via local shell. See should_drop_privileges() in adb for details.
-     * @hide
-     */
-    private static final boolean IS_SECURE =
-            SystemProperties.getBoolean("ro.secure", true);
-    /**
-     * Returns true if the device is running a secure build, such as "user" or "userdebug".
-     *
-     * Secure builds drop adbd privileges by default, though debuggable builds still allow users
-     * to gain root access via local shell. See should_drop_privileges() in adb for details.
-     * @hide
-     */
-    @TestApi
-    public static boolean isSecure() {
-        return IS_SECURE;
-    }
-
     /** {@hide} */
     public static final boolean IS_ENG = "eng".equals(TYPE);
     /** {@hide} */
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 27f4d08..7df9290 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -23,9 +23,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.provider.Settings.Config.SyncDisabledMode;
@@ -68,6 +67,7 @@
      * different namespace in order to avoid phonetype from resetting it.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
 
     /**
@@ -86,7 +86,6 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
     public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
 
     /**
@@ -224,7 +223,6 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
     public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
 
     /**
@@ -284,6 +282,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
             "intelligence_content_suggestions";
 
@@ -291,7 +290,7 @@
      * Namespace for JobScheduler configurations.
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
 
     /**
@@ -331,6 +330,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
 
     /**
@@ -386,6 +386,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
             "remote_key_provisioning_native";
 
@@ -410,6 +411,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
 
     /**
@@ -461,6 +463,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
 
     /**
@@ -560,6 +563,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_TARE = "tare";
 
     /**
@@ -584,6 +588,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
 
     /**
@@ -591,6 +596,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
 
     /**
@@ -601,7 +607,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_ANDROID = "android";
 
     /**
@@ -609,6 +615,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
 
     /**
@@ -625,7 +632,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
 
     /**
@@ -633,6 +640,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
 
     /**
@@ -640,6 +648,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
             "device_policy_manager";
 
@@ -690,6 +699,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WIDGET = "widget";
 
     /**
@@ -697,6 +707,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER =
             "connectivity_thermal_power_manager";
 
@@ -705,6 +716,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONFIGURATION = "configuration";
 
     /**
@@ -712,6 +724,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
 
     /**
@@ -719,6 +732,8 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("IntentName")
     public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
 
     /**
@@ -726,6 +741,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
 
     /**
@@ -733,6 +749,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE =
             "virtualization_framework_native";
 
@@ -741,7 +758,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
 
     /**
@@ -749,7 +766,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
 
     /**
@@ -783,6 +800,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
 
     /**
@@ -790,6 +808,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
 
     /**
@@ -797,6 +816,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
 
     /**
@@ -804,6 +824,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WEAR = "wear";
 
     /**
@@ -819,7 +840,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
 
     /**
@@ -835,6 +856,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
 
     private static final Object sLock = new Object();
@@ -1112,6 +1134,7 @@
      * @see #getSyncDisabledMode()
      * @hide
      */
+    @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
         Settings.Config.setSyncDisabledMode(syncDisabledMode);
@@ -1123,6 +1146,7 @@
      * @see #setSyncDisabledMode(int)
      * @hide
      */
+    @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static @SyncDisabledMode int getSyncDisabledMode() {
         return Settings.Config.getSyncDisabledMode();
@@ -1143,12 +1167,10 @@
      * @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
      */
     @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
     public static void addOnPropertiesChangedListener(
             @NonNull String namespace,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
-        enforceReadPermission(namespace);
         synchronized (sLock) {
             Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
             if (oldNamespace == null) {
@@ -1271,23 +1293,10 @@
     }
 
     /**
-     * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
-     * @hide
-     */
-    public static void enforceReadPermission(String namespace) {
-        if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                != PackageManager.PERMISSION_GRANTED) {
-            if (!PUBLIC_NAMESPACES.contains(namespace)) {
-                throw new SecurityException("Permission denial: reading from settings requires:"
-                        + READ_DEVICE_CONFIG);
-            }
-        }
-    }
-
-    /**
      * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
      * @hide
      */
+    @SystemApi
     public static @NonNull List<String> getPublicNamespaces() {
         return PUBLIC_NAMESPACES;
     }
@@ -1344,6 +1353,7 @@
          * @param keyValueMap A map between property names and property values.
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
             Preconditions.checkNotNull(namespace);
             mNamespace = namespace;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f3e8b7c..03846db 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3366,7 +3366,7 @@
         public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
                 List<String> names) {
             String namespace = prefix.substring(0, prefix.length() - 1);
-            DeviceConfig.enforceReadPermission(namespace);
+            Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
 
@@ -18370,6 +18370,21 @@
                .getApplicationContext().checkCallingOrSelfPermission(permission);
         }
 
+        /**
+         * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
+         * @hide
+         */
+        public static void enforceReadPermission(String namespace) {
+            if (ActivityThread.currentApplication().getApplicationContext()
+                    .checkCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (!DeviceConfig.getPublicNamespaces().contains(namespace)) {
+                    throw new SecurityException("Permission denial: reading from settings requires:"
+                        + Manifest.permission.READ_DEVICE_CONFIG);
+                }
+            }
+        }
+
         private static void registerMonitorCallbackAsUser(
                 @NonNull ContentResolver resolver, @UserIdInt int userHandle,
                 @NonNull RemoteCallback callback) {
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 169c8c5..ce7606a0 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -227,8 +227,10 @@
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param modeId The new mode Id
+     * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
      */
-    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
     }
 
     /**
@@ -303,8 +305,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-        onModeChanged(timestampNanos, physicalDisplayId, modeId);
+    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
+        onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 138017c..85cb517 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,16 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at, which is a divisor of the active mode
+     * refresh rate. This is the rate SurfaceFlinger would consume frames and would be observable
+     * by applications via the cadence of {@link android.view.Choreographer} callbacks and
+     * by backpressure when submitting buffers as fast as possible.
+     * Apps can call {@link android.view.Display#getRefreshRate} to query this value.
+     *
+     */
+    public float renderFrameRate;
+
+    /**
      * The default display mode.
      */
     public int defaultModeId;
@@ -376,6 +386,7 @@
                 && Objects.equals(displayCutout, other.displayCutout)
                 && rotation == other.rotation
                 && modeId == other.modeId
+                && renderFrameRate == other.renderFrameRate
                 && defaultModeId == other.defaultModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
                 && colorMode == other.colorMode
@@ -428,6 +439,7 @@
         displayCutout = other.displayCutout;
         rotation = other.rotation;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
         colorMode = other.colorMode;
@@ -475,6 +487,7 @@
         displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
         rotation = source.readInt();
         modeId = source.readInt();
+        renderFrameRate = source.readFloat();
         defaultModeId = source.readInt();
         int nModes = source.readInt();
         supportedModes = new Display.Mode[nModes];
@@ -535,6 +548,7 @@
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
         dest.writeInt(rotation);
         dest.writeInt(modeId);
+        dest.writeFloat(renderFrameRate);
         dest.writeInt(defaultModeId);
         dest.writeInt(supportedModes.length);
         for (int i = 0; i < supportedModes.length; i++) {
@@ -764,6 +778,7 @@
         sb.append(presentationDeadlineNanos);
         sb.append(", mode ");
         sb.append(modeId);
+        sb.append(renderFrameRate);
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
         sb.append(", modes ");
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 30e7a7a..89a0c05 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1504,6 +1504,7 @@
     public static final class DynamicDisplayInfo {
         public DisplayMode[] supportedDisplayModes;
         public int activeDisplayModeId;
+        public float renderFrameRate;
 
         public int[] supportedColorModes;
         public int activeColorMode;
@@ -1520,6 +1521,7 @@
             return "DynamicDisplayInfo{"
                     + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
                     + ", activeDisplayModeId=" + activeDisplayModeId
+                    + ", renderFrameRate=" + renderFrameRate
                     + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
@@ -1535,6 +1537,7 @@
             DynamicDisplayInfo that = (DynamicDisplayInfo) o;
             return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
                 && activeDisplayModeId == that.activeDisplayModeId
+                && renderFrameRate == that.renderFrameRate
                 && Arrays.equals(supportedColorModes, that.supportedColorModes)
                 && activeColorMode == that.activeColorMode
                 && Objects.equals(hdrCapabilities, that.hdrCapabilities)
@@ -1544,7 +1547,7 @@
         @Override
         public int hashCode() {
             return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
-                    activeColorMode, hdrCapabilities);
+                    renderFrameRate, activeColorMode, hdrCapabilities);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 09a9d46..bfb489e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,6 +77,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -505,6 +506,13 @@
     Region mTouchableRegion;
     Region mPreviousTouchableRegion;
 
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+
+    // This indicates that we've already known the window size but without measuring the views.
+    // If this is true, we must measure the views before laying out them.
+    private boolean mViewMeasureDeferred;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mWidth;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2593,7 +2601,8 @@
     }
 
     private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
-            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
+            boolean forRootSizeOnly) {
         int childWidthMeasureSpec;
         int childHeightMeasureSpec;
         boolean windowSizeMayChange = false;
@@ -2649,7 +2658,15 @@
                     lp.privateFlags);
             childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                     lp.privateFlags);
-            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            if (!forRootSizeOnly || !setMeasuredRootSizeFromSpec(
+                    childWidthMeasureSpec, childHeightMeasureSpec)) {
+                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            } else {
+                // We already know how big the window should be before measuring the views.
+                // We can measure the views before laying out them. This is to avoid unnecessary
+                // measure.
+                mViewMeasureDeferred = true;
+            }
             if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                 windowSizeMayChange = true;
             }
@@ -2665,6 +2682,25 @@
     }
 
     /**
+     * Sets the measured root size for requesting the window frame.
+     *
+     * @param widthMeasureSpec contains the size and the mode of the width.
+     * @param heightMeasureSpec contains the size and the mode of the height.
+     * @return {@code true} if we actually set the measured size; {@code false} otherwise.
+     */
+    private boolean setMeasuredRootSizeFromSpec(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+            // We don't know the exact size. We need to measure the hierarchy to know that.
+            return false;
+        }
+        mMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+        mMeasuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        return true;
+    }
+
+    /**
      * Modifies the input matrix such that it maps view-local coordinates to
      * on-screen coordinates.
      *
@@ -2751,6 +2787,14 @@
                 || lp.type == TYPE_VOLUME_OVERLAY;
     }
 
+    /**
+     * @return {@code true} if we should reduce unnecessary measure for the window.
+     * TODO(b/260382739): Apply this to all windows.
+     */
+    private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+        return lp.type == TYPE_NOTIFICATION_SHADE;
+    }
+
     private Rect getWindowBoundsInsetSystemBars() {
         final Rect bounds = new Rect(
                 mContext.getResources().getConfiguration().windowConfiguration.getBounds());
@@ -2801,6 +2845,7 @@
         mAppVisibilityChanged = false;
         final boolean viewUserVisibilityChanged = !mFirst &&
                 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+        final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
 
         WindowManager.LayoutParams params = null;
         CompatibilityInfo compatibilityInfo =
@@ -2922,7 +2967,7 @@
 
             // Ask host how big it wants to be
             windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
-                    desiredWindowWidth, desiredWindowHeight);
+                    desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
         }
 
         if (collectViewAttributes()) {
@@ -2962,8 +3007,8 @@
                 // we don't need to go through two layout passes when things
                 // change due to fitting system windows, which can happen a lot.
                 windowSizeMayChange |= measureHierarchy(host, lp,
-                        mView.getContext().getResources(),
-                        desiredWindowWidth, desiredWindowHeight);
+                        mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
+                        shouldOptimizeMeasure);
             }
         }
 
@@ -3383,6 +3428,13 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (mViewMeasureDeferred) {
+            // It's time to measure the views since we are going to layout them.
+            performMeasure(
+                    MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
+        }
+
         if (!mRelayoutRequested && mCheckIfCanDraw) {
             // We had a sync previously, but we didn't call IWindowSession#relayout in this
             // traversal. So we don't know if the sync is complete that we can continue to draw.
@@ -3973,6 +4025,9 @@
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
+        mMeasuredWidth = mView.getMeasuredWidth();
+        mMeasuredHeight = mView.getMeasuredHeight();
+        mViewMeasureDeferred = false;
     }
 
     /**
@@ -4068,7 +4123,7 @@
                         view.requestLayout();
                     }
                     measureHierarchy(host, lp, mView.getContext().getResources(),
-                            desiredWindowWidth, desiredWindowHeight);
+                            desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
                     mInLayout = true;
                     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
@@ -8167,8 +8222,8 @@
         final WindowConfiguration winConfigFromWm =
                 mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
-        final int measuredWidth = mView.getMeasuredWidth();
-        final int measuredHeight = mView.getMeasuredHeight();
+        final int measuredWidth = mMeasuredWidth;
+        final int measuredHeight = mMeasuredHeight;
         final boolean relayoutAsync;
         if (LOCAL_LAYOUT
                 && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index f55932e..e027934 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -128,9 +128,10 @@
             ActivityInfo activityInfo, int windowFlags, int systemWindowFlags);
 
     /**
-     * Returns {@code true} if the tasks which is on this virtual display can be showed on Recents.
+     * Returns {@code true} if the tasks which is on this virtual display can be showed in the
+     * host device of the recently launched activities list.
      */
-    public abstract boolean canShowTasksInRecents();
+    public abstract boolean canShowTasksInHostDeviceRecents();
 
     /**
      * This is called when the top activity of the display is changed.
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 614f962..75f0bf5 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -97,6 +97,7 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -415,7 +416,7 @@
     @VisibleForTesting
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
         // Check permission early.
-        DeviceConfig.enforceReadPermission(
+        Settings.Config.enforceReadPermission(
             DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
 
         mRunningTrackers = new SparseArray<>();
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index afea69a..a555ae3 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -158,11 +158,11 @@
 
     private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
     static {
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4");
     }
 
     private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
@@ -418,7 +418,10 @@
         return Double.NaN;
     }
 
-    private static String keyToString(int key) {
+    /**
+     * Returns a human readable version of a key.
+     */
+    public static String keyToString(int key) {
         StringBuilder sb = new StringBuilder();
         final int drainType = key & MODEM_DRAIN_TYPE_MASK;
         appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
@@ -427,6 +430,7 @@
         if (drainType == MODEM_DRAIN_TYPE_TX) {
             final int txLevel = key & MODEM_TX_LEVEL_MASK;
             appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
+            sb.append(",");
         }
 
         final int ratType = key & MODEM_RAT_TYPE_MASK;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ebfc4f7..db288c0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -327,4 +327,14 @@
 
     /** Shows rear display educational dialog */
     void showRearDisplayDialog(int currentBaseState);
+
+    /** Called when requested to go to fullscreen from the active split app. */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @param leftOrTop indicates where the stage split is.
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 019e3bd..a8d8a43 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -80,7 +80,7 @@
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
-                             nsecs_t vsyncPeriod) override;
+                             nsecs_t renderPeriod) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -168,14 +168,14 @@
 }
 
 void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                                     int32_t modeId, nsecs_t) {
+                                                     int32_t modeId, nsecs_t renderPeriod) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking mode changed handler.", this);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeChanged,
-                            timestamp, displayId.value, modeId);
+                            timestamp, displayId.value, modeId, renderPeriod);
         ALOGV("receiver %p ~ Returned from mode changed handler.", this);
     }
 
@@ -290,7 +290,7 @@
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
-                             "(JJI)V");
+                             "(JJIJ)V");
     gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
                              "dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 2c5386d..1ed3555 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -109,6 +109,7 @@
     jmethodID ctor;
     jfieldID supportedDisplayModes;
     jfieldID activeDisplayModeId;
+    jfieldID renderFrameRate;
     jfieldID supportedColorModes;
     jfieldID activeColorMode;
     jfieldID hdrCapabilities;
@@ -1184,6 +1185,7 @@
     env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
     env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
                      info.activeDisplayModeId);
+    env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
 
     jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
     if (colorModesArray == NULL) {
@@ -2174,6 +2176,8 @@
                             "[Landroid/view/SurfaceControl$DisplayMode;");
     gDynamicDisplayInfoClassInfo.activeDisplayModeId =
             GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+    gDynamicDisplayInfoClassInfo.renderFrameRate =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
     gDynamicDisplayInfoClassInfo.supportedColorModes =
             GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
     gDynamicDisplayInfoClassInfo.activeColorMode =
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 7e17840..3c4bac8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -107,7 +107,9 @@
     sdk_version: "core_platform",
     certificate: "platform",
 
-    srcs: [":remote-color-resources-arsc"],
+    srcs: [
+        ":remote-color-resources-arsc",
+    ],
 
     // Disable dexpreopt and verify_uses_libraries check as the app
     // contains no Java code to be dexpreopted.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8da91ee..fb451dd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3327,7 +3327,9 @@
          <p>Protection level: normal
          -->
     <permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
-        android:protectionLevel="normal"/>
+                android:label="@string/permlab_startForegroundServicesFromBackground"
+                android:description="@string/permdesc_startForegroundServicesFromBackground"
+                android:protectionLevel="normal"/>
 
     <!-- Allows a companion app to use data in the background.
          <p>Protection level: normal
@@ -3343,6 +3345,8 @@
          <p>Protection level: normal
      -->
     <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+                android:label="@string/permlab_companionProfileWatch"
+                android:description="@string/permdesc_companionProfileWatch"
                 android:protectionLevel="normal" />
 
     <!-- Allows application to request to be associated with a virtual display capable of streaming
@@ -4914,11 +4918,15 @@
          of their associated companion device
          -->
     <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+                android:label="@string/permlab_observeCompanionDevicePresence"
+                android:description="@string/permdesc_observeCompanionDevicePresence"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to deliver companion messages to system
          -->
     <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+                android:label="@string/permlab_deliverCompanionMessages"
+                android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to create new companion device associations.
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
new file mode 100644
index 0000000..849d32e
--- /dev/null
+++ b/core/res/assets/geoid_height_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
+and using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
new file mode 100644
index 0000000..6fd4022
--- /dev/null
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
new file mode 100644
index 0000000..546dd0d
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
new file mode 100644
index 0000000..eb3fe46
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
new file mode 100644
index 0000000..0243d6d0
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
new file mode 100644
index 0000000..3c2f777
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
new file mode 100644
index 0000000..5e9a480
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
new file mode 100644
index 0000000..c57e873
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
new file mode 100644
index 0000000..800b3e5
--- /dev/null
+++ b/core/res/geoid_height_map_assets/README.md
@@ -0,0 +1,8 @@
+These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
+Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+
+[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
+
+[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+URL: https://earth-info.nga.mil.
\ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
new file mode 100644
index 0000000..3f504d4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 9
+cache_tile_s2_level: 5
+disk_tile_s2_level: 0
+model_a_meters: 255.0
+model_b_meters: -128.0
+model_rmse_meters: 0.36
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
new file mode 100644
index 0000000..7fac234
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -0,0 +1,3 @@
+tile_key: "1"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\244i(\242\224R\216\264\361OZ\221je\251V\246Z\225jE\251V\245Z\225jE\247\216\324\361N\317\024f\214\322\023I\2323I\232L\322f\220\232LdqM\316:\321E\024\354\344sJ)\364\340i\340S\200\315(\030\243\255:\224\032p4\264\340i\300\324\213\315:\232EFz\320\0174\244\323I\246\223M-F\3527R\026\244\rR+S\353\314\r%\024QN\247\212z\324\253R\255J\2652\324\253R\255H*U\251\024\324\213O\006\244\024\264QM4RRRt\244$\342\220{\322a\201\312\363M,I\344P\017\255;\214\360h\245\006\234\r<5(4\365cO\006\226\227\024\264\nPiisNSS)\245&\232M0\232J3Fj65\031j7Rn\244\335F\352\225\033\212\224\036+\314\215!8\242\212)\303\245<S\326\245Z\225jU\251\226\245Z\221jU5\"\324\202\244\006\236)\342\235\2323HNh\2434\204\322SI\244\3154\322\t\n\364\241e\301\346\234]X\360(\371{Ss\315(4\340i\340\323\305<S\251A\365\247\216zQE.i3J\017\255(8\251U\251\305\251\204\323wRg4f\226\243sQ\023M\335I\272\223u\033\252x\232\236\322W\232\346\201\315)\351H\r-8S\305<\n\221jU\251V\246Z\225MJ\265\"\232\221jQO\024\360i\340\323\263I\232;\321\232Bi3IHO\024\302i3M4\334R\216)E-:\214\323\301\251\024\323\301\247\203K\326\201\221\305(4\273\251C\003KE&j@iwSKSKR\006\247f\226\230\325\003\034Te\251\245\250\335K\272\244G\300\241\236\274\374\212J\t\245\024\264\341O\002\244\025 \251TT\252*Q\305H\246\245SR\255<T\200\342\236\r<\032x4\271\2434f\214\322g\336\214\322n\3074\326bi\224\322i3I\232PiE:\235J\0058S\205<\032x4\340psN\316\356\264\224\240f\214R\200{sN\036\364\204zP\033\024\026\246\346\220\265\000\323\324\323\211\246\267J\253!\346\241-M\335I\276\215\364\345\227\212S%p\331\244\315!4\240\342\2349\251\000\251\022\244\002\236\005H\016*E\004\367\251\000>\265\"\232\225MJ\246\244SR\003N\006\236\r?4f\215\324\002)3Fh\315!>\264\204\323M7\223M9\024f\226\224S\201\247\212p\024\340)\324\242\234)\302\235\232\0058\014\323\302\346\244\000\001H\334\216*\022\r\' \323\261\232i\024\332L\322\356\3058=\014\374UYZ\2533sM-M-I\276\220\311\203K\346W\036\335i\271\245\315-<T\212jd\000\324\300\nv\332]\264\365\251\024\323\305H\246\244V\251\025\252P\324\360\324\340\324\354\322\346\214\322n\244\315\031\2434\231\367\2439\242\233\315&\t\240\nZu(\024\341\322\236)\340\323\207ZZu--(\251\024T\200S\361M \322\021\212\215\215\013\232v3\326\243u\305Bi\271\243v)\013UyZ\253\026\246\026\244\335HZ\243g\305\n\371\025\313\223\223E\'J\003T\201\263N\rS\306\370\251\321\352]\364\006\247\n\221i\300\323\301\251\026\244\025 4\360i\340\323\263J\r)4\334\321\232L\321E(\243\036\224}h\3074c\326\224\016)\300R\342\226\234:R\212p\247\212u:\212P*t\\u\251\024sO\307\2454\203M*M\"\305\334\323\304`\016\224\205)\214\271\025VE\301\250I\346\232M4\265V\225\252\271j\214\265&\352\013Tl\324\212Nk\235u\301\244\006\226\232G4\345\034S\226\246SR\251\251T\324\200\323\301\251\001\342\224\032p5 5\"\265H\032\236\032\236\032\234\r(4\354\322\032i4QE8R\343\024\244n?-(\030\034\322\355\357J)\301sF)qK\212p\024\242\235N\002\236\005(\024\365^jQ\351R\016*D\030\0314\270\024\234f\220\221J\016)O\"\242u\300\252\317\212\251/\025\\\2651\232\240\220\346\2533TE\2517PZ\230Z\244OZ\304u\315W*E%\031\346\2274\340jE5*\232\221MJ\032\244\016)CS\203S\203T\212\325 jxjxjxjx4\340i\331\2434Rd\032>\224\242\224\032p\247t\373\264\240f\234\247\035E.=*D\311\030\244\307<\322\342\214S\200\245\3058\np\247\201N\002\244Q\3058\nxS\326\235\272\223u!jM\334\346\202\371\251#\177Z\212y;\n\250\354j\274\247+UKb\242f\250]\262\rUc\223Q\261\246\346\220\2657v[\025aH\305d\221Q\262\203Q\264~\225\021\0304\242\234\005<T\200\323\301\247\203O\rN\rO\rO\006\236\rH\r<\032\220\032x4\360i\300\323\301\245&\222\212)E(\247\212p4\341N\034\365\247\004=\251\300`\323\331r3M\242\234\0058\nu\002\244Z\221Fi\346\225E<\275FZ\224P\302\231\2323NV\3051\2335VV\305V\221\370\252\214\334\324.\325\013>\005BMFM4\232ajj\037\232\246\316*\211\031\246\021M<\036j)S\270\250\207\006\236)\300\323\201\247\003O\rN\rN\rO\rR+T\201\252@i\340\323\301\247\203R\003O\006\234\r;4f\226\212QN\035i\302\234)\302\234*@i\300R\266q\326\220R\322\212Zu(\251\026\245N\224\340(\346\2028\250\363\315.\361H\322SKSKR\027\244.1Tg\227\223U\336O\226\253\226\250\335\270\252\356\325\021zc50\2651\232\225\033\034\323\214\225X7\024\3074\314\322\365\025\t\034\320)E:\235J\r(4\340i\341\251\341\252Uj\2205<\032\2205<\032\220\032x5 4\354\321J)iE8R\216\264\361\326\234)\300\323\205<\032x\346\215\270\240\nZ)A\245\025*\324\240b\244QI\'\025\031~\324\322j6ni\244\323wTm.)\215!=*3)\031\315S\222M\315QH\374T%\351\217\'\025Y\236\241g\244-\221Q\231)\245\350\3631F\372\256\257\212\035\371\247+\002)H\300\372\324\r\301\240\002FiA\247\003N\024\341KE(4\360\325\"\265H\255R+T\200\324\200\324\212j@j@i\324\240\322\322\203J\r8S\2058S\251\300\323\201\247\203OS\212\220`\365\247l\006\243<\032(\240\032\225\rN\247\232\225M6S\232\256i\245\2526j\214\265F\317P\273\032h|S\036@{\325f\340\325y_\232\204\265E$\225]\244\250\313\324m\'\2753}&\374\232\031\251w\361\212\210\032\010\315 8\342\245\r\221H\311\270\344S\261\260u\241B\261\347\212xU\'\031\240\250\354h\002\212(\245\006\234\rH\246\244SR\253T\212\325*\265H\246\244SO\3158\034\321\232Pis\212p4\340i\300\323\301\247\003N\024\340i\340\324\213R\251\300\250\336\243\315\031\247\n\231*PjE4\217U\337\203Q\263b\242g\250\331\252\"\325\0335B\315Q3TM \025\003\2605\003\266*\273\266j\274\204\212\207\314$\323Y\351\273\351CsAl\232pj\211Z\244\315;\002\224qR)\024\222\214\257\035\2521N\025 \247\216:R\032\000\024\231\242\224\032x5\"\232\221MJ\246\244SR\251\251T\324\200\322\212u\024\240\322\203N\006\234)\324\341N\006\236\r<\032\221Z\244\335Lza\3068\244\024\3455:\036*@i\340\342\225\216EWaP\311\305Vf\346\232\315Q3Te\251\214j\007\252\316\rC\236\271\250]\372\324\005\361Lr\030Uv\030\250\230\323\013b\205|\323\225\275i\302Nh\024\240\346\236\265 \024\003@\2239\030\315\"\343w\315\300\247\034\003\3058\032x4\264\204SM&i\300\323\201\251\026\244Z\225jU\251\026\245SR)\247\322\322\321J)E<\032p4\271\247\003N\006\236\r<\032xj]\324\323I\364\247\n\224\034\npj\2205!|To \305V\221\352\2635F\317\305D\315Q3\324fJa|\323\030\344Ug\030\315Tr9\252\356\324\315\370\250\344q\332\243$\032\206F\3051_\013I\347`\322\371\340\016\265p\232\007Z\221H\247\223@\246\276\027\225<\323U\311\353R\016i\302\234\016)\300\321\232Bi(\024\365\251V\245QR-J\242\244Z\221jU\247\322\212\\\321Fi\324\341N\024\np4\271\245\rO\rO\rK\272\234\034R\202\r9y\247\223\232Pi\013\342\232d\250\231\252\027j\254[\232\215\232\230Z\240v#\245@\322Sw\344\3224\230\025RisU\267TNs\322\253\273\021Q\026\246\371\225\034\255\221\232\214\036)\214\334\324l\325\262M74\240\234\324\212r9\251\007\000\324\017\311\241x\251T\323\301\3158R\321\232)3J*E\251EH\265*\324\242\244\024\361R\003N\006\235N\242\2123N\006\236\r.h\315\024\271\245\rN\rN\335I\272\2245N\217\305H\264\342x\250\\\324d\323\031\252\031\033\212\256Z\230MD\307\024\322\303\034\325Iz\234TFP\243\336\253IrI\300\250\231\311\034\232\207q&\220\266\006*\0275\013TD\323$o\227\024\302p*&jh\311<V\306\354\323\t\251\243\373\274\323\324\323\363L#\212h\340\323\301\251\026\236)h\242\212QR\n\220T\213R\251\251EH*@i\302\234)\331\245\006\235E\024\242\234\r-\024f\2274f\2245.\3523OSS\257\"\236\246\225\215B\315L&\242f\250\035\252\026j\210\2654\265B\315P\263UIO\245W\315F\315@\340d\324,\331\311\250\213Td\323\r@\347\006\241g\315\0107\034S\300\330s\236\225\177\'4\365\251T\366\247\322\364\024v\244\305(\251\026\236)sFiiE8u\247\n\220T\212*U\251V\244\025 \247\001N\024\264\341J)\331\030\351IJ)h\242\2123K\2323@9\245\247\255N\215\305H\0174\3622*\026\025\021\025\013T/U\331\252&5\0335D\315P;TM\316j\244\200\255F\0334\331\037\013P\253dT.\374\361Q\226\244\335QHj#\3159\030(>\265\033=l\200\r4\360i\350y\251A\241\216\005,|\2574\273M \353O\006\244\007\024f\200iE:\234)\342\244Z\225jU\025*\212\221E<S\205(\247S\201\315(\245\243\351E\024Q\2323Fh\245\006\224\034S\251EJ\246\246\034\214\323\201\241\205B\343\025]\352\007<UF=j&j\214\265D\355U\334\323\013T/\363US\36256^V\241\034-B\375j3Gj\216R\002\324!\276Z\214\2754\265n\253\323\261\232p\030\251\001\247\021\305:1\201O\307\345L<\032)\331\245\006\224S\251E<T\213R\250\251TT\252*@*@*AKE(\247\np4\354\203\332\222\214\372\322\023I\2323Fh\315\024\271\247\003N\024\361O\034T\250\335\251\343\031\247\023U\345`*\243\275Ww\252\356j\0064\306\346\242cPHs\322\241-\353M&\253\3102i$\000\'\275@zsQ=ELv\300\252\222K\232a~*2\324\205\253l5L\215S)\315H\026\236\005=W\025 \034SY8\250\250\247\nu:\234)\342\244QS(\251\224T\252*@*E\024\360)\333h\305&)\302\224\nZ)\246\212L\014u\244\242\214\321\232Pi\300\323\203S\203S\301\251\024\323\303\363C>\005S\226L\232\256\355P;T,\325ZG\346\243\3631Q\273\347\2450\236*\273\2674\200\346\220\214\232c&[\223\300\250g\340qU\230\361P\261\305C/CU\030b\243&\233\232ij\334\006\244SV#j\264\234\212x\342\244Z\220\nR\274Ug\034\361M\245\025 \024\243\255<S\300\251\026\246AS\255L\242\244\002\236\005H\242\237\212LQ\266\227m\030\243\024b\223m\030\246\220;SN1M\242\214\321\232]\324\340\324\340\324\360\365\"\267\024\026\346\221\334m\252r?5\0135@\355P\263\324\016rj\007\342\242\3630y\241\233\212\205\201\355LRKb\207s\273\002\230M5\210\306\rVu\306qP7Z\205\370\252\322T\004\323\t\246\223[\240\324\212j\304f\255!\251\001\251Tb\246ZRj\263\016i1@\024\360)\300S\200\251\000\251\024T\312*e\0252\324\242\244\025\"\212~(\305&\3321K\212LQ\2121AZiZa\\\347\024\302)\244RQF}\350\335N\rN\004\324\212\330\034\323\032L\232Fo\226\252\273TL\334Uy\036\253\264\225\023I\3151\233\212\254\347\232M\365 \341y\250\207\031\367\246\232k\032\201\330\223M \221PH*\263\236*\263\234\325v<\323\t\246\223[\252je5<ue\017J\235je\247\203J\307\212\212\214R\201J\0058\nx\025\"\255H\242\245QS-L\242\245\002\244\002\236\253O\002\235\2121HE!\036\224b\226\214Q\212\n\324l\246\242a\212a\024\332C\326\233\2323N\006\246Zk\266*\271~i\314\374T\016\325\004\217U]\352\274\215U\313\234\322o4\3269\246/Z\224\266\0056\243s\212\215\232\243=i\340qPH9\252N9\301\252\257P7Z\210\232a5\274\225:\212\236:\262\274b\254!\315J)CzP\\\343\030\244\034\322\201OQN\333N\002\236\005<\n\221EJ\242\245QS(\251\224S\300\251\000\247\201N\305\030\244+M\305\030\244\245\006\226\202)\244TN9\310\250\230S\010\246\232i\024\224\245\200\245\022\340To&\352\217<\3223\361U\336LUg\222\240y*\006z\205\233&\233\272\215\331\247(\346\234\313\315\025\024\234\232\204\365\245\305/j\205\272\223T\346\0309\252\255\311\252\362pj\003Q\265t\010*\302\234T\250j\312\363V\022\236\030\032\025\261\320\323\363\221\3159O\030\247v\247(\247\201J\005H\005<\n\221EH\242\245Z\231jU\251@\247\201R\001K\212)\r%&(\305\030\243\024\021HEF\313Q\262\324ei\204S\010\246\261\305G\313R1\300\342\233\232kTL\330\252\262\2775Y\332\240f\250\331\261Q\026\346\233\236i\353SD2j}\234\234\324.1P=DFi@8\305(\004\214\nc\256\336\265NnsU\010\353U$\250MF\325\321\"\032\231S5*\200*\302\234\nw\231\330S\303\032r\232\225NjAO\024\361O\024\360*@)\341i\340T\212*E\025*\212\231jU\025 \024\3608\245\"\223\024Q\212m\024QK\232Ji\024\302\276\225\031J\214\2550\255DS&\221\206\321Q\221\232\002b\230\342\252\313T\344\252\256j\022\365\013\276M34\n\231*\314?xU\2228&\253\311\336\25352\227\034qD|\212\216j\243%@zU\031\206\r@M1\253\250\351J[\002\205j\231Z\245S\353S)\030\251\027\232\221V\244U\251\025\rJ\261\232\221b&\236#5 B)\352)\340T\200\n\221EJ\253R*\324\200\021\326\236\265 \024QHE%!\024\224QKIE4\212i\024\302\275\351\205i\206<sP\310\274\323U;\320\303\002\240\220\3259\0175NS\212\246\3475]\215DM%*\324\351V\242\030\"\255\0221Ue\252\315M\357OQH\027nj\031\252\224\225]\370\252\322\256j\243\014\032\214\327LOzhl\232x52\232\225MJ\2652\232\235MJ\246\246Z\225EL\2652\212xZxZ\\{P\024}*@\247\352)\340\343\255L\2305(\024\270\245\315\031\245\242\220\212Ji\030\242\212L\321\2323IHi1\232k\014Tl{T/M\350)\215Ue5Q\352\224\334\232\252\374T\014)\204S)\351S\'QV\242\346\245|\214\021P?5\023-DF\rH\2074\3622*\244\243\004\212\251(\346\252\311P\236j\244\313\203U\315tE\363J\265*\212\231jU\034\324\312*eZ\231T\324\310\246\247U5:!\251UjP)\342\236>\224\341N\306h\000\216\224\340\304u\251\025\324\366\305J\030v4\241\251w\212p#\265-&)(\244\'4\224\021\232m\024\231\245\315&x\244\007\0249/\315DEDW&\220\214TRUIj\243\203T\344\035j\263\212\205\2051\2050\255*\212\235\005X\214\342\236_\327\221Q1\007\245\'Z\214\216x\247\001\265M&x\252\322\034\346\251\310j\273\212\200\214T2.\341U$\\V\332\324\253S(\251\224T\313S\245N\265:T\350*t\025:\212x\031\247\201N\002\236)qJ)\334\322\322\201N\024\365\315H\006i\333\005.\334t\244\311\024\231\245\315\024\322)\264QHi3I\232:\322\206\300#\035j3M\353LqU\336\253H3U\334dUI\022\252\310\265\\\212\217\0314\2453@J\231F\005<u\247\3435\013\251C\354h\246\363\221J[\203M\317\006\253Jj\253\014\324.*\"\264\306Z\253*f\264\326\245Z\231jU5:\324\351V\022\247AS\240\253\010*e\247\212x\247\016\224\372p\024\340)\300R\205\247\205\245\013O\013O\002\235\212Z\nR\025\244\331\355M \212NM!\024\332)\247\336\232i@&\244T\241\222\233\345\226\031\364\2462\355\250\330T\016\271\250\031*\273\307U\244N*\244\211U\236:\214G\315?e\033iqKO\007\212d\274\212\214PG\024\303L\317\006\240~j&\025\003\212\205\2054\212\201\305\\SR\251\251\222\246J\235*t\253\tV\022\247J\260\225*\324\213N\024\360)\340S\300\247\201N\305(\024\340)\340S\261O\002\234\026\245H\367\032\227\311\002\232\321q\3050\307\216\325\033% \212\220\307\352*6OJn\332c\n`\0252-HN\0054\363H\006\r1\223\'\232\215\223\025\031AQ2\n\255\"\325YW\345\252\016*\026\031\244)\307\024\230\246\342\232x\240\034\322\366\246\236\234\322\001JG\025\021\025\031\351L+Q:b\253\270\252\357L\353Q\270\251\301\251\226\246SS-N\225a*\302U\204\253\tV\026\244Z\221i\340S\305H*@)\352)\370\243\024\340)\300S\300\247\252\324\251\036MXU\300\247\342\232E4\212\214\2504m\024\233i\245\001\246\030\207j\201\3415\037\226\300\323\324\021\326\220\344\232r\255;\024\240T.\274\324\0141P\260\252\356\265^Q\305Q\2219\250\n\321\212iZ\215\306*#J\253KH\302\231F\354\212k\n\214\n1\326\242q\315V\225j\233\216i\240b\243z\225MJ\246\247J\231*\302T\351VR\254%YJ\231jU\251\001\251\026\236\242\236:T\252*@;\323\326\227\024\340)\301jEBzU\210\340<f\247\010\007\002\234\022\224\2454\212a\024\314R\n\r7\255%\004\003M()\245)\246:M\270\246\232BqL$w\353Q8\025\013\255V\222\2538\315U\221sP4t\2051L+Q8\3153e.\321MaM9\"\230E ZR\271\024\2018\244+\324T2.*\254\235\rTu\346\242#\025\023sOJ\231jt5:T\350*\312\n\260\234U\2048\251\325\205J\257R\253T\200\324\212jE5*\232\221MJ\265 \366\024\242\236\242\245U\315[\202<\014\232\225\260\243\212\022\246\002\224\364\250\030\363Q\223LcM\3154\232L\323wS\201\342\214\321\232ajN\325\031\036\224\326\250\315F\325\013\232\257%VcQ5Bi\244f\230\313Q\224\246\221L\"\230E0\214\322\021H\005=W=\2516\342\232G4\307\213p\342\251I\0363\232\253 \252\354)\205i\026\245SS\245X@j\302\n\262\203\0252\324\352jU5*\232\225MJ\246\245SR\255J\265\"\324\252}j@\376\224\345\253\021\256j\312 \0258\245a\220)@\305.\354P[\212\211\215Fi\246\232i\246\232M0\365\247\003JM74\003\203\315)!\272P1QH3\322\240\"\230\302\241aP8\250\031j\007J\205\201\024\332\017Ja\031\250\330TdTl9\240-!\024\3209\251T`R\021L+\221M?v\252\3123\232\246\351\315B\321\346\242d\250\026\246A\232\261\032\325\244\025:T\313S-J\246\245SR\251\251\226\245Z\231jU\251TT\252)\364\340*E\025n\016\225d\032z\232\225H&\236j2)\246\2434\001M4\302)\270\342\230E4\212i\310\240\236)\205\216i\300\322\346\214\323\032\242jcT-\315B\3035\023\n\211\226\241d\250\312R\025\246\025\2462\346\243\"\231\262\224\255F\303\024*\323\310\307Zn3F*\'\\\016*\273\256E@\361\324-\035D\351Y\351VPU\210\352\302\324\252jUj\221Z\246SR\251\251\224\324\313S\240\251\324T\312\2652\212x\251\000\247\205\251\000\253p\217\226\246\013O\002\234\016\r.\352\\\323MFi\r\'ji\246PE0\323M0\323\010\244\316)CS\263\3055\215Fj&\250\332\243j\215\206j\"*2)\245)\245)\214\225\023%0\245\0331Me\250\331h\013M#\'\232\\\036\324\214\204\014\323\010\312\234\325r1Q\260\317Z\211\226\240u\315e\240\251\320U\204\310\251\224\324\213R\255L\246\245SS\245N\225:\n\235\005XAS\250\251Ui\341jEZ\220\n\225S5f5\300\346\246\006\2349\245a\3050\032\\\322\023L4\207\245(\2468\364\250\311\246\026\246\226\2439\246\236)\206\232E\002\234\r\006\230\334T/Q\036\264\323Q\232k\014\324eh\tF\332c\257\245B\313L+M+\212a\025\031\034\323H\246\323\227\255\0142)\254>^\225Y\205D\302\241qU\333\203Y\310\2652\216\225*\324\253R-L\265*\324\310*\302\n\260\202\254 \253\010\265a\026\254\"\325\210\342-\322\245\3733c4\2331OU\251Pb\254\257LR\201\315H\242\211\016\005C\272\227u4\232J)E-0\250\250\331*2\224\241i\214)\230\240\212LR\205\240\212\215\252&\025\031\024\302)\204SqM+J\005!\024\3223P\262\323\n\342\232EF\313Q\221L\"\232V\2000i\3148\342\230\303+U\330Tl*\t\005Wu\254\325\034T\253R-L\265*\212\231EL\202\247AV\021j\302-YE\253(\265a\026\254*\325\210\320\366\253pr\n\277J\212E\033\270\024\005\247\201R\3069\251v\363OQQK\311\250H9\245\353IFh\315\000\323\201\243\024\230\315!Za\025\033-7m&(\305=S\"\221\226\242e\250\331j&Z\211\205F\302\233K\214\212\000\241\205FE4\212\215\226\243\"\232ED\313L+M\"\220\214P\t9\305\005p\rVa\315F\303\025\013\014\324N\225\222\005=EJ\265*\212\231EL\202\254\"\325\204Z\263\032\325\224J\262\211V\021jtZ\2361\310\315\\\014\252\240/Z\025\210<Q\214\320F)sO\214\363V\200\340\032x\030\025\013/4\322\242\230V\232E!\244\242\200i\342\236\006h\333\315!L\324n\225\036\312iZLsR\240\244aP\260\250\210\250\310\250\312S\031*\"\224\230\305\000\320Ni\244SJ\323\010\250\330S\010\250\310\2462\323\010\244\333\232ENi_\345\004Ub*\026\246c\232\032<\212\300\006\244Q\232\225EL\202\247AS\240\253(*\302\n\263\032\325\250\326\254\242\324\352*U\0252T\242\244\024\374\322\023\232J\2325\357V\024\361O\3154\256i\245i\204S\010\246R\021\2121\305 \034\324\2128\247S\363\221F)\031A\250\331*2\224\302\264\345\024\244TL*\026\024\322(\331\232C\021#\245E$[G5Y\206)\270\315\030\244#4b\243aQ\232a\024\322)\2148\246\025\246\221GN\225\013\222MD\302\243+M\013\3159\206\005sj*U\0252\212\231EL\202\247AVPU\230\305Z\214U\250\326\254\242\324\312\264\361R\251\247\203O\006\235\272\2234\344<\325\264\344\014T\341x\245\333N\333Me\250\210\2460\250\332\243&\223u\000\346\236\r<\032}8R\342\223m#%DR\200\230\246\260\250\331j&\024\300\265,i\223S\004\030\306*\033\230\376Z\314t\346\243\306(\245\333I\214Tl*6Zf)\244S\010\246\221M)\221Q\343\002\243aQ\260\250\312\321\216i\262g\025\316\250\251Uje\0252\212\231V\247AVPU\230\305[\214U\250\305YQR\n3O\006\236\r<S\351qOU\253p\203\212\264\2434\340\224\3420*6\025\021\025\023\n\214\212c%0\255\030\305(4\365\251\001\247\346\234\r8\n\220 \"\243h\361L)Q2\324n1P\232@*P=)\335\2529\016\340A\2522\2475]\3053\024\365\031\244e\250\210\250\310\246\221I\267\"\230\313L\305\0140*\006\246\021Q\260\246\204\'\240\245\331\216\265\004\247\002\260UjUZ\225V\246U\251\325jdZ\262\213VcZ\265\030\253IS)\247R\212z\212\221EH\005<\n\221V\246T\251\223\345\251C`\325\205pE\014)\245i\214\265\013-D\302\233\212B\264\322\264\3221FiC\322\371\225*6je5 jBsHF*\'\"\240a\232\211\226\243\350jT4\346\2467J\255(\252\222\n\212\244Z\030TL\265\031\024\303I\315!\024\322\264\306\034T\004S\010\246\225\245\215y\311\244\223\212\245)\306k\031V\246U\251UjeZ\231\026\247E\253\010\265e\026\254\245N\265*\323\300\247\205\247\201R-H\242\244QS\242\324\341qMn\r9[5*f\247\316E(\244a\221Q2f\241d\3057m\033i\245j\027Z\214\323y\240\023R+\021R\254\206\245\022R\031h2f\230Ni\206\230\302\230V\234\274S\310\342\242j\201\352\273\214\324%piV\202i\206\230\302\243\"\223\024b\232\303\212\215\272T$R\005\243\313\356hn\007\025VJ\25175\230\253R\252\324\312\2652-N\213S\"\324\350*\302\n\235EL\202\247QR\205\251\002\323\302S\202\323\300\251TT\351\305L9\244d&\205\201\210\310\025*#\016\2650\024\243\203A\2445\023.i6Q\345\323\031*\026CP\262Rm\246\221\3158-/AFx\244\0074\271\245\245\353\326\202\264\322\264\233i\370\342\241a\315B\300TEj\007^j3M&\233\232\t\246\342\232E&)\2568\250H\246\021@\024\343\322\243qU\234UIEg\252\324\252\2652\255N\253S*\324\352\2252%N\253S\242\324\312\265:-L\253R\252\323\302\322\343\332\200*U\025*\212\261\027$U\305\010\247\347\0314\327\230.Dc\203O\211\303\2140\0242m84\321\212\221c\315+[\236\324\337 \322\371\036\324\206,Tm\035@\311P\264u\013Fi\233M\024\322i\t\244\315(jx4\341J)i\244\ni<TOQ0\250\332\243e\252\3561Q\323H\240f\214\342\221\2104\001L\177AQc4\322\224m\305\014*&\025\003\214\325i\022\263\324f\246U\251\225*tJ\235\022\247T\251\325*eJ\231\022\254\"T\312\225(JxZp\024\355\264\241jEZ\224-H\274T\200\223R\004\006\244H\361Sc\214\036}\352&C\236*xP\236\247\025q\023\324\212\220\306;\n\215\242\250Z:\211\343\250Z:\211\243\250\031=\252&\2175\023\307\212\205\226\230E&)@\247\250\251\002\323\200\3055\251\271\244\250\332\243aQ\221\3155\207\025\004\211\232\204\256)\207\2321M\"\224-+\014\n\204\212n\332wjk\nc\n\211\206j&\025^E\254\364J\235\026\247D\251\321*tZ\235\022\247D\251\321*uJ\235\022\247T\251\002g\245)\214\257ZP\264\270\245\002\245QR*\324\201i\301i\342\245V\305<\267\245\033\260*H\334\032\2345H\256A\342\247\014\030r)\214\202\241h\352&J\205\343\252\354\224\302\225\023\307U\335*\022\264\335\264\340\265\"\2558\322c4\322\270\250\317\024\335\324\204\346\232V\242e\3050\323\030f\242t\364\250J\320E7\024\360\000\246\260\334*=\264\355\235\351\254)\245j&\025\023\212\205\215B\346\251*T\350\265a\022\247T\251\321*\302%N\211V\021*tJ\231R\245\tR \301\342\234\371f\344Rb\223m(Z\225EL\213\232\235c\315N\220\257\361\032kE\203\305FW\024\240\361Mf8\250\304\205\032\255G85:\316\007z\221nTT\242ezL\212i\025\033.j&J\215\243\025\013GU\244J\252\353\212\217\024\341\232\224\003F(4\306\250\332\243\"\201\326\244\n\rG$uY\306*2\336\264\322\300\212\205\210\246f\216\364\356\270\024\366A\267\212lj6\234\323\037\212\214\323I\246\032\205\305V\220Uv8\250\343Z\260\211V\021*\302%N\211V\021*\302GS\242T\312\2252\245J\022\236\251\203\234T\205\001\034\365\246,T\246:M\264\340\2652\014T\352jU\004\364\247b\232S4y&\230\321b\243h\201\246\204\333FqNQ\223\315Y\215\200\351S\357\243}\005\263M&\230\303=\252\026\025\004\213\236\325\t\267\334y\240Z\001A\267\307Ji\217\024\335\224\205*2\264\306J\205\2050\361M/\216E\006\347\214\032\247<\343\265Q{\203\353Q\371\344\3654\276a=\352T9\251vq@\0304\374\346\225z\232c\255B\303\025\031\250\311\250\335\252\274\225U\372\323\343Z\262\213\232\260\211V\021*\312%XD\253\010\2252%N\251R\252T\312\224\361\035/\227JW\035)\273sF\312P\230\247\001\212\220T\310\330\251G\315\322\230\310A\245\010\330\344\322\265\273c \346\243\020\261\353R\01029\024\306\266\3054B\001\346\244X\300\350jQ\037\024\306LS1\351F\342(\363=\251\013\2554\225\365\244\033OJB\0054\201Q\262f\231\262\232\313Q\354\315C7\313\300\252\304\346\243~*\244\262b\251\311>*\263;9\342\230\310\330\252\316\305\r\t6MZ\216J\271\034\231\024\245\250\006\244\003\220hqP0\250\230T\rQ1\250\034\325g\025b1V\343Z\262\211VQ*\312%XD\253\010\225:%L\251S*T\213\036j@\224\375\224\236^iD4\030\351\276]\'\227J#5\"\241\025*pjl\202*\t\t\3155e`q\232\23599\315XQK\260\032cB\rG\345c\245(CC\naQ\351Q\262S\n\232aZaZiZL\021J3\336\227\024\230\246\262g\2655W\373\302\251\\\217\234\342\252\234\324o\310\254\371\301\346\251\030\231\317\025n\336\323\035EZ6c\035+.\356\307\004\220+5\241dj\221\033\025f)*\312\266i\300\346\254 \312\212V\217#5\013&*)\026\252H*\273T\rP\275[\214U\270\305[\215j\324kV\221*\302%XD\251\321*eJ\231R\244\tR\004\247\204\247\010\363N1\2208\024\303\037\255\001\005#(\355H\006(\357J)\313\326\207L\324b0O&\254\306\212\243\203\223R\201N4\337\251\240\342\231\232Ni\254*\")3\232FL\364\250\331qL\"\233\212\220 \3051\227\024\231\2434\202\241\232\000\340\234sY\322&\302EU\220\342\241\333\270T\220\300\255\332\256\307\000\003\201S\030A\035*\254\366\201\201\342\261/l\366\202@\252?g;rE5\020\206\255(-L\213\220y\241\340h\31755\271\347\006\247t#\351P2\324\022-T\221j\254\202\253?Z\201\315_\214U\270\326\255\306*\344B\255F=*\324kV\021j\302%N\251R\252T\201)\352\231\251U)\341y\241\200\355Le\246\021\212n)6f\202\270\246\322n\"\233\274\232o9\251\341<\363V\326\224\212L\n\215\200=)\002\232\017\024\322\302\230j2)\271\"\232Nz\323)\010\346\246P1Mq\223Q\342\223m.\312\n\341O\025\233p\233\311\300\252o\006:\324\"\"3V \214\203W\221x\247\221Q\262\346\252Ml$\353U\215\222\200F*\224\226![\345\025b\010\031\007\024\351>a\206\034\325`\233_\"\254\261$sP=@\342\253H\265NQU$\030\315U\220\326\254kV\343\025j1V\343\025j1V\243\253Q\212\262\202\254\"\346\246U\251U*UJ\220 \240\2554\2554\2550\2550\256\r7\034\321\203M\"\230V\223e(\2175,i\203V@\247m\3155\206*<sA\351\311\250\330\366\024\334f\215\206\215\206\230\313P\225\246\221I\212\221\006N)XsM\333N\tK\267\035i\030\006\025VH\324sT\245\000\232j\302\032\245\020\340\323\300\305.(\3050\256j3\0350\302\t\311\024\024\000t\252\362\301\225$U=\234\363O\"\242a\305WqP8\252\262\255P\230U9\005l\306*\324b\255F*\334b\255F*\312\n\264\225e*\302\n\260\202\247QR\005\247\001A\024\204Sv\323v\323YsL)I\262\220\245FiUsR\025\013J\204T\200\212]\300R3\203Q4\212:TM \365\246\207\006\234$\002\227\315\364\244\336M!\031\246\225\024\323\036j2\244R\253m4\343\311\315(\245\310\025\013K\223@\220c\025^c\232\250\312I\253\020\307\305LS\212a\\\016)\204RSM\024\322)\244TdsPK\010\373\302\241)\305@\353P2\324.\265ZE\310\252\023%R\221+^1V\243\025n1V\243\025f1V\220U\204\253\021\212\264\202\254\240\251\324T\240R\342\214R\021I\266\220\2554\214Q\2674\322\224\306\035\251\236^i\215\307J\211\331\2155d\"\236\327\001G\0315\013]\023\320SE\303w\244iKv\241\010\357R\0021F\352PsOZ\220\014\323YiB\346\225\320\001\223U\037\031\371i\013\343\251\246\231\207\255Ff\31579\247\n\n\347\265\036Ni\352\233zS\261H@\250\230\n\215\2522Nx\244\335FsHi\206\232j7@j\264\221\343\245Wt\250\035}j\264\211T\345\216\251\274y\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004\255IDATx^\355\335\321\226\2338\014\000\320\036\372\377\237\2749{\232m\272\035H\006BlcI\367>2\t\030ld\311$\231\037?xfYo\000\000\000`\210\217\353\261\345\343=\300\264n\353\rl\010\001\000\300\347E\005\000\000\000\031<\312Ck\353\000\000\000\323\363\204\017\020\t\3302&h\303H\342\210\233g\t\000\000\300d\324\263W\370\2738\324\003\000\000\000@0\341\2273\302\237\000\000\000\000\260\021\355\307\342\2075\3277\030\000\000\200\234\326eU\344\352g}.\ru\334\365\305\362\236\031\000\000\000\007(\013y)\362\"\021p\206)\201\277\231\005\240\004\241\237\223\226\237\353-\320U\343h\325xw\034\"\273\004\000\000`\237\352\221.\014,\200\227\254\227\003\000@1\212\000\000\000\n\363\330\020\000\200\272,\016\003p\221\321\0132O\247\274\345K;n\267\321\215\002.\36642\000\000P\213J\020\000\306\2122\367Fi\'=X6\204h\304l\310\313\375\235\314\3414\353\360\013\001`\2079\005\340MY\002\347\222\345D  \367\037\000\000@2\236\333G3\2602\367\273\014\274\305x\201\310\334\301\000\014a\302Ii`\225JSz\016\000\200\246$\230\305\031\000\000\025\025X\360Mv\212M\347\353\246;\003\372s\323\206\240\233\000\n\032Tt\014:\014\2632\000\000\000\000\240\252\343\217\036\254\037\000\000@\025\235\263\377\316\273\347s\307+\305b:]\230N\273\2459=\005\000@8\007+p\271nZ;\377;\360\373\277\002\344\'\016\262og2\005 \253I\342\377\301\262~>a\033\036\322m\226\361\n\220B\2429\314\364\000@\017\346\027\222J\224\005\002\000\345\311l\000\302\020\262y\305\032\034\000\000\364%\347&\224\027\013\010}\307\361\355\353Q_\264\001\250\304\267l+\373\325\371\006\000\000\344\243\324\203\000$\342\000\360\006k\230\000\033\277\213\177\0012\000\235\004\225\211\000\000\000\344\341\203\010\331\254z\364Q\276\334\326\3376\340\177\313\372\252Q\315\310\001`I\001\000\270\222\\\204\211\030\216\343\214,y\016\233\262Q\326N\200\234\3046\000\210\310\014\376\233\013\221\307\234KAs\266\n\262\021\313\001\000\230\205\334t\270 \227<H3\001\000\000\346\263}\340\252\304\002\250\314\017\214\027\267\374\\o\001\000\312\220\010\002\000\000q\251h\342+\327\207\345Nx\207\353\001@j&:\200Wr\177b9\367\331\3255\240_\007\034\3423\235s\233\315\3567\033\022\230\276\223G\312\330\301\234g<\000\000$%\321#\261k\213\374k\217~R\310FS\324}\3762\211\001\300\365\244\220\314l|\2768\376\210@\024\342\003\000\000\314\302z\026\000\000\000@\024Vr\000\000\000\000\000\000\000\340n\211\366]\345h\355\005\000>g\376\207\366\334WD\341s\337\245\351\376\016\"\305\177\003\240\270\267\007@\244\321\3153\341\026\252\001\242y{n\005\030\357v}\254\272\276\005\000\000\000\000\000@X>\376\002P\234\'\316\347\334\\:\000h\313\314\n\000\205\230\370\001\000\200\231\004\371\350\214R\252\270\t\276\316\016\000\000\000\300\033,\347\0009\210f$\026\344!!\000\320\310\005\037\274\270\340\220\034!\017\344\356\327\r\372\317z#\005\374\t\315\213E\017(h\221\007@I\2339\177\263!\006\021\254\021\027\222\347\202F\006\336%\004\024g\000\000\000\360\214<\021\000\200\324<\002I*Q\307\216/\312\336:\342[/&\212D7\020\224&DCY\207\277\010\262\010\024I\035\034\000\000u\325\230\001\217\235\345\261W]\250m\276b\216\004&\3264\336\001\334\265M\245\276!\313\232\323\260\001\300\\\222\335\220\311N\347\205\0067\353%\027\252\347\017\0076\270&\000\325\010\235\000\274\3201q\'\262O\006\306\355\243w\037\361\310ld8g=z\250wO}\357K\377\035\353\314c\257b\353\332\256\006h\313l\000@\006r\364}\3679\337\205:)\370\205\013\336|`\000q\002\000\370\236\222\362S\255\027\342?\351\214\263\357=\373>\272h:\244\364mu{\303\311\010\231\220N\001\372\370oJ\330\233\030\200\224\"\244\027O\303S\204\206\267\223\366l\307\235\330\270#\r\326\363\267\007\000\000\000\370c\306\352\353\374\217^.\347\337\032\315\214\035\007\227+\023\001\000\000\330\220\013\326\266\314\264 0OKz\362<\037\000\340b\023\344c\0234\001\362\333/\276j\024\241;\016\\\204\307K\376\005s\343\23463\025f\327\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
new file mode 100644
index 0000000..486adf4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -0,0 +1,3 @@
+tile_key: "3"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364/\245.\352pz\225d\247\356\3151\316j65\0314\302i\245\251\244\323sFh\315&h\315\004\323I\246\223M\335HZ\232M4\232ija4\302i\204\324d\324d\323\t\250\311\246\023Q\223Q\263TL\325\0235D\315Q3T,\325\0135D\315Q\263T,\325\0235D\315Q\263Te\251\205\251\244\323I\244\315&h\315\031\242\2274\240\323\251\300\323\201\247\003R\242\223Vb\212\264!\213\025\353E\251\245\250\017O\017R\007\247\026\030\037Zk\032\214\232\215\215FM74\233\2513K\232L\321\2323M&\232Z\230Z\233\272\220\2654\232ijijaj\215\215FZ\230Z\243&\243-L-Q\263TL\325\0235D\315Q3T,\325\0235B\315Q3TL\325\0235D\315Q\263TE\251\205\251\204\323I\244\315&i3Fh\315.isJ\r;4\340i\302\245E\315[\215*\364H\000\253I\355^\231\276\223}\033\351\341\351\341\351\301\363O\334)\215Q1\250\211\244\315&h\315\031\2434f\220\232ijajaji4\335\324\026\246\026\246\026\246\026\246\026\250\331\2522\324\302\325\0335F\315Q\263TL\325\0235D\315P\263TL\325\0135D\315P\263TL\325\0335D\315Q\263Td\323\t\246\223M&\2234\204\322f\214\322\356\2434\240\322\203O\024\361R\242\022j\334q\364\253\221\307\264d\325\204\253q-z\000z7\322\356\245\017N\017OW\251\003\346\235\2735\033TML\315\031\2434f\214\323sHZ\230M0\2654\2650\265!j7S\031\2522\325\031jaj\214\265F\315L/Q\227\250\331\252&z\215\236\242g\250Y\352&j\211\232\241f\250Y\252&j\211\232\243f\250\213S\013S\t\246\223M&\232M7u!jM\324\233\251A4\360\rH\020\323\304u\"\305S\254F\254\305\016:\325\250\323\025co\002\245\215*\344K\212\355\003\346\215\324o\305(\222\234\036\234\036\244Y*P\371\240\267\025\0214\302i7R\346\214\322\023M&\232Z\230Z\230Z\230Z\232Z\223u\000\323\034\324L\325\0335FZ\243/Lf\250\231\2526z\215\236\242g\250\331\252&z\211\236\242g\250Y\252&j\205\232\242f\250\231\2526j\214\2654\2650\2650\2754\2653u\031\247\005&\234#8\351J#5\"\307\355R\244C\275N\261\217J\231b\036\225*\305R\004\002\244QSF9\253\210\231\253\021\307V\022:\350\326Jxz\013R\007\247\007\247o\251\025\352U\222\244\017\221\3150\236)\204\323wR\206\245\335HZ\232Z\230M0\2650\232ajijM\324\233\261HZ\240s\212\211\232\243-Q\263Te\2526j\211\232\243g\250\231\3526z\211\236\242g\250\231\352\026z\211\232\242f\250\331\252&j\214\265F_\024\302\365\031~i\013f\232h\000\232\235\"\365\253\t\037\240\251<\272M\230\355O\013OU\315J\253\212\235G\025(\034RT\252*\304K\315^\215j\312%YE\2550\324\360\324\355\324\205\2517\323\303\324\212\365 zx\222\234\030\021McL-\315\033\250\335HZ\220\2650\2654\2650\2650\2654\2654\265!jaj\215\232\240f\305F\315Q\226\250\331\252&z\215\232\242f\250\331\252&j\215\232\242f\250Y\252&j\211\232\243f\250\231\2526lT,\377\000\205F\\w\246\347=)*EJR\242\204Q\232\262\007\313V\020p)\370\243m(Jz\256\rJ\027=*@\265 \024\273y\251\221j\314C\025v<U\250\326\254*\325\260\330\245\337K\276\221\244\246\357\247\253\324\241\351\341\351\302Jz\311N\337\236\264\306<\323wQ\272\215\324\322\324\322\324\322\324\302\324\322\324\302\324\322\324\322\324\302\365\0335B\315Q3\324l\325\031j\211\232\243f\250\231\2526z\211\236\242g\250\231\252&j\211\232\243-Q\263TL\330\250X\236\246\242f\246S\200\245\035j`@\024\335\324/Z\234\036\225j#\225\251@\247\201O\013N\333O\214`\324\333)\300S\202\232\231\026\254\"\036\325b4j\271\0225[D\251G4\326\342\243/Az@\364\340\365\"\311O\017K\346S\326J\225d\343\024\026\244&\233\272\215\324\205\251\013S\013SKS\013S\013SKS\013Tl\365\033=D\317Q3\324e\3522\324\306j\205\232\242f\250\231\3526z\211\232\242f\250\231\2526j\214\2651\232\241rs\234\324li\206\223\034R\321N\240T\2129\253\n\234f\255B\277(\251\302\323\302\323\202\324\252\225(\216\236\026\244T\251\026*\263\025\271n\325r8\002\216je\n;T\311\364\253Q\256j0})\254j\00684\335\324n\245\335N\rR\007\247n\245\337OY)\341\363\212v\352Bi7Rn\246\226\246\226\246\026\246\226\246\026\246\226\250\331\352&z\211\236\243g\250\331\352&z\214\276)\245\352\'j\205\232\242f\250\231\2526j\215\232\230A=x\250\233m0\342\230Xv\024\302F*&\344\323H\243\030\242\212QR(\251Q7\036*\340N\000\253\010\230\002\246U\251\002\324\212\225*\245J\022\236#\251\243\2135j8=j\342\"\250\342\2022i\311\035Y\215*\324Q\346\250\253qC6*\t\rE\272\200\324\340iA\247\206\247\006\243u(|S\325\352@\371\247o\365\246\356\315!8\246\226\246\026\246\226\246\026\246\026\246\027\250\331\352&z\205\236\243g\250\231\3526z\215\236\233\277\024\326z\205\232\241f\367\250\213{\323I\035\3114\302\303\265F\315Q\026\246\223Q\261\246\3474\206\220\2121I\2121N\002\245E\315[\202*\274\261`\n\225R\244\013\212z\256juJ\220-J\251R\252U\204\001G\275N\274\324\240b\225FMX\2162{U\350\255\275j\354p\001\332\271\250\344\342\234NEA#qP\226\240=<58\032pjvh\335F\352]\330\247\253\323\374\312R\340\323Kb\232Z\232Z\230Z\230Z\230^\242g\250\231\352&z\211\236\2432TL\365\031zc=3}1\244\250Z^j6\222\241g\246\357\365\246\226\246\026\246\026\240\364\250\311\244\315-\006\233E\030\247\250\253Q%^\211qV\224t\251\200\245\013\223\305N\211\212\231S5 \216\244T\253\010\234T\211\036MXX\352d\2075a QV\243\214/AV\343Bj\322%p\361\276j\300<T,x5Y\233\006\2205<=H\032\227u;u\033\251CR\356\245\335K\272\215\324y\224\205\275)\205\251\245\3526z\215\236\241g\250\231\352&z\210\275F\317Q\227\250\331\3522\364\322\331\250\331\261P\227\250\231\371\246\026\246\227\246\027\246\227\244\335Jd\342\233\232)\331\242\214R\221@\031\251\243J\273\nU\245\\T\350*`8\247\242\022j\334QU\205JxJ\221c\251\3250*x\343\346\254\254u:ES\244&\255G\020\035j\302(\035*\312-y\324G\275YS\221Ls\214\325)\033\006\232\032\234\032\236\257R\256[\245\004\225\353F\374\322\206\245\rK\272\227u\033\251\013\323K\323K\323\031\352&z\215\244\250\231\352\026z\211\244\250\331\3522\365\031z\215\236\243/F\372\211\3335\013=F\315\3150\2650\2654\2654\265&\352]\324\341\203E8\032x\245\245\0035 LT\211\326\257CVUsV#J\262\261\324\311\035N\243\0252.j\312G\232\220%H\251V#\216\255\"U\204CV\0251R*\223V#\216\254\252W\230F\370\253\010\364\216sT\346\353\232\213u(jpz\265o2\251\371\205\027\022\2536W\212\204?\255H\036\227w\245\001\350\337AzizB\364\306z\215\244\250\232J\215\244\250ZJ\211\244\250\232J\214\311Q\263\324e\3526\222\230^\200\364\307z\201\236\230Z\230Z\232Z\232Z\2234\231\2405<58\032x\247\216i\336\302\246ARb\205\034\325\270Z\257C\315\\\215j\312\255<\014T\321\256j\334q\364\253J\234S\325*t\216\254$uj8\352\312GR\210\352DJ\260\211V\021+\307\204\236\2254sv\251w\346\240\224\344Um\324n\247\006\245\337H\\\232\262>h2G#\275F\262T\233\3517\373\322\356\244\337H^\230^\243g\250\232J\211\236\242i*&\222\242i*#%F\322TfJa\222\243/M/F\372c\265D\317Q\226\246\226\244\335F\352L\323I\240\032x5 5\"\342\247T\317\265.0jt\034S\261OU\251\343\\U\330[\025~#\320\325\201\315J\213\232\271\014|U\330\243\342\247\tR\244y\253\t\035X\216*\260\221\340\325\224J\224%H\211S\204\251\343Z\360\245\233wCS$\2305i\037\"\232\346\2539\301\246\346\227u;u&\354\032\235g%v\346\231\300\357K\276\215\343\265;x\355I\276\232^\2432Tm%B\322TM%B\322TM%D\322Tm%F\322Tm%Fd\2442SK\320\036\232\357\305D^\233\232ijM\324f\2279\244&\220\032z\232\225ML\206\254\253qJ\0075*\n\231V\245T\253\010\265e\022\255\302z\003WQ3\322\255D\225r$\253h*tL\325\204\216\254\244uj8\352c\037\024\344\030\251\325jdZ\231R\247H\353\347tl\032\260\222f\254\306\346\245c\300\250d\342\242\335\2127S\203PZ\233\272\245^A\244\335I\276\223\314\305\036e4\311Q\264\225\023IP\264\225\013KQ4\225\023IQ\231*&\222\243i*6\222\230d\246\231i<\332x|\323Y\252-\324o\2434\204\321\232\\\321\326\201\326\236*U\251\226\254%J\242\246QS\240\253\n\2654iV\321x\251\222>j\334y\025v\023\232\277\032f\255G\035\\\216.\225e#\305Y\216<\325\225\217\024\375\264\252\225:\245N\211V\022:\235c\257\232\310*y\247\253`\325\330OCS\261\252\362\265@Z\215\324\340\364n\246\227\251c\220r3H\347\322\2432SL\224\236e4\311Q\264\225\013KP\264\225\013KP\264\264\303%F\322TfJ\215\244\250\332J\214\311I\346f\232_\024\364\222\236\355\306j\035\364n\245\017N\316i3\212p4\361N\003\232\221EH\242\246AS\240\251\320T\350\265a\022\254\306\265a\022\255\242\000\265:\307\306sR\242\342\255@\270<\326\244\003\201Z\021%ZD\253\010\225f5\253\n\264\375\224\365\216\254,ub8\252\312GR\004\257\230\244}\346\233\320\212\271\031\332\005M\346qU\344\223&\242-I\276\224=.\372ij\217~\rX\337\362\324\014\364\303%!\222\232d\250\232J\201\344\250^Z\201\245\250\232Oza\226\232d\367\250\332J\215\244\250\332J\214\311M2R\371\231\247#\324\305\362\265\016\356iCR\251\247\251\301\247\236FE*sR\250\346\244QR\252\361OU\315N\213S\242\324\350\265f4\253H\225:%Z\215*\312\002F\000\251\322?Z\260\221{U\250\343\253\360\'J\321\204c\025q\026\254\"\324\352\2652\212\231W&\246\t\322\254G\035YD\251\325i\341y\257\224\267R\253d\365\251\326\340\214\017J\223\317\310\250\231\362i\273\251\013R\207\243}4\2751\236\244Ir\234\324L\365\031\222\232d\246\264\225\013IP\274\225\003\311U\332J\214\311L2SL\264\303%4\311L/Q\226\246\356\247\003\232\221x\247\227\342\242\r\363S\263\203OV\251\001\315J\207<\032z\256\rN\026\244QS*\324\310\225:\307S\"U\250\343\315Z\216:\266\221q\232\231#\253QGV\322!\216\225:GV\022*\263\034uz\010\375\252\342GW#\\\212\265\032T\252\2652GV\021*UNj\332\'\0252-L\027\212r\212\371\'u\001\261N\rO\017\212\013qF\352M\336\264n\244-M/Q\263\346\204~\324\326|TfJa\222\230\322T-%B\362Uw\222\240i*&\226\243ii\276m\'\231M2S|\312\013Te\371\247\254\270\251<\352x|\212f~j\223w\024\34452\032\225\0175eFj\302\n\221V\246E\253q&j\312\307\355R\244Uj8\261\212\267\034Uv8\270\305J\260\373U\230\341\366\253+\037\250\253\021\307VV:\2364\253\221\014U\224>\225n,U\225\"\247J\262\213S\252T\250\234\325\224\025:\212\230\nP\265\361\376\374u\245-\273\221\320Q\346d\323\267r*U`x\244\'\232i4\233\251\013S\031\2522\324\253\300\3156F\342\2533\342\2432\323\032J\201\344\250ZJ\205\344\252\355%D\322TM%0\311I\346\322\371\224\236e(zij\025\351\333\261S\304\324\245\271\251\024\344S\324\342\246CV\"\003<\325\330\370\351S\252\324\312\275*dNj\364)\322\255\"U\244\2078\342\247X\261V\243N\225n5\2531\240\253J\243\002\235\220\rH\216;U\204b{T\350O\245Y\2103\032\273\032b\244\335\264\342\244B\331\310\346\255G&:\365\253qL*\332J*eqS\243\n\260\246\246S\221R(\257\215\213z\232\2267\302\034TA\260\334\324\241\263\315(~})\305\275\r7y?J\013SKTl\324\300\334\323\214\230\353PI(#\212\254\362T-%1\244\315B\362T\r%D\362Ui$\250ZZ\215\244\246\031)<\312Q%\033\351D\224\375\331\024\014\346\236\017\025,MR1\311\342\244\211\275je\301\251\220b\255F*\334~\365m{T\350\275*\334q\216*\324k\212\271\032\325\350\0235d\303\200\r*\340T\310\330\251\321\316zU\224\311\251\304\031\031<\324\251\t\354*\322[\271\253p\3321#5\261ib\253\202ji\342\000|\242\250\264M\273\245^\266Q\2140\253&\327w+Q\230Y)\213#\206\305\\\216V\342\255\307!\305[\212L\342\256\306r*\302-|`i\276a^\224\007,j`\330\024\273\263C\034\n\013`b\233\272\220\267\025\03357uG#\324\016\365]\336\241i*&\222\241y*\026\222\242g\252\362IU\332J\214\311L2Ry\224\242Jv\372pzz\275J\0374\360jD\251~\265,uaz\325\204\031\2531\n\270\203\000U\270\327\"\255\"t\253\221-\\H\363V\342LU\310\260\265)\223#\002\234\210I\346\255\305\026x\002\257Ed\314>\355\\\207On7V\214V\034c\025n\033\000:\212\273\035\242\000\t\351S\013d\316EXED\357R3G\214\001\232A\0227U\251R$\037\303R\205\031\371F\005+\303\221\3275\017\331\207a\315*\246\323\310\247\026\njX\344\346\264\255\344\r\212\320N\225\361a4\323@\342\236\0374\340\3243d\nB\334\234\323Kb\233\273\212a99\246;\342\253\273\324,\365\003\275@\317Q3\324N\374Uf\223\232a|\212\255#\325v\222\2432S\013\323|\312z\311O\022S\203\324\213%J\257R\253T\361\265MR\306j\312u\2531\212\267\020\315[J\273\n\360*\364i\234U\270\322\256D\265eH\025&\342G\025=\274e\316\024d\326\315\256\230\317\202\303\025\255\005\212G\216+A!T\034\212\231B\2020*\30279\002\246POJ\225b=\352e\2074\357\263\234\325\224\266P2y\251\004c\037(\247*T\241)|\2726b\221\220\021\315f]\023\033c\265Ij\306LV\244A\243\301\355Z\2606\345\025\361ni\271\2434\241\251CR\223J\314\n\202z\324d\344\322c\202)\214\330\252\356\365\003\265@\317P;\324\014\365\013=F\317\305U\221\351\202L\212\206V\305Tg\250\214\224\205\351\273\351\301\352Epj@\342\236\257S\253T\310j\314|\325\220*D\0305f>\265r1\305Z\214U\310\3278\253\361/\025\241\n\360*\352\'\002\246^*tN\346\255End<V\355\205\232FA\3075\265\033\0000\005J\2715aA=jdJ\260\211VcJ\260\251S\242T\253\036\343Rm\344\212\220F})\351\027s\326\237\260R\204\240\306)\214\225\233{\016\3420)\326p\354\353Z\341r\200b\255Z\251\003\006\276+\315&h\316h\315(4\273\251r\017\007\2458\000\0055\333\002\252\310\371\252\356\325\003\265@\355U\335\252\006j\205\236\242g\252\322=B$\301\250\345\220\232\246\317\311\250\313\322o\246\027\245\022T\210\365.\342*Dj\265\033U\2045f#V\321\252d\253q\n\271\030\351\212\265\030\253\360\247\002\257\302\274U\370S\201V\301\300\251\341M\307&\256,E\210\013[6V\273PdV\254H\007J\264\213VQj\312-XE\253\010\225j4\251\325*A\307\025n\025\030\247\010\306\356*`\224\316\344\nz\212~\007\343HV\223h\006\240xC\222H\2428\300l\n\323\212\037\224f\247\216,\034\327\304\006\2234\231\245\315.h&\223u(\227\002\242\222M\325\0035@\355P;Ugj\201\332\241f\250\035\252\026j\255#Ugza\223\212\255#|\306\242-H^\232^\227p\251cz\234\020E=MY\211\252\322U\250\252\352\016*t\025n!\322\257B=j\354KW\241Z\320\201j\364`\001R\250\334\303\025\243\004Y\300\025\265kl\240\002\302\264c\003\265ZAVc\025f1VPU\230\326\255F\265j5\315YU\240\307\316j\334\021\222\265<Q\374\307=\251\345qP\221\226\342\244\002\245*\252\276\264\305 \212B\200\362M5\306@\002\237\005\271$\034V\200\033p*@k\341\243L&\2234f\224\032Bi7SKTL\325\0235B\355U\235\252\273\265@\355P1\250Y\252\027j\253#Ugj\210\265A+r*\"\324\302\364\205\351C\324\310j\322\221\266\234\244\346\254\304\325r3V\342\355Wc5j1V\342\034\212\277\020\253\321\014\325\370V\257D0EX\317LU\313u\307&\266,\"\334A5\260\270\030\002\254\305V\322\254\245Z\214U\230\326\255\306*\314b\254\3060EZAO8\034U\373|2b\235\267nsQHK\034\nhV\317J\231P\234qJ\352FEV(\301\370\351V\022\022\335j\302\333\201\326\254$x\245e4\252\276\265\360\333\n\204\365\246\223FisHM&i\244\324Lj\0265\013\232\254\346\253\271\252\356\325\013\265B\306\240\221\252\244\215U\235\252=\325\024\255\305BZ\243&\232Z\205j\261\033\325\205z\231\0335j#V\3428\253\321\034\342\256F*\324uv*\320\206\257\302:U\370G\"\255\216*\335\272n\344\325\330\027{\214t\255\353e\021\240\035\352\342\032\267\035[\216\254\307W\"\253q\212\267\032\325\230\326\254\306\274\325\2001A^sWm\270\305Y\312\263`\323\035\0009\035*\274\227+\031\250N\247\351\201B^\031\\\016\265\251\034a\324\020*\302\305\216\225*\246jU\2175(\204w\250\335\0247\025\360\223\032\205\2523Gj3A4\334\323I\250\232\241cP\271\252\356j\263\232\201\315@\346\241cU\244j\252\355U\2445\036j)\017\025\016x\246\023M&\224\032\225\rYZ\2363V\343\253\221\325\350j\364F\255\307\315\\\213\265_\207\265h\302:U\370\370\305N\2373\001Zp!\332\005hZ\307\206\025\250\215V\3429\253\221U\270\315Z\216\256EWb\355V\3435n>j\302\2561S\016i\315\320T\320\266*u9<\365\245\221\266\216\265\227:\231\033\212\317\2226F\255]&\r\356\t\256\215#\n8\247\242\234\373T\333x\244I6\2674\351&\335\302\324Y\311\346\276\024\"\241j\211\250\006\202qHM!4\302i\214x\250\036\253\271\250\036\240z\256\365]\352\007<UY\rU\220\325w4\300y\346\242\220\365\250\r0\232J*D5j6\342\246F\346\256D\325z#\232\277\r]\205sWc\\U\310\205_\204c\025\241\025]V\033j\335\252\344\346\265`\255\030\260\243\212\267\031\315\\\214\325\310\232\256Fj\334f\256Dj\344f\256EWb\346\255\240\315J\027\024\377\000+&\225>Y\000\253\312\200\214\212I-\367P\266H{U;\273\020\275\005K\246\257\226\370\255\325PE;\201F\340i\n\212B*&\310\351_\016:b\2538\305@\324\212y\247\036\264\323M\3150\232\215\215D\365]\352\026\252\357U\336\253\275@\346\252Jj\244\206\253\261\246u\250\330\324,j2iz\nAOZ\231\032\255E\315^\210U\330x\255\010{V\204#\030\253\2503V\343\030\025r#\216\265r)*\354J\315\327\245_\205\266\340V\255\250\316\rh!\305Z\210\325\310\215]\214\325\250\315\\\215\252\344MW#j\271\023U\330\232\257Ds\212\2621\201Rn\003\245B\315\363\325\250\346!1K\346\2615<R\034\202j[\225\014\231\252\021\270\216Q[1\311\205\006\223y,EH\264\356\364\206\230y\257\210\235A\025RT\252\216\274\3231\203JO4\323M\2465D\306\243sP=B\365]\352\007\025]\352\254\206\252Jj\253\234\325w\246\257z\211\272\324\rL\240\232L\324\212jx\352\324uv#W\242\347\025\241\017j\320\207\265hF8\2531\324\350{\n\275\016\027\031\353Z\020\313\221\201W\355\343.sZ\220\260A\216\365r3\232\271\021\253\221\232\265\033U\270\332\256F\325n6\253q\265\\\215\372U\370[5z\'\253(sO$\366\246\204\346\246E5:%Y\215*r\273\220\212\307\234\025\227\217Z\320\266\237\200\255VP\202\306\247\024\242\202j2k\342f\025\013\256ER\225pj\022\265\023\036\271\246\253v4\036\264\323Q\260\250\232\241aP\270\250\034Uy*\244\206\252\310j\244\206\252\271\305@\335i\005B\335MB\324\312c\036h\024\365\2531\325\310\205\\\217\222*\3645\241\rhC\332\264\"\346\254\216\005K\t\3475ad\313`V\275\234D\201\232\327\215\202.\005O\023sW\242j\273\023qV\321\252tj\265\033\325\350\236\255\306\325r&\253\221\265]\205\361W\241|\325\304l\324\3523R\005\305J\242\254 \251\327\212\232.s\364\254\353\210\201r})\321\240\0035j\023V\001\247)\244\315FO5\361ST&\253\310*\253u\250$\353P\323\263\221\357HzS\032\243aP\260\250XT\022\n\247%T\220\325Y\rT\220\325g\250Z\232;\324-\336\230i\204TX\311\251\222\002\325!\210\255=\005Z\216\256\304:U\370E_\207\265h\300j\364b\245-\212zK\205\253\226\274\260&\266\241\220\200\000\253\220\271\'\232\275\021\253\261\265\\\215\360*\312IV\021\352\334MWaj\271\033U\310\232\256D\371\253qI\316*\374M\214\032\277\t\357V\343~qVP\206\251\002\355\251\026\245\335\201K\024\303\232\245qq\202\331\357L\212\1770\205\025\243\027\002\246\315*\236iI\353Q\232\370\271\226\240aP\270\315T\221H\317\245U\177Z\214\212fpi\364\204TdTL*\027\025VJ\247-S\222\252IU^\253\275B\302\233\332\2414\334Pc$Tay\253\260\201\306jFPA\305F\253V#\025r!W\241\253\320\326\2045v6\300\247;f\233\031%\200\255{a\2001Z\220\267\025v\023W\242j\267\033U\224z\263\033\325\250\332\256\304\365n\'\253\261\275[\211\352\344MV\324\364\305^\267\2238\006\264\340|qVP\374\325a%\305^\211\204\213N+\264\373S\325\014\247\013J\366\376R\234\034\361X\272\211*F*]92\0015\256\235)\333\250\r\3158\2650\265|j\353P\262\324,\265\004\211\220j\204\313\216\225\\\323\030R\253v4\374qL\"\243aU\344\252rU9j\234\225VAU\\Uw\025\t\024\323\322\242\"\205\\\323\312\361L\331\315L\203\024\346n\324%X\214U\250\352\364=\005^\203\255^\216\254\253b\236\032\226&\303\214\326\265\273qZ\020?j\277\023U\310\332\255\243\342\247G\2531\275\\\215\352\334oW#j\273\023qW\"j\275\023t\253\2617J\264\234\034\212\275o!$V\202\265L\206\254E!C\221Z(D\210\rIn\n9\367\251\'\031J\306\324\340$\006\247\331\r\250+@\032\t\244\006\234O\025\021s\234\036\265\362\003-FR\243h\352\007\217\212\316\236>MTe\301\246\025\246\355\3475&8\246\225\342\242qUd\252rUI\005T\220UY\005V\220Uw\025\013-4\255D\313\3159\006\005;nW4\230\246\223\212h952\n\263\030\2531\365\253\261U\350{U\350\315M\232x<RFI\220V\275\273`\n\275\023\362+B\027\253\221\275XG\253(\365j7\253Q\275\\\215\252\344OW\242|\325\270\236\257\302\365v\'\253\261\266j\334M\202+F3\275x5<G<\036\265>p*{{\222\204/j\322I\200 \372\324\3228#\216\365\235\250\260\362\300\250mxQW\003Rn\245\315.\352%Q\267x\355_#\262TE1Q\225\250]*\224\361g5\237\"`\363P\225\246\225\247\017J\030qU\344\252\222UI\005U\220Ui\005U\221j\263\255Wu\250\212S\031j=\2314\360\231\340R\274ei\241)\222\257\034Th\265e\026\254 \251\343\034\325\330\252\3545r:\237\265*\032\222 \004\2315\247\t\343\212\267\033t\253\3617\025n6\253(\325b6\253q5ZF\253q5^\210\346\256D\330\253q\275]\211\352\364Rr+B\'\253\221\232\275n\3705qFy\035j\300\345pjkU\035\372\325\254g\247j\236\',pz\n\316\324\244\314\241i\320\034(\251\367Q\272\200\364\355\324\273\262\214\t\342\276Rd\250\214t\302\225\004\213UeL\326t\361\363U\331)\205=)v\2201Q\222T\340\364\250%\252\222\n\255 \252\262-U\221j\263\255Wu\252\356\265\036\332\215\222\221S\034\324\210\203\322\234c\315F\321\342\242e\317Zb\245N\213S\250\002\246\214d\325\270\305\\\212\255\307V\0074\252>j\220\374\244\032\277n\331\025n3W\342<U\230\332\255+T\361\265[\215\261Vcz\271\023U\370\037\025y_ T\350\374\325\310^\257B\375*\374RV\204\022\014\001V\243\223\232\320\212L\212\264\255V\"85e[\232\231[\0035\215;\371\227\'\332\255\306x\2517PZ\223u8=9[\203_-\236j29\2460\025\021Pz\325yS#\212\243,95]\355\375*/+\035i\254\265\004\251\306j\254\202\252\270\315Wu\252\316\265Y\326\253\272UgZ\201\326\243+Q\224\311\241\223\260\247\252b\234x\250\\\346\243)H\027\025*\n\223mX\211qV\220U\250\352\334uaE?\035\351G\315\305]\267\\\001Wc\253q\232\265\031\251\321\252\324f\254#{\325\230\332\256\304\365v\026\253\250\374T\350\374\325\330Z\257D\365z\'\253\2615]\215\352\344S\205<\236*\364R\206\344\032\262\222b\254$\231\247M8X\2175\231\021\313\226>\265y\033\212v\352M\324n\2405H\255_1\025\250\312\324l\265\023\n\211\327\212\256\311Q\025\333\332\240t\311\250Y*\t\027\212\245\"\365\025U\306\r@\353U\244Z\256\353U\335j\264\213P2Te)\004tyy4\273*6\\\323<\274\363OX\367R\233zO/m=V\246AVPU\224\025f1VR\244\305 \030j\277\017J\264\206\254\304j\334f\246CV\024\342\245G\2531\275\\\211\353B\007\343\236\225ie\315X\215\363W\242z\273\023\325\370_\245^\211\352\344oR\003\271\261\232\323\200\225P\005[V\251\267\0208\252SL\354v\223SC\300\025d5.\3727Q\272\22459Z\276me\250\212\324l*&Z\211\205B\313\326\242d\250Yj\027Z\253(\252R\202\017\025M\371j\215\226\253\272\325wJ\257\"UvL\232\211\322\242\331\223Jc\300\244\tA\216\233\345d\322\230\251V,\032\231\"\315#Z\363Q\030\n\366\247F\234\325\225Z\235\026\254GV\024T\242\223\034\325\2503\212\271\0375f>*\312t\251\220\363V\024\324\250jt`*\334-WRLU\230\3375j\'\301\253\360\276j\364.\001\031\253\310\300`\203Waz\275\033\361Vm\206\351FkMxc\355J\267J\247\031\251\305\300\333\234\325r\333\3375e\016\005I\272\227u.\352]\324\241\251\352k\347\246\207\212\201\243\250\314u\013\2475\013\'\265B\353P\260\250XT\022\n\251 \353U\312d\034\326s\256\030\212iZ\211\322\240d\252\322&N*#\026\005B\351\236(\020\200=\351\217\035\'\227\212<\2726zR\371t,y52E\203S\371{\227\336\230\326\371\355Q\233r\247\212x\216\244T\251\025pjt\025 \004\324\210\207\251\2531qVc\253h8\0252\364\251\022\247S\300\251W&\247\214z\325\244|t\251\343z\271\023zU\3049\372\325\270d\305]\211\352\364RU\350^\257\305%^\265\227ksZ\261\220Ww\255S\272\217k\006\007\203R\303\222\006MXQ\212\2206)\341\251CS\203R\346\234\032\236\246\274\r\333\002\253\261\250\331\261P3TMP\262\346\240u\367\250\035N*\006\004\216\225ZD89\252\344qT%O\336\032n\312\215\226\241t\315Bb\357PH\230\246,<d\3222\034\323\032>:Q\345\361Hb\243\312\366\243\3134\242<\032\220-9x\251@\004PR\233\260f\245H\201\024\361\020\251\025\000\251UG\245J\0234\021\264\200*\314c\246j\332\216)\352jU\251\324\360*t5:5J\246\245F\253\260I\212\273\033\346\255\306E[\214\325\310\236\257C%^\211\352\3442a\205lE\'\356\361Q\334I\362\250\245\205\361V\203\346\2245?u(l\323\203S\203S\303S\201\257\004qP\271\250\035\352\273IQ4\265\023\313\305Wi*&\222\231\2734\307\\\212\252\311\212\253:\014g\275C\214\212\215\226\240n)\215\310\250\032=\306\244\021|\265\031\206\230c\244\362\275\250\362\250\021\212_&\223\312\244)\212P\225\"\255?o\024\322\234\323\324b\236)\300T\213\301\251\327\326\243\335\271\352\304g\221V\225\252E5*\232\225ML\206\246S\306jEl\325\2045b3W\242n\225r6\253\221=\\\214\325\330\217J\271\023\325\244~\365~\332\344\343i54\222n\024\350\332\254+\324\241\251\341\251\301\251\301\251\301\251\301\252Ej\360\211\rU\221\252\244\215U]\352\026z\211\236\242f\246\023\232r\212y\\\212\202H\370\252\222G\236\265\003\304T\373S\032<\212\254\361\020i\2062h\021\340R\355\342\243d\250\331(\tHc\244\362\361I\202)z\323M\"\216j`\005;\024c4\230\346\235\212x\024\341R\216\225]\270\223\332\255\305\332\255)\004{\324\253R\255J\2652T\240\372\324\253S#U\210\315[\215\252\344MW#j\273\023\325\350\232\255\306\325e_\212\232\027\303\n\272\357R#T\352\365*\265J\032\234\032\234\032\234\032\236\032\236\255\315x\\\246\251\311U$5Y\315@\306\242&\232E&)V\246Q\305+G\362\232\241\"\340\325yNF)\241\306\323\270T$\206\351HPb\232R\233\345\346\220\307M0\346\232\321\342\243+\212i\250\230\3233M\335\212i\220\n<\352p\236\244YjU9\247\250\251DD\364\247\213f\245\362\266\3655\033\305\236\2254Gh\346\246C\310\253IR\250\251TT\312)\343\212z\265J\246\254Fj\334g\245[\215\252\344mW\"j\275\023U\330\216j\322\216*x\370 \324\373\363R\306\334U\204j\231Z\244\rO\rO\rN\006\236\r<5xt\225RZ\247%Vz\201\2522(\305.\332\002\363VR?Z\216\346@\243j\325\006\250\035\t\250\231\030\016\224\301\031\317\"\237\266\232\302\232\0074\2458\246\221M`\030Ui\001\006\242\'\025\003\232\205\237\025\033IQ\027&\22015*\006c\300\253Q\333\310q\301\253q\333H\007\3355e-\237\031\332i\342&\035\2158\254\200t5ZF#9\250\222|\036j\310;\271\025*d\n\271\031\340T\340T\242\244\006\236)V\246Z\231*\324f\255\306j\334f\256D\335*\364M\322\256\302\325v6\315\\\214\003\031>\225\t\233i\251b\270\025n93V\025\252e4\340i\340\323\301\247\003R\003^+\"\3259\227\255Q\220u\252\316*\002)\204Q\212Z\232\030\363\311\245w?\303U\232&s\223J-\375E\006\334SL\000\366\250\244\200\016\325Y\223\031\250Xg\2450\214\032p4\326\3053\245G\"\203U\\b\252\310*\273)4%\273Hx\025m4\247n\306\255E\242\271?t\342\265\255tE\030$\001W\377\000\263\242U\343\255=mbP8\247\030\223\267\024\326\265N\242\201j\230\346\250]X\214\235\2039\254\271,\212\234\343\024\350\240}\330\305h-\266V\244HJ\365\253\n\274T\241x\244<P$\002\244\017R\253f\254Fj\312\036*\304mV\342j\271\023U\330[\245]\210\325\310\332\256B\340\002\017z\204\304\314\307\236*E\201\207\275X\2100\351V\243\223\326\255#T\231\247\003O\006\236\r<\032\361\347J\2472U\tV\252:\324\014)\204R\021M5n\022\014|S\322\035\334\323\314@v\246\230\352&\\TG\255\014\241\205U\232.\rS\333\202j\0318\250w\363K\234\322\212cT\016\231\250\014\005\317\002\245\217N,\303ul\331\351\n0H\255d\260\215\000\342\236#U\340\nF8\246\022MF\331\246\362)7\237Z\004\236\364\355\343\275C2#\203QG\030\317J\262\024\001\236\364\207\031\245Z\220t\250\035\300\004US!\315M\034\276\265n6\310\253Q\265ZC\232\235\rY\211\252\344mW\"j\275\023U\330\333\245Z\215\252t\346\254%K\264u\035i\244\367\035EK\024\231\253hr*AN\024\361O\006\274\226E\252\223/\025\237*\3259\026\253\260\346\243\"\232E4\2415,D\250\305_\200eO\024\346\004\236)\276Y\357P\312\270\252\376]!\\SfO\220\326pB[\030\246\334A\204\310\025\232F\032\244QN#\002\230FjH\355Z^\202\264\355\264\262{V\202YG\037Q\315M\200\275)\215%1\237\322\243$\236\264\224\322i\204\323MFN)7R\026\240>:S\274\312PjE5 <Uk\210\333\250\351U9\035i\312\325j\027\307Z\275\035\\\214\234sS+U\230\332\256Fj\344G\245]\211\272U\330\333\212\265\033U\250\332\255F\3258\366\244x\311\345z\324`\0259\253p\311\221V\223\232v1N\006\234\255^W%S\230U\031\207\025FQU\210\246m\315K\0349\353S}\234c\245:;M\307\245\\Kr\203\030\2462\200x\245+\201U\234\014\363\315D\330\003\201P2\346\227fF\rD`U\344\n\255r\000\214\326)L\261\251\025qC\220)\221\251v\300\025\277\247\331\034\002\303\025\247\263`\300\250Y\252&&\243\3154\232i4\322i\214i\231\244-M&\230i\271\244-NSR\003R\003R\006\300\247\3440\301\2523\306\003eEB\005O\0305~\002E^C\305=j\314f\256Fj\344F\257Dj\334mV\320\325\250\332\255F\325j3\232\231i$L\214\325p\333\032\256\305!8\305M\311\247\000GZp\257.~ES\226\250\313T\345\025\\\216i\361C\270\364\253\211\016;U\210\355\363\324U\330\241U\035\0056\343\n\274U\0227\034\323%<qU\230f\242aM\305(\024\307\\\212\317\272S\264\326[\020\246\2432zS\222&\224\360+Z\306\304\002\t\034\326\332\3425\300\250\244\2235]\230\323\r4\221L&\243-L-L-L-I\273\024\322\364\335\324f\2239\247-H\r<5;4\345jl\204\032\210(\31752\001Vc|U\244\222\245\r\232\263\033U\270\216j\354g\025r#W#5n3Vc5i\rZ\215\252\302\232y9\025VU\301\247\301!\007\025u\0375(j\007^+\313\244<U9MT\222\252\310*!\036Mh[\301\201\234U\225\213=\252dL\nq<{UY\316\343\201\332\242\333\305C \250\030Svf\230S\024c\212\215\306*\205\3361Y\023\250\'\345\346\231\024\005\210\310\255kx6\340\001\212\325\210\010\320b\202sQ\265FsMcP\263Tl\325\031j\214\2654\2650\265!ji4\204\321\272\2245(jpjw\231\212p|\323\303R\026\024\200\363R\253\001\324T\361\310\t\025e*\302\n\260\234U\250\232\256\304sWb\253q\232\267\031\253iVP\325\2045e\rH\r5\306G5]xz\275\031\310\340\324\240\323\324\327\226\311U$\252\262T\004f\247\267\203q\316+M\"\332\240S\266zP\303h\250\334\361\216\365Y\2074\323\300\252\322\236qQ\3434`\ncS0*\0311\315P\226#)#<T_b\000\363V\342\265D\000\342\234\355\260\361\214\323\321\313\032\224\360*\031%\n\t5J[\325\\\363U\216\242\t\347\245\037o\214\322}\241\033\241\243x=)\206ALi*3(\246\371\264\307\237m@\327\200t\250\215\343\036\224\365\272jx\270cS\244\247\034\323\213\023OV u\2517\232P\371\251\343\367\247\367\251U*\314d\216\265m=j\302U\210\352\354]\252\354Un3V\3435j3VP\325\2045f3S\nq\031\025VE(\331\02542U\260r*E\025\345\222\032\251!\252\315\315\010\233\230V\234\020\355\025c\024\207\201\357Q1\342\240s\324\324-Q\310\330\252\262\034sQ\027\003\2554\315\237jkJ=j\007\271\003\245R\271\272\003\241\346\243\206\350\036\365ed\337R\006=*\'\0074,\273?\n\251q\251\225$\003Y\263_\273\367\252\215#\267SM.GZa\227\322\201pGzQz\313\336\246[\364?z\246[\204q\301\246\271\003\221P<\307\265U\222V5\017&\246\215\t5m#\030\251V1R\252\323\302\212\177\035\351\014\253\234R\207\031\251\221\352\302\266MXJ\260\230$U\310\207j\260\023\214\212\2321\203W\"\355W\"\253\221\325\250\352\324f\254\245XJ\260\206\247S\232\220sL\221r*$\0305n3S\003^U)\252\222\034\324[rj\345\254\005\233\245i\210\266\212B\270\344\324NE@\354*\273\032\211\217<TL3\326\253L@\2522IP<\270\357U\236s\330\325y&n\325RB\317\234\322\300\010`+^.\000\251\2529\016;\325\033\213\225@@\353Y2\271s\232`J\016\005W\222AU\332J\211\244\250\332Jn\372U\235\220\360jU\275q\324\346\236/3\326\227\317S\332\236\222\'z\225$L\361VU\301\357R\006\003\275)\235W\2750\334g\356\320$8\347\232\003sS!\253\t\323\212\261\031\253Q\232\265\027QW\243\343\007\265[A\221NN\265n*\275\025ZJ\265\031\2531\232\265\031\253)S\255N\225*\322\260\310\252\303\206\305N\207\025aMyT\2035\027\226X\361SEhI\344V\224\020\204\035*f\340sU\235\262MV\223$\324,\246\242~*&5\014\207\000\346\263.%\3118\252N\365]\3335\021\353N\n\034`\323M\267>\324\242\r\274\212\235\034\255;\317\342\232\356XqY\362\332\263\234\365\250~\312GQQ\310\205\007\002\263\347v\006\252\263\032\211\230\324Li1\232pBi\336Q\243\3114\322\230\245\tN\013\216\364\360\017cR\2430\357Roj\006M<\034T\212\325:T\3501S\243m\342\254\241\315Z\214U\310\305^\217\221VS\245=z\325\230\352\344F\256Dj\334ua*\314f\254\2475a*e\251\224\324\202\241h\362\331\025 N=\351\352ppk\314\266n5f;lu\034\325\250\341\305M\267\002\241\223?\205C\345g\223\322\241p\240\361U\337\212\254\3475\013\266\001\315f\334\317\270\220\247\212\240\355P75\033\na\024\200\342\232\327\005j#r\344qP5\313\203O\216g|U\264}\243\236\264\3573\'\245)\301\034\325y\242\3348\252\022\330\263d\325\tm\031{Usl\304\364\246\233F\364\2446\305\006H\244\001\207j\031\210\246y\236\246\227p4\322@\243u=ML\206\244\342\214zR\212\221jt8\251\225\252\302|\334T\311\225<\325\350Nj\344g\326\257BF*\324~\2250J\2321\212\263\035[\214\325\310\315YCVR\254!\305XCV\026\244\007\025\"\232\\s\232\224\014\212G_J\363\310b\311\253\313\026{T\2420)\257\201P\2200Y\272U\013\233\241\321zV{\316{\032\256\323\222y4\323.{\325;\211\267p\275*\213\363P\260\250\210\250\332\231\365\250\335\2608\252\315\315>%\365\251\232\335[\266)R\035\2751N8\007\223L/\375\334\032\215\213\372\322y\244pM5\3468\342\253I.z\212\256d^\324(\311\245p\270\347\025VIQx\025Q\2432\234\203M\373&:\265\006\334\216\206\220@\304\324\211o\357R\224T\036\364\300rx\251\000\247\201NU\317J\225P\324\252\2652-L\203\025j2\017Z\267\020\002\256\304GCV\343\307j\266\225a\rL\242\254F*\312\n\267\021\253IVP\325\204\251\320\325\2045.x\245W\251\224\346\245^:S\310\334+\202\206,b\256\252`R0\252\362\270S\317J\314\274\273\317\312\275+-\344\311\252\362IPn\315#\023\212\256\365\003TMQ0\250\332\242j\256\346\241=jH\363\326\244iO\255F\323\036\334S\001$\362i\031\310\350j=\355\330\322\035\346\242e\220\372\324N\204\014\271\250|\345Rr*\031/\030gh\305T\222\351\333\275W23\032\2269\034p\rN\245\217Zx\311\247g\024\231n\302\201\0339\346\245\020m\353N\tO\tOT\"\244\037J\225@\251\227\035\252U\251S\212\265\031\253Q\232\271\023U\310\332\255Fj\312U\250\371\253\010*x\3705r3V\022\254!\253\tS\245H[\212@jTj\262\255\305H=\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\026IDATx^\355\335\333\266\243(\020\000\320^\231\377\377\344\311\232\311\255\317\211A\243\010J\301\336O\335&\022\254*\020r\351\376\363\207q]\247\007\000\000\000\350\323ez\000\000`\216w\216\001\000\000\000\000\240y\276\007\320;\037\330\000\000\000\014\3046\037\000\000\000\002\261\221\357\231\354\002\000\000\000\000\000\000\020\224\237\247\237O\016\000\2003\370\352#\000C\370\262\351\256}?\254\335>\000\000\000la\237\272\326\2277\024\2003\031\240T\241\260\000\200\026\014\277&\271\016\037\001\250\301\300\002\000\200q\244\277\022p\271\244\217\263\235\035\026\000\000\020\200\255K\232\270\220\2440\350\225\332\006\000\000\000\000\000\000\200\306\370y\017\000\300D\344\005\222oj\026\220]\000\201\242\237}\215\335)\023\211@\251\007\240Weni\014E\321@P%\266\037\327\177\247G\3063\356$x\235)\242q#RV2\270-\222pT\001\037\224\004\014\305\220\007\310Sl\323\227?\021\347\237\311\016\037a/V\nG\t\327a\000\240\214\217e\014\000\000\0004\315Nv\007\237\005@\017\214d\000 \213\275\024@\030\246l8\235a8\250\265\211_\373<\000\000\200\265|\017\000*1\270\000 &o\304\303VF\r\0000 K \000*q\2139\316\330\261\366I\346\237\177\246\007\016u\271\304*@\005\223ic\340bU\305(nY\t\233\231\260\035o\207\020\016@\222a\025Ce\233\237E\340\306\345`\253\024\300\031\352\025\317\346\226\317.\200g\2077\367\033\316r/V\025\013\035+6\300\2135\304\261v\254\215\2469\23745}\030\350\336\216\t\205\346\310\346\300\336\223\177\335vCO=;u\254\022\205;c)0\357\371\271?\363\357\323\227\316\333\250`S\354\224\316E\372\350 \016\234\2456(\220\222\307{z\253.\257\300\253\235*z\377w\233\317\362\374#w\303G\256o\337\322\373\355\361\275\276T\037a\234\236\311\332\245\332\261\323s\027Q\007AK\r\231\313\333\205\335\277\352\237zZ@M\\F\023\235xIv\246\334\257;\236\205\264k\240\354:y<\305r\307\010\272\036]?\027WnJ\353\315\257\002\350\272\026\000xi\366\226X\362>\324\354E\236huLJ&\342L\253/x \367\334\326\nLf\273\275\324\333\350.\271\005\020\226\312\205\035F\2330\000\206\222\275Jrw\250Fh\271d\017\3142\376\326\340\311\375\240\0029\005\240\tV\274p\234\357\343\355\3733b\n\264\364m\246\253\315t\204|\257\001\375\1772o?/~\3765\177\230\'\213\"\2779\340T\311\021\r\000\014\315\372`@_\222\036{\303\367\345\342z\365\223\264\330\351\353\234\344\214\315\017\3449\314\255\326\006\275\035\002\360#\275\364x\334 \334&\016\220N@}9\257\233s\016\007\311\034\255\263\247\315>\300(\214\367C\031q\000\000\235K\254\257\217Z\003\336^:\361\362ws\307\227\034\325\357~\345D\035\000\212r;?E;ao\247\'\024$\255\300K\353\363\201MqE\333\223/\035\345Mc\272=+@H\323\301\177\214\274)&\357,\032\2646\225k\237G\001\347\314\005\234\312\010\033\314$\341\341\006\275\202\355LN\005*\202\200$\215\202r\346\r\326\020Y\240M\226\021T\244\274\356\002\205\241\314r\245L+\r+\221\321\022m\324\327}*\363\304H\036\000\000\300\273\373^\346\366\357V\333\353m\323\326&pO\366\236W\262\247\t\222\202\204\364\270R\016\022\020\000\030\213\033t1\307\255\252\226Ii\023Z)\207\251\352\375R\177\215\223\240\261]\025\300\030\344\231e\325\027\003\023\275Wd\357\327GS\224\333fG\317xU\225\310\177W\001\001\322\322\003\3752\367\000\235\221f\036T\302\340v\177\003\354\262\343\\\032\223\223\312\234s\310\'\336T\245\300:\363\270\307_[^\353\265\334\267\320\226\003[r\250\337~I\002\214hy\236!\214\334D\346\236G\'\024\000@\024\211\031\273\3517\t\250\344\261s\177O\275B\030\234\002(\372\366\0304\250B\211Wh\222\352L\367\274k\254\"L+\301\224I\330O+e\332[\322X\305\237\353\025\214\374\240\024M\235w&\026\255\213N\201<\254t\334+\361\260\256\002\310\"\270023\000c\353\177Ig\214\337\t\303\340f\n\340\232\271\007\337=q\354n`^\305\246\343\232I\263X\3018f\246\001F\241\000\000\370\253\231]@3\035\031\324\001?\370\336\370\n\326+\017\033\303\026@\177W\004\001\031\210\320\260R\003t\266\235\331\007\0060\304\002s\335E\256{\026\364c\300\251\3170\347I)\3600\340<\310/\362\017\034\312\244\003P\313\3103\254\315\355J\267\"y\025J\253A\363\177R\325\326j\346\001\000\0006zno\326\356r\326>\017\000\200#X\235\215\300\'>\207z\206\273\335\261\365\3363\325Q2S%\333\002 $\267\002\000\240+\3365H\213\037\227\330\313\326\370\361\247/\261\307\023@\014\346Z\232\024bY\372\326\311\020=\216\342o0MP+\tT\033^\225k:\330+^\004\357\377\213E\274n\003\005\271\027\227 \212P\2261\005{X\3363u\275\225E\370\302\010\177\001\'\022;\240W\366\r#X\270\213)\000\200w\013S\346>U\032N\314\342\211C\213\266>\277\204\353\031/\332\233IA\025\ti\221FV\2512\034\006\261\030\273\305\007\233\361\253\320bt\270\005\223\321\371\036\270\2310~\214\350\327\201\217\007hV\361\\\335\277\330\004\204\264yB\230\216\367{\003\323\2034\346\232\221\351:\224\312I\236\201\377)\203\234L\334\316y\235\367j)\247\035\372qidf\341`\006>\360\303\2140\n\367\374\215\014\r\030Q##\377\327\367\320\357\223\267\217oZ\223*\224\337\331I=\276\305[\332\211D\326F\267\267\002\366N\036@4\257Qo\364\277\023\217\256\364\234\316\2757\376hF\273\336\257\366\006\344q\376\3077\345\247\177/-{L\326\356\330\313\257\016\036\365\222\247h\372\342\026\252\244\351~\227\262p\375\364m\210\372\246\032\365\003\301\275\177&\367\366\207\rr\316iC\334\236\307b\235\231\253v\205^sr\263\241S\031\2553\006\245\001\000\000p\246*\273\262*\215\022\301\206\367\212\032sV\321\306\215X\302\245\257\313\201\263\2345\037m\027\247\247\241\010+\320,\377\034khU\263W\265\361z\202v\273\025\327\217\355\354\256o\232U\2605\301[\237\017\254\031\354FV\347$x\237\250\361{\374\3703j\357\331l\315l\017\324\267b\3325\\w[\021\345\272\016\313\341\351W\312@\016+kj*\232F3\320\340\024\300\331d`\352m\206\353?<>\351\345\215z\340TE\227\230\305\233\343\253\3523\310\363\277\240\370\370\247(\327\252\336A\362\345&\265\260 \353\242\030\275<Y#%\005\361\230a\000\330\343\324E\330\334V\261\263\233\333\221\2273FDYo\246\"\200.|\033\341\337\036\007\000j\330\260\375r\263\036\333\206R)h\356\235\030 \251\346@\255\3316p\276\364\030O\037=\312\271\257>\246I\314\367\245 \347\354\337\337\251\3139\177L3\221Z\273\216^\216y\342\320\321\326^\010o.{\343\266\367\374%\305\276=[\252\235\n\372\330\311\026\313\324/\345\002\363\210q\271\366\272\262=sa\003\031\266\343Um/\200C|&\253\321\216\306St\t\377\'\225\252]\n7G4\351\002\330]\246p\223./\226\030|\203Z\231\370Rc*\361r\245\232^\345\320\027\013\344\371c\371\300\266\365\277\306\033\032\215\032\347J\001\236\252M|\026\021P\201\201\005g:{\004~\274\376\307\201\031\033o\366\033\237\036\337\177\034\3615X\n\320m\016\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
new file mode 100644
index 0000000..0cb5489
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -0,0 +1,3 @@
+tile_key: "5"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\325\210r*\326\332c\257\245S\272\203zq\326\271\353\244*Nj\203>*&;\272S\243OZq\217\212\253\"Ug\025\021\025\033.*\"\265\023\212\254\313M\021\344\323\230m\340TMQ\363HN\005D\304\323w\221NI\260y\251~\320\244t\250\245UpNj\250\201[5\024\226\313\216*\224\220`\324~MH\251\212\260\210jeLv\247\010\363\332\236\"\245(\007|R\205\007\2758*\216\324\274P>\224\360=\251\340T\210\2652\n\235\026\247E\253q\255Z\215j\344kW\"J\267\032U\224\\U\204\025:-[\215j\312-Y\215*p\000\247n\354)\342\244\002\245U\251A\342\245^+\224H\360j\310N)\205EB\351\3075\215\250\332\203\222+\233\271\214\2515\004g\346\346\256*\361JEV\225j\233\255DF*6\025\003T.*&Zh\030\2465D\334\232i\024\302)\214\265\023-3i\245\333A\004\212@\215\332\227\313\343\232i\266R3P\274\000S\004C5:E\236\3258\207\003\232]\241z\322\023L\306\343NU\003\245<&z\322\371b\236\020R\355\245\013R\252\324\252\265:\n\260\213V\243Z\271\022U\310\222\256\304\265i\005N\202\254\"\325\230\326\256D\265e\026\254\242`PF)V\244Z\221y\251\207\025\"\325\210\327&\271\245^j\302\307\305F\361\324.\244\203\212\315\271\214\220w\n\347\357m\216N\005d\264e\036\255\306r\005+\n\255\'5]\222\240u\250\030TL*&Z\211\205FE7mFS\232F\\TdSJ\323\nR\010\251|\272M\270\355Ml\343\212\210\356\244\332\306\224D\307\255H\260\016\365(@:\n\010\250H$\346\223\031\245\013O\tO\000S\250>\324S\224T\2503S\252\324\350\265b5\253q-\\\211j\344b\255\304*\312\212\235\005Y\215j\324kW#\025e\026\254\252\361\315F\364-J\24352\214\np\346\254D\274f\254E\367\253\237)\212\221\007\024\025\315@\311\203Un\242\014\206\262\036\000\344\202+\026\356\334+\236*\262\246\332q\034T\016\265\023/\025]\326\253\270\250XS\n\324L\265\031ZB\224\302\224\306J\217m!ZM\224\340\224\326\300\250Z\233\326\215\236\224\345^\330\251\004g\256)\3338\351M)L\332O\322\232\313\212hQ\353K\201J\0058\n\\R\201O\013J\026\246E\253\010\265:-X\215j\324kV\343\025n!W\"Z\262\213V\021j\314kVcZ\266\202\254\247\025)l-E\324\323\305M\030\251\324qNQS\257\240\251\342Z\306)\236\324\340\230\024\205j\027L\325k\205\371Mf\204\371\3533P\207\014H\025\224\313\212cTn8\250\034Ug\025\003\255FR\230R\243d\250\212\321\266\223m1\222\230c\246\371t\322\230\246\232\211\206j\"(\013R*\342\237\200)~\224\264b\243lTE3\336\223\313\367\247\005\024\360=\005;fz\321\263\024\273i\333qJ\00752-XAV\021j\302-Y\214U\250\305\\\211j\354kVQju\025b1V\243\025j5\342\247QO\3054\256\r9EJ\234T\353\315H\242\254 \253\n0++m.\332aZ\211\205W\225A\0305O\311\352k#P\307#\275bH\274\232\211\2050\256j\'Z\205\324T\014\242\233\260\036\264\326\213\025\023GP4t\315\264\025\244+L+M\"\243e\250\231i\205i\205i1\212N\224\233\21581\247\002i\371\310\246\220\r0\250\244\331\232p@)\352\276\325 L\212]\224\334{Rn\\\342\236\005L\202\254\242\324\350*\302\n\263\032\325\270\326\256\302\265u\027\212\235\026\247E\253\010*\312\n\262\203\212\231jA\3158\256G4\320*U\251V\247\214U\250\327\035idp\200\346\251\021M\"\241\225\266\203T^\340\346\2432\231\010\024L\010\210\342\260n\306\342k6H\352\006\216\243d\305@\365]\306j\026\024\312z\034\360i\255\035@\321\324L\224\320\264\205i\245i\205j6J\211\226\241aL\"\223\024m\244+F\332p\247\204\310\250\330\020x\244\301\247\001OT\024\360\230\245\342\223u79\2441\344{\323\343\007\241\253(*\304b\254\"\325\210\326\255\306\265n5\253\221-\\AV\021jtZ\235\026\254*\324\353R\255J\264\343\300\246\255J\005L\213\221V\243\000\n{\316\261\257^k*\352\3739\301\253\246\230\307\025\237r\3475E\315\020\374\315\305Y\237\0011X\2271\362k>D\250\031*\274\242\251\3109\250H\246m\3155\242\250\310 \324\313\363\255F\361\324\r\035G\267\024\025\2462\324dTl\265\013\n\211\226\230V\223e.\312M\224\205iU1Noja\244\300\245\340R\206\024\027\250\313\023N^jUZ~\336iv\340\203S\3063V\221j\302-Y\215j\324kW#Z\271\032\342\255\"\325\224Z\235\005N\202\245\025*\232\221j\314c\212k\036iTT\261\256ML\\ \252\322\337\204\350j\224\267\245\272\232\253\270\312}\253\245<TN+:\340u\252R\016\rE\014\233d\253\023J\010\254\371\271\252R/5ZU\252r\257\006\2522\234\324e)\230\307Zi\344Te\t\247\306\2705#G\232\205\343\250\032:\214\246*2\264\302\265\033-D\313Q\025\246\225\246\355\242\220\212JF8\351Q\223\2323HO\245!\367\244\335\216\224\231&\224\n\225EL\2434\374`S\212\344\212\261\032\020\325m\026\247AV#Z\267\022\325\270\305\\\214U\230\305X^\005J\225:T\303\255H\2652\216jq\302\324}MJ\253S)\300\250g\014\340\366\025\223pB\344f\252\002]\272\325\350\227\000WHG\025\024\230\000\326e\311\347\212\244\307\255V\301\335H\354sP\2775\013-W\2213T\344CU\335@\250\231sQ2R\010\271\247\210}i\255\026\332P)\032<\212\201\243\250Y*\026LTl*&\025\013\naZc-0\256)\244SH\244\305!Zn\332n(\013\232k!\246\343\024\n\225EH\005<S\327\222*\310\2178#\265N\203\326\254F\265a\026\254\242\325\230\305Z\217\265Z\216\254\245N\265:\n\262\202\237\212\221j\302\017ZWn\302\221j`jU\246J\245\201\305a\336.\3065\005\270\346\257)\342\272\031$\n*\224\323\0228\2522\261&\252\266i\270\3438\250[$\323J\323\031*\007J\257$UY\340\315@\320\221M\020\372\323\274\254R\371u\033\246j\002\204\032Q\356)\0320j\254\221\324\014\276\265\013-D\313Q\025\250\330S\010\246\021L+M+I\262\215\264\302(\t\232x\216\202\242\243(\r7e=V\245\013\305\033i\350\2705r>EL\253V#OJ\267\032T\352\270\251\343\025j5\253(\270\253\010*\302\n\262\203\212\235E>\236\202\246\007\002\230[\232z\232\2206)\301\352@r+\013Q\220\0075\005\261\310\315\\\007\212\336\237\322\2522\325y\022\230\2109\334)%A\216*\261\213\322\233\345S\035p*\253\014\232c&j\026J\211\343\250\374\272B\224\335\264\326Z\201\326\230\026\215\265\014\261\367\252\256\225\013%B\311Q\262\324,\264\302\264\302\264\302\264\233h\333F\312iNiBb\224\214t\250\230\032m.)B\324\200R\201O\013\212\2263\203W\023\006\254 \003\025e\0179\253\t\311\253Q\245YE\253\n*t\031\353VQj\302\216*@qN\0075\"\323\213qH\274\365\251\001\002\223\314\317JUl\232\260[j\023\355\\\276\2416\351\210\007\275X\265\030QVI\256\226X\362*\233\251\007\245!\213\"\242h\366\324.3Q\354\246\262\325y\026\2520\301\246\221Q2\323\n\361L\333M+M)Q\262\324.\225\036\314Q\266\221\223\"\251\310\2305\003\255B\313P\262\346\242e\246\025\310\250\312`\322\024\246\354\240%.\332M\200R\025\244+M+\232aJM\264\340\264\354R\201O\307\024\016\r[\205\262*\302\236j\324F\256\306*\334hx\315[\215*\302GV\021*T\03056q@\346\245Z~\354R\016z\322\226\240\232E5*rE\027\223yp\266=+\224i\014\267\034\372\326\274<(\251\031\253\257a\232\205\343\250\031j\027\031\250\nS\n\324l*\254\203\255Ve\346\230V\243e\246\021L#\024\230\244+M)Q\262Te)\214\265\031\340\3242\307\273\232\252\311\216\265\013GP\230\352&J\214\2450\255&\312B\224\233x\246\355\305&\332]\224\326\025\031\\\323H\244\305(\024\270\247*\324\230\243fj\304K\267\255Y\215sV\342N\225v1W\"<\n\270\235\252\302\324\302\235N\025\"\324\231\240ry\247\026\002\214\323I\315*\361R\306~a\212\253\253I\266\023\315s\226\243t\244\326\322\034\n\031\253\271+\201P\275Ts\203Q\036i\254\264\302\271\025]\324\203P:\346\2532b\242e\305F\302\243\333C-G\266\223h&\224\255FV\230\313Q2\324\0169\246{\032\202T\364\252\344Tl\242\243d\250\231*\026Z6\323J\321\266\232V\233\212R8\246l\3157m\006<\212h\212\235\345P\"\346\236#\245\331\212U\0375H\300\361\212\265\000\343\232\271\030\253iVc\253q\0360j\322\232\225i\364\340i\341\251\331\244-\212\001\247\nu(\251c\342\262\365w\314x\254\333(\361\315h\203\201Lf\257Aq\305Wq\305A\345n&\230\320\343\221Q\225\246\025\250\035*\006^j\007LT\014\242\242d\315&\312aZ\215\226\233\266\220\212a\024\3223Q8\305@W&\230\313Le\014*\007\212\240d\305D\313Q:\324%h\333HV\200\264\2333L1\322l\243e\'\227HR\224%8&i\342:_.\232\313I\262\236\007\255X\217\002\254F\325j3\232\267\035Z\217\203VS\232\235)\304\320*A\357J[\322\223\2558\nz\323\361\305\000T\313\302\223X\232\223y\222\005\024\220\307\261EHMB\355^\222\303 \324\016\264\213\036\325\317sQ\225\354j\273\256\323Q\232\211\305Us\203P\310\300\212\254\335i\204Rb\230\304S\010\244\333M S\010\024\302\265\033\246EWd\305F\302\243#m5\230b\242!Z\242x\275*\273\307\212\204\2574\233h\333M\305.)\n\342\232\027=\251\3333I\262\223\313\346\202\224\233qO\002\237\266\230R\220\2554\014\032\231*e\340\325\310NqWc\251\322\255F*\302\234\n\\\346\234\242\237\264\321\212p\024\340)\302\236:S\221w\032m\324\302\030\311?\205c\'\357d.js\300\250\231\252\006j\364\362)\247\2574\322sQ8\035\252\274\303\"\253g\326\230\334\325i\227\212\250T\346\230V\230V\232EF\313I\212c\036\324\303HE4\212i\034T,\274\324\016\274\324L\271\025\023-DS\024\231\301\250\244\346\253\224\346\223m!Zi\024\230\247\000\017ZpP)\017\024\302=)\240\034\323\266f\217.\224/\265=V\227m1\226\232#\334x\253P\301\201\310\251\032\014\364\247F\214\235E[\211\263Z\021/\002\254(\305<t\247\n\225EH)qF)@\247\201O\305=\230D\204\232\302\274\2717\022\355\007\212\2225\n\242\2075]\315B\306\275T\216*2)\206\243j\211\207\025M\324\202qQ1\250\233\232\201\222\243e\250\230TD\021M\357JG\025\004\213H\007\255!\244#\212\211\2526\250\335r3P\225\305D\302\242a\305D\303\025\023S\010\244\305\005j2\271\246\225\244\305\030\315\000{\323\200\035\350\300\317\025\"\256i\305@\246\021@\024\255\300\250\031\262j\325\274|d\325\221\307J\221\005X\010\033\265H\220\000sV\221p*e\247\201N\002\244SR\212Z\\S\200\251\000\247\242\3675\233\251\334\355R\252k2\3352w\032\267\234\n\215\332\253\273T,\325\353#\2450\212i\024\302\264\307N*\253\2575ZT\364\250\010\2460\250\234T,*&\025\t\353G4\204f\233\214Rb\2028\250\312\372\324r(=*,qP\270\250\030TL*\031\005E\212M\264\233qA\034SqMaM\305\0053\322\233\267i\245#\212j\360j`h\316iqN\013Mu\250\322\034\265]Q\265p)\300T\350*d\030\251\324\324\252ju5 \247\001N\3058\034S\201\247\2575*\255H\005\023H\"\214\375+\234\271\223\316\227\216\225$ch\247\026\250]\252\006j\205\215z\332\236)M6\232N)\255\315W\221j\254\225Y\226\243aP\275Dx\353Q\265@\303\006\233\232v)\204sJ\005\014\274S\nqQ0\305D\303\212\256\342\241aL)P\262sQ\262S6Rm\244\"\233\212B\264\335\224m\244+\232n)\n\322\212u:\236\2640\317JX\306*QR*\324\350\206\254(\002\244\003=*E\025*\324\313R\001K\216)1\353R \251\2213S\204\307Z\216i\204JI\254K\313\343&@<UhT\236OZ\261\322\243f\250\035\252\026j\211\232\275q\rJ\005.\332\215\226\231\266\241\220u\252R\214\032\200\232\211\352\023P\311Q\324L)\201y\2511\3054\255 \247\036E4\257\034T2-@\313P\262\324,\264\312C\036\352\211\243\250\312\323\n\324dSh\244\315\035i\n\321\262\224GA\217\035\250\331F\332P(\247(\251TT\3121Rg\0254{Oz\235F:sR\nz\232\225\016jQ\322\2274\001\223S \253\000\205\0243\340Vm\365\332\200T\236k\025A\221\363\3335mF\000\241\215B\306\240cQ1\250\311\257\\\214\325\210\316MH\303\002\243\316i\255PH*\234\313UY9\250\331j\006\025\003\365\250\311\301\244\"\223\024\206\222\216\264\230\245\003\212\212E\250\035j\026\025\023\n\211\222\223\030\240`\365\2441\203Q4 \364\250\0319\3057\312\246\262\342\233\2126\322\355\247\010\351\341qHE7m&\332M\264\005\247\205\247\201R\n\220sO\013\351R)+R+\023S)\251R\246\335\201M\315H\206\247CN.;\232\257qp\241\t\006\271\371\244i\245\340\325\230c\332*S\305F\306\240sP\261\250\231\2522\325\353\360\363\305]\2120\0074\222\361\322\242Z\030qP8\252\322\214\325r\265\033-@\351\212\256\351U\331i\240Rb\233E(\024m4\355\230\025\033\255B\313\232\211\222\242d\250\312S\nSq\212i\024\303P\260\346\220-!\2174\323\035\001)\302:v\312B\270\246\342\215\264m\244+@ZP\264\340)\300T\212*QO\247\255H\225:\320O4\242\246Q\305H[\002\253\317\'\312H5\213$\256\354FN*h#\000s\326\254\343\003\212\215\315@\355U\335\352\027\222\241/\232aj\366H\276Y1ZJ\006*)y\250\361KP\270\252\356\271\250\031j2*\'L\324\016\225\003G\232\214\307Ld\244X\211\243\312\346\234#\247yt\245*\'N\265\\\2550\250\2462T,\225\031ZaJiJc%D\311H\026\224\241\246\355\366\245\tK\262\223\030\2460\3153m\006\234\007\024\005\311\245+F(\305(\024\361N\025 \247\255J\275jaJ94\354\201N\rMy1\221U\244\311\3435\003\332\356\031\217\357TA\214M\265\3705:\311\221H\304T.F9\250X\002\274TE\021\201\301\250\377\000v\271\365\246*&I\'5\354\261\304K\364\253d\020\265\016\t4\270\244\"\243~\225]\373\324,)\204sLe\250\035j\"\224yY\246\230E\'\226\000\243\313\024\276]\036]F\313\351Q2u\315@\311L\331M)Q2TL\224\302\224\323\0354\2450\305\232o\225K\345\323Lt\335\224\036\005BFi1\212i\031\246\260\245\035)\300`PM&(\240u\247S\305H\265\"\324\240T\203\245&H4\240\344\323\267b\240\335\311\240\234\322+\355<Uk\245\017\363\n\254\222\02585`>i\216\001\006\240u5\001R3Q\0255\031\316+\337\002*\364\246Hs\322\242\034Rri1Q\275Uzn3Me\246\021\232\214\246i\246*\004t\326J\217fivb\233\232v3\322\243(ED\310MD\361b\230c\366\2464u\023%FR\231\345\321\345\346\232c\246\024\305\n\240\236i\216\203\265DEFE0\256i\245qM+\353Q\2650\323\227\2458\n1HE6\212p\247\212\221jE\251\226\226\202h\034\323&|\016*\020\334R\202i\245\251\205\263PN\230]\303\2556)2*\\\344TmP\265B\302\230\302\275\325\230\223M\'\327\2553u&i\t\357P\271\315Wzh\353JFi\204S\225)JRy^\324\306\212\231\345b\230c4\301nX\346\245\021\000\005\006,\324f\034v\250Z,\236\225\033G\212\211\222\242x\352\023\035/\226)\245)\214\270\250\034c\245F\006)\255Q\225\3154\256)\207\330S\010\250\332\242aM\"\225y\247\n\\R\021Q\232QJ:\323\305J\274\324\202\236\016)wQ\234\323\272\n\212R\270\371\215D\030R\027\250\313f\231\234\032\224\'\232\244\032\240\352`\223\025:\234\214\3225D\303\232\211\252&\036\225\356\254\270\250\3150\232@i\030\346\2425\023\nh\034\323\261I\212\231Tb\236\020\032R\243\025\013\255Bh\t\232]\270\024\320\271\346\235\266\215\240\324m\030\025\033C\270dUW\214\203\322\242)\236\325\031\216\233\266\243a\315E%@\313Q\225\246\354\246\025\364\2462\201Q\265D\334\324dS\010\246\021M\007\006\244\352(\315\004\323\0174\200S\200\245\251\024\342\245\rN\315&\352p4\342\340\n\241p\344\26501\247\346\223v)\240\345\252h\337k\324\027\250X\356\250\341l\255Hj&\250\230TF\275\325\352\006\250\330\322f\2239\246\236\365\031\244\305;\024b\236)\340\322\265F\336\365\036\334\322\343\003\212M\271\247\005\243m\001)\031j\"\010\250]7v\2506\020\324\311#\347\212\205\223h5Y\205FV\243+M)L`1\357P\267\035*\027\250\2150\212i\\Tl2j2\264\303\301\245V\305;9\351Hi(\006\2274\264\345\247\212v\352\\\322\346\243w\305T\221\262\324\253N\3155\215,JpX\323\315%\313~\344\343\232\243l\374\220j\331\351Q\265D\325\021\025\356%\270\250\332\2424\224\235(\3051\205 \024\360(\333K\266\234\0058\014\324n\231\245\013\305\0333K\263\002\215\264m\315;g\024\306\002\242+Q\262\324,*6\030\252\262\014\324\016\270\250\312S\010\002\243`*\027\305@\325\013\323\n\036\246\223\201Q\261\346\2439\246\032\214\212f9\247)\245\315!\244\024\242\224\361NZ\226\212L\320\0335\023\344\344\325|d\323\205:\232\3252\377\000\253\000Si]wDEe\257\311.*\3709ZcTmQ5{fsHi\270\246\221\212JP)\n\322m\247\001N\002\214S\202\323\266\342\223ni\3018\244\333\212B(\3058-\0140*\007\025\031\366\250\230\361Q1\250\217\275B\342\253\260\346\243aQ7\025\003\232\205\215Dj&\034\324m\222i\245x\246b\230E1\205FE4\255&1A\244\315\002\234\264\032U\342\245\007\212\t\246\232\024\363N\342\253\310\230<S@\247\001H\302\244\204\344m4\2450i[\345^k\036f\377\000H8\365\253\310~QCTL*&\025\355C\255:\214SXS@\247\205\245\331K\262\223m.\3326\323\302\320iB\323\261HE4\255\001i\341x\246\270\252\356*\006\030\250\232\242aQ\270\342\240l\324D\376\025\023\232\201\315@\365\023\n\214\217Jcq\365\250\210\3151\251\270\342\232\325\013\032a4\334\321M<Sz\236(\247\n\\\361H\r<\032\\\321J:\322\323Xn\250\312\021H)q\3054\214\034\212x\224\367\243%\301\315d\314\000\270\343\326\264#\037(\241\205D\325\033W\264\201N\305(\024\025\315\001)\352\224\375\264\273i6sMd\247\204\244aH\251K\214R\342\220\255&(\003\232\220\014\212\215\305WaQ0\315W\224\343\201U\036LTfBi\205\261\326\243b\010\340\324\'\223P\311\317J\200\323\010\3151\205D\302\230ED\302\232x\250\336\241j\214\322\037j94\323B\014\320\303\024\320i\335\251)\300\323\201\247\nu)\034Rc\024\244pj\035\264\264\021M#\024\341\367X\326C\374\323\3765\245\030\371E\rQ5D\302\275\245\016E<\014\322\201N\0034\360\264\365\030\024\354f\200\264b\215\242\215\264\2052i\312\264\215\035.\312M\224\233(\330\007Zq\034qP\270\250\030T\022p*\204\362c\201T]\2114)\315+.F* \204\032G\025]\315@y8\244n\005Bi\246\243aQ\265D\306\241sP\261\250\311\246\203\223Rf\232}\350\r\203C\220E0S\373Sh\025\"\212x\247\001N\002\227\031\243\030\250X\037JA\326\235\212i\024\262|\2201\365\254t\033\246\255$\351CTL*&\257dS\201S!\315J\0059E<\nv3N\002\227\024\241h\333H\306\221Fj@\264\355\264yt\205qH\026\215\264\204Uw\025\021\\u\252s\347\234Vl\334\036j\253\002\335(D \324\342>3Lu\364\252\262\034\032\204\340\217z\201\206\323Mc\221P\2650\232c\034T\016\325\0135B\315Q1\250\3157q\355N\317\255\004\323sN&\201KE\003\255J)\302\244\002\224S\300\245\307\265\0057\014Ur\273X\212\\R\005\311\250\357\316\310p+2\325r\371\255 8\246\260\250\232\241j\366E\351O^\rZ^E<\n~)@\247\001N\240\361I\270\na\303\037JT\0252\212\220\npZ\ng\265FS\024\230\2467\025\013\016\365\003\325g@A&\251=\276\366\366\250\336\334/j\213\313\013A \n\205\330b\263\356\034\202qU\274\323Am\335j6\250X\323\013TL\365\0035D\306\242cQ1\246\026\246\347\232]\324\205\361M\337OBZ\244\351M\007\232x\024\240sR\250\315;\030\247\212p\024\360)\330\245\002\253\310\270z1J\253\315S\324\217\312\005U\264\\\n\275\216)\214*&\025\013\n\366E\034S\300\253\021\2361S\250\251\002\322\355\245\305\007\000f\243-H9\316i1R\240\251\007\024\340j\302-+\n\214\212\215\252&\300\250\034\325g\3115\003\324y\002\241\221\252\254\215P\263T\016\325J~k>F\330jDp@\2476*\007aP\267\265Ws\212\205\232\242g\250\232J\210\276i\205\251\241\251\333\2513M&\237\033T\244\346\205\034\325\2208\243mH\243\212v)TT\200S\300\245\305(\246H\2319\246\355\247\204\302\223X\367\255\276B\007J\222\336<(\253\030\250\336\241j\205\253\331\224qN^\265\"\037\232\256 \310\251\000\245\305(\025\034\265\0363O\2152y\247\024\000\346\234\007\034R\323\324U\244\340R\265B\325\003\236i\2147\003UO\007\223Q;dUv\340UWl\032\205\3375Y\316MD\306\240sUf=k.v\353Kn\341\207\275H\357\212\201\244\006\242g\250Y\263Le\315@\353\212\254\324\302p*2\324\335\324\340\324\271\246\026\247\304jqR\240\346\247\307Jx\024\360\264\355\271\247\205\245\003\024\361N\305.)6\344s@L\221Q]\270\2110:\326:\251\222L\232\272\213\201Jj&\250Z\241j\366\304L\212f9\251PU\250\316\005;94\345\346\236\005G\'&\221W\234T\244`qM\344\322\343\002\224\n\224\n\231\017\024\023Q9\250\017\255\034\021UYpOz\202CU\344\351Tf<\361P1\250\034\324D\343\255F\304\032\2550\3105\225t\207\223T\321\312\036*W\230\260\346\241\337\223C\270\307Z\256\317\317\024\3174\212C.j\0269\246\021\232\211\226\242<P\032\227u%I\030\346\254\245Y\215sVB\212P1N\024\341N\247\001O\002\235\2121J\0274\343\210\324\223X\367sy\256@\242\030\260*|b\230\325\013T-P\265{x_\226\205\2175 \\S\306E(52\260\002\227w\2457\2559\0079\247\023\305 \024u4\341\326\245\002\236:R\023P\261\315D\306\243g\307\322\242f\316qUdj\253#\325Y\rWcP9\305W\221\352\271\222\243w8\252S8#\004U1\215\324\222\201\216*\253\270CP4\271\351Q\2310i\215&i\236`\246\2313H_\024\201\367\036h\221x\315@z\323\205(\025i\024\005\367\253\021DO5r8\3609\025&1J\0058\014\323\200\245\002\244\024\352SH9\251\020`\363U/\246\302\355\006\263\242\217sd\325\300\270\024\326\250\332\241j\205\252&\257s\000m\241F\r<\n\\R\342\212QR\216\224\341\305\035M\006\222\236\242\244\024\244\340TL\325\016\356i\214j23PH\330\315T\221\352\254\215P9\250d8\252r\311T\244z\256\322s\305!\220\221\315V\227-U\330\005>\365\013\270\317&\252\315\317CU\217\006\242rsQ\226\3050\265\001\270\246;\322)=ju\223\214\032M\231\351J\027\024\340\274\325\210\306p+B \025j]\330\245\0074\372x\036\224\360\236\264\273pi@\245\244\357\305H\243\271\246\314\373W5\225!2\275O\034{E<\361Q\265D\306\241j\205\252&\257vQ\223O\013\315(\024\340\264\273h\333F\332p\024u\247 \240\365\240S\324S\307\002\230\315P;\324\005\271\241\337\025\031\224b\252\310\371\252\222>*\263\275WiqP\311(#\025FBI8\252\333Y\316)\031\002\216j=\303\322\242\221\205P\231\262p*\264\2101\235\334\325f8\353P\273T,\325\003\311Q\031i<\332\024\356<\324\243\212w=\251\351&\323\203S\207SN,;T\22075y\rH\005J\202\245U\311\346\247\003h\340Uw\224\253\363OY\225\273\323\203\002i\304\201M\363\000\245\r\232\212y21U\343\217\234\232\233\2651\215D\306\242cQ5B\325\023\032\367\244\\S\300\247\005\247\001K\212\\{Q\212gCG&\2348\315%8S\305)<T\016\325]\332\242\335\315A4\265\030|\212kt\252S63T&\227\025L\313\270\343\232^\325\023\020*\274\222\355\351\305Uy3\336\243/\357U\246\233\035\rg\315+)\252\257p\335\352\006\231\215F\\\232\211\336\240\221\352\003%&\363\232\225\036\246\017N\363\005*\266ML\246\245U\315X\210m5q*\302\363S%M\030\313T\300\214\32471\003\326\241\020\205\357FpqR/4\2059\241\334(\250F\\\344\323\372R\023Q\261\250\230\324Lj65\013\032\211\215{\341\247\255I\326\224\nu\024S\010\364\243\030\244\242\234)\340\342\243v\252\3625Wf\250\367`\325y~f\300\245\013\264TR=Q\235\305f\316s\234Ud\030j{6\005V\226N\0175FY@\357U^\340\n\256\367G\267\025ZI\262\0175I\3459\344\346\240\222L\324-!\002\231\347v\246<\234\324\022=W\337\315.\372\2266\315I\273\024\34595f4\316*`1S%L\255V\342l\212\267\030\251\2623R\306y\025ch\340\367\250\2475\016Gz\000\035i\305\325\005@\323\0268Z6\347\2559@\024\214j2\324\3065\023\032\211\215F\315Q1\250\230\327\320\000d\323\302\323\261N\024\242\227\024\224\323I\236)(\245\024\244\340T\016\365Y\336\241f\2463\0002i\261\214\363D\216\000\252\023\313\214\325\007\223uU\221\211\310\2507\001\326\241\232a\214\n\245$\243\234\232\314\232}\316pj\007\220\001\315U\226\340\016\365N[\237z\254\3679\357P\264\376\365\023OL\363rhy\006*\006\2235\021|\032O2\245\212Nj\312\275M\031\031\311\253j\343\034S\303T\252\365*\022\306\264aP\024f\254\007\300\342\234\246\254Fy\025h\0163\351U&\223\3465Y\244\'\245 v\247\034\221\315:5\301\251\030\342\231\272\232\315Q\026\250\331\252&j\215\232\243f\250\330\324lk\350UZ\220\014S\261N\013F\3321HE4\212\214\360h4\235)sLv\252\316\365]\332\242\3150\234\237j\014\201G\025Zy\2532yI\357U\267\032\206I\007j\252\357\305R\232oJ\245+\2229\252\022\270^\365Fk\214w\2522\334\023\336\252<\331\357Q\0313\336\230\362c\275E\277\336\2173\025\033L}i\206Zi\2234\201\351\351&\rYI\211\253Q\271\"\247V5\"\311S\243\325\313c\226\346\264Q\270\247\006\346\245SV\021\352g\230\210\316*\233\266T\223L\006\236)I\245V\346\225\332\230[\212\215\232\243-Q\263Tl\325\031j\215\232\243&\243&\276\214Q\305<{\323\300\247Rb\220\323I\246\226\250\330\212nsE5\233\025\004\217U\235\352\006z\215\237\002\253\313)\003\212\254\327\030\030&\253\3119#\255Ty\200\252\317?\275Wyrj\255\304\373W\255Pi\300\0075F\342\357\260\254\331\256I\357T%\236\252I-Wi)\003\323\035\351\201\263J[\212\201\232\230Z\233\270\323\303f\244S\315Z\216\255FqSn\251\020\346\254\306j\334\'\006\257\243qO\rR\253\324\310\365&\340\303\031\252\316\330R)\241\251\341\250\337J\036\202\364\322\374Tl\325\031jc5F\315Q\226\246\026\250\313S\t\257\243\326\236)\340\323\251\245\2050\2650\275F^\232Z\231\277\024\031*&\222\240w\252\356\365\003\275A$\234UYd\343\255Uy=\352\264\217U\244z\253$\225\001\223\255P\272\233<\n\241#\026\035j\224\307\025Bf\252R5Vv\250\031\351\276e4\276h\337\212i\222\230\315Q\026\2405=ML\255\212\261\033\325\204z\224=L\215Vcj\265\024\234\325\324\223\212\224=H\257\232\225_\024\342\374\361QJ\334\373SCS\203\321\276\224=\033\351\245\3522\364\302\324\302\325\031jajajaji5\364\222\323\3058PZ\243-Q\263Tl\365\031zizc5Fd\250\332J\205\344\250\035\352\274\222Ug\222\252\311-Uy*\254\222\325i%\367\252\317&j\031$\n+:\342A\234\3257\237\216*\234\262\016rk>iG5M\345\252\362IU\313\022h\372\232ajM\324\233\251\214\365\031z@\365*=N\255\221S#b\246V\251C\324\321\275[\216LU\204oJ\265\034\270\340\325\205|\364\251\221\252`\324\245\275)\254r\rB\037\236i\333\3517\322\357\243}4\2750\2750\2750\2750\2654\2650\2654\232Bk\351Q\322\234\r\005\261L-Q\263\324L\365\023=0\2754\275F\317Q3\324FJ\205\244\250\036J\255$\225VI*\254\222UY%\252\222IU\236Z\201\3445VY}\3536\346\343\255gIpMWyI\252\322d\365\252\316\r@\355P\263Te\375i\246Ja\222\220\313M2S\013P\rJ\206\254#T\352\325\"\265H\032\246\215\215[\210\346\255#b\247V\315X\205\271\253j\325 z\013\322o\252\356\330\177\255.\372B\364o\243}!zizazizajijijM\324n\257\245\305\005\261L/Q\263\324L\365\033=F\317Q\263\324fJ\211\244\250ZJ\205\244\347\255D\362T\017-V\222Z\251,\265U\345\252\262KUd\222\253\274\200\014\223Ud\234\023\305S\232oz\316\232\\\346\2523\n\211\210\250\235\352\254\222Uv|\324\016\325\0135FZ\230\315Q\226\305&\352P\325*\232\225jT\253\010jU\251\026\247\216\256F8\251U\252d&\254\306pj\312\275?}\033\351\013\324r\034\256Gj`~)\013\321\276\215\364\322\364\322\364\205\351\273\251\013SKSwQ\272\215\325\364\301jc=F\317Q\263\324L\365\033=B\322Tl\365\031z\211\236\241i*\273\311P\264\265]\345\252\322IU]\362j\274\257\201\326\251\264\234\362j\264\327\n\265\237,\345\317Z\200\275T\232J\243,\225X\311\232cIP\274\225RW\252\306L\032k>j&j\2179\240\324mM\247\255L\242\245Z\225\005N\242\245Z\225jd\253(ML\265e\rJ\255S+\361N\337N\r\232B\324\335\334\021P\206\244-I\276\202\364\322\364\205\351\273\350\335I\272\232Z\223u\031\245\335_K\026\250\231\3522\364\306z\211\236\241g\250\231\352&\222\243/\232\215\232\253\311&*\264\222\372T,\304\324.\330\352j\264\222UY$\252S\314{U\t\'#\275S\222]\307\255B[\025\023\311T\245\222\251H\3715\0136*\026z\201\344\252\362>j\271=i\205\251\245\251\231\2434\207\232LS\324\032\231EN\253S\"\342\246Q\232\225V\245U\251PU\2055*\361R\251\251CT\240\361K\272\234\255C5F[\025\036\356i\013SKSKSKRn\244\335K\272\215\324\233\2517Q\272\224\032\372I\244\250Y\351\205\3526z\215\236\241g\250Y\3522\371\244\006\230\355\201T\245~\265X\266M#\266\005S\226J\254\362U9&\252\222\310\rg\316\370\'\025I\236\230d\250\235\370\252R\234\232\256\347\025]\332\253\273T\016\325\0136j75\0214\204\322P\005.)\300T\252\265*\255L\213S\250\251UjU\024\354T\211S\251\251\224\324\253R(\346\245\307JN\224\3654\255P\267z\2046sHZ\232Z\230Z\232Z\220\265&\352]\324\273\2517Q\272\215\324\273\253\350\306\222\243/L/Q\264\225\013IQ\0311Q4\231\250\313\322\254\237-C$\231=j\274\244\036I\250I\013\316j\264\323g\201T\235\352\254\322\340U\007\233\223U\244\2275VV\3105U\372T\r\221P\263T\016\325ZCU\334\325w5Y\330\212\2179\2460\3154\203M\305\030\240\n\220-=TT\252\242\245U\251\225jUZ\224\nx\025 \025\"\214T\200T\212je9\251\343\\\323\363\203M&\234\247\326\206jc\237\2275S8&\202i\244\323\030\323\t\246\356\243u.\3527Q\272\215\324n\240\267j\372)\236\242g\250\332J\211\244\250ZJ\215\244\250\332J\214\2754\311\201PI)\035*\264\223\037Z\257$\307\326\253\264\231\315@\317U&~\rf\313\'5Y\346\250\232L\324L\325\023\236*\253\266*\027j\256\346\2539\252\356j\026\250\310\240\323M7\024\240S\202\324\212\265 ZxZ\221EL\202\245\013R\001R\240\251\002\324\212\264\360\264\340*D\253qc\024\215L\3174f\232\317H\355\362UV<\320M4\232a4\302i\231\2434f\227u\033\250\335F\352@\325\364+IQ\264\225\023IP\264\225\013IQ\264\225\031\222\232e\250\314\231\250\244z\251$\225Y\244\311\346\241y*\273\313U.%\342\263e\222\2533Te\361M\3633M/U\344\3475Y\332\241v\252\362\032\256\325\013u\246\363IF)6\323\302\323\302\324\201i\341j@\265 Zz\214T\312je\\\364\251Uj@)\340b\236)\330\245Z\261\031\342\203M4\225\024\234S\031\262\225\001jM\324\231\246\223Q\261\246\223M\315\031\243u\033\250\3154\265(j\372\t\336\241i*\026\222\241i*\026\222\2432S\014\231\246\263SK\340T\022IU\036Nj\273\311\212\255$\265U\346\367\252\223M\232\252\3075\013SJdu\250_\345\342\230Z\243v\342\252\310j\026\250Z\240j\214\2126\322m\245\013F\312P\265\"\255H\253R\005\251\024S\361J\005H\253S\3061S\201R\001K\212z\2558-(\034\324\212\010\247S\t\246\026\244?0\250\266\225\353\322\241\221q\310\250\263\212\t\244&\243c\232i4\322i3Fh\335Mg\244\006\234\r{\353\311U\236J\205\244\250ZJ\211\244\250\232Za\232\217<t\250\344\233\336\252I8\035\352\253\334\201P4\331\025ZIj\2735B\347\212\200\232\214\232ij\206S\232\203&\241\221\252\271l\324lj&5\031\024l\365\245\331M+@\024\354R\205\251\002\323\302\201O\013R\005\247\205\247\005\251\225*U\025*\212\220\npZp\030\247\212r\217Zp\024\204`\323\030TMQ\346\235\234\216i\204\216\207\245W\2210x\250\263HM4\232a\246\223HN)\245\251\013SsK\234S\201\257vy*\273\311U\336J\205\244\250\332J\205\244\250\232\\T^v\016MC-\317\275Sy\211\250\231\352?7\007\025\024\217P4\224\302\331\250\211\2461\246\026\250\\\324]MG\"\212\200\212\214\255D\302\230E\030\245\240\212LR\342\224\nz\323\305=EH\005H\005<\n\225je\247\250\251qJ)\300S\200\251\002\344\322\355\305\014*6\024\302\265\023G\3157n)\244S\010\310\301\252\262\251\006\230\016i\r0\323I\246\023L-M\315.isFk\334\032J\256\362Uw\222\241i*\026\222\242ij\027\226\253<\276\365\003I\357Q3\324O.\005W2\363N\336\n\324\016y\246\027\3057~i\214\325\0314\306\351Q7\265D\315Q\261\250\311\24674\314R\355\243\030\240\322\036\264b\236\0058.*e\214\021\301\247l\3059EH8\247T\213R-J\246\245^i\340R\212x\251T\323\210\310\244\333M+L\"\243ja\250\315F\302\242q\221UXm4\302i\244\324l\330\250\213f\233K\2323FiA\257gy*\007\222\240y*\006z\211\244\250\036J\201\344\250\032J\201\344\250^Z\201\344\250\014\234\322\211\251\305\363Q\26574\023Q\223L\3150\324,9\246\021\212\214\323\0174\336\224\271\244\315\035iqF\332z\247\275L\027\326\2342:T\241r9\243m8\nZx\251\026\244\025*\032\230\036)qN\035j@02ju\301^(8\250\310\024\306\025\003\212c\n\214\323\032\2435Ve\346\253\223\212\215\237\322\2429&\222\2234\231\245\315.h\025\353\355%@\357P<\225\013IP\264\225\013\311U\336J\201\344\252\357%B\317Q3\324e\251\233\271\247\2074\355\371\024\231\346\220\2650\232a<\322\036\224\316\325\013\232\214\232ni\t\246\021F)\312)I Rn\346\236\257R+\212\220\032z\276)\371\356)\300\323\251E<qO\006\244V\251\320\346\245\307\024\243\255K!\004\014T\221\034\nV\250\233\212\215\215Fy\2465D\324\303Q\266{Uv;\270=j\264\203\025\001\246\023\212i4\334\322f\212\\\323\201\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\nIDATx^\355\334\331\222\343*\014\000\320\251\374\377/_\327\255^&\023\307\361\0166H\347<ug\263A\002\204\235\356?\177\026=\336\037\000\310\3439\005\016\257\217\002?\014\214\340z\t\260j\265\222^\022\200Z\376\016\255.\206\330\\\272NO~\372\010|\231\313!\200l\254\224@\010&\263\334\304\037v\351u?|\346\274M\023\000\000O)J\2433\265#\001H\200W\237\306\374\260\322E\237\336C\034\263\361\235}\242\tki\013\034\021w`\265=\2435#n\002$#\337\001\350\325\236b\344\343k-\202\313\336\373\347c\'\002\000\000\320\250\367]\035\000deM\204-\334\003\200\027\351\006D\272\006\357\224\246\226(\222\010\355\366\326\243\335S\273\330\277\216\360\325z\010\312\330f\003i\002!\030\312\225\005\336B\310\035r\3330\0026\274\204\261X]v[kn;0\000\000\000\255^\r\264U\00463a@h\215\226*\244e\321\201>\030\253\237<\327\324x\335s\331\337\005\014\001;\017\000\310\300\325\025\000\2567\267\203\236{\374\220\242\037\226\200\222\000\340\020\313Mr\022\000\240\030[\022\216YX\214\027\236\242\031C\307\203\277\3373\377\344\266\377Bz\327q\271\314\362\\,\001\000\000\202[.\007\211gs\304\177_\270\371\365\304\"\360\331u\232\001\235\2366\274\221\311\300-\032\276\027\320\360\251\305\262m\001\332\366*\256P\374\336q\351\317\003\000\000\240_\007.\000\034x\013\037T\337\236\377=@\365\003\001\000\274R-\002\375\010\271]*9\r\207\354 \000\000\000\366\262=\034i\240;~O\341\3305\200c\357\2421g\322\360\314{i\310\321\261,\001\202\220\000\000\300\026u\326\376\311_;\037-M\306\312|JD\337=3\354\350\237\367\000\365\244\347s\207jv\214\177\000 \026e@^\027\307\376\342\303\261b\020\221\344\\\034\001\312+=\263X\251j\321\263\000\364\241tm\261\327E+\346E\207\341V\367E\371\276#\323\004\t\200$\310N\002\000\020\304\335\027\010\370L\251\321$\303\005\000\000 \205\347\366\317>\020\000\000\346\204\272\231\025\2521\020\302\337QYit>\367\373\225>\237Rf\003t\356\222\315\373\273g\017CtB\017\274/\t\2045\236\362-\000@\313n\232\243n:\354uj-\372\265>\227NH\200N\010\024@\026f|\000\322\010\177%\347\234\331\356Q,\360\211\274\210hv\032\000 \021\253A^\207b?*\n\037J\304\276\274\306k\030~~_K\203b1^;PGv5e\327\213+(\026@\000 55\005p\237\331\031h\366\211\266\335v\332+\007^yz\352\356\355n5a\033\006\020\307\356E\013\000\350\2225\377\220\323\273\332\323\037\320\233(\211V*pM\365\307\025\'s\3051\340v\022=9\t\000\220\315\317\314\177f\376/\265\275\340\026gBO \3061@\034\026w\000:\365\334\226\254\355O\326\326\272\265\347I\351\353\337#\214eK\224I\007\000\220E\266%o\302\032\010o\014\212\344$@.\3432 }QpP\244Q\023\251-i\t\"\000\000k\324\214\'\331=\323\244\303#\373\360\033\001\240\013V:\000R\033\206\351\327\343\263K\333!i\033\236\335k\340%\001\000\000\267R\220f%\362\367\320\357\000\000@L\253\273\035_\364\017\332\003\2555k5\023/\326\332\371p1\t\000\000@o\036\207vy\t+\337\355M>\324\243tO\334\001j3\323\002\r\331\276= $\t@\033\232\250\216\374\337\204x\232H\254\375d\"\000\000\334\257\323\355\004u\004\332\246\311l\370\307x \227@\213\031@%j\203\276X\3316x\351\244x\371]\240E\005>\242q\001[\370lR\300\266\0010\343\247\242Q\374%7\310\201\254*\307]Q\331\262\237\340\307\215Q\334\226\225Ub\0228\362\247\001}\306\247\317\263\256\356H\002\320\200\207\214\246\016\211\005\345Xbc\020\307\344ZK\200\342\347\023|\345\037\312\367X\020o\201\017\330M\337\033\335\200\355\002\200*\202\327\204\300\275\224\345e\350\307\234\252.\321U?\2746#\"\210b\201,\366A\271\350\266\250D\026\000xuam\320\3656\023\000\000B\272pC\000e5\277\305l}t5}~\315G\267\007Y:\261\351L\006\000`\037\305\335\001Y*\177\026\254\3747\260\346s\344\367\364\233?\317\336|\367\353\320x\307\216On\364\333$\255\233n\0104\313\310IN\002\320\271I9@O\204\357rmM\372\022\340IW\314\3201k\226.\366,<\365k\375\025\177.\2306>\035\340\323c_\346\036\347\3338\240\233\302\013\264i\347\000\336\371r\000\200\306\274U3\212\233#\036\017;f\000\216\262\366\002@\233\336\327\350\367\337\273\366\322\230\274\373\331\363-\017\225\023@\n\347g>\"\350~\375\352\276\0017\323\177\000O\243)Q\241\224\211h\247\246\030b\201\364\000\200\313(\312\001\340(\233W\200:\314\257\000\204aQ\333a\347u\312\235/\007\000\332c9\007\2022\275M\244\332\034\213\377~\361\372,^\213\000\000\000\226\330\005\001\000\344\223\352\346\017\000@V\212>\026I\020\000\210\305\332\016\231Mf\200\305/\202,>\t\000\000\000\320\024W2\000\200\350&\367y\200\210\346\206\372\353\343\377\275\374Lvs\031C\022\022\000\000\000 \231\177\267\305\335 \007\000\000\200\256\270\273[N\207}\331\341)\003\000\000\264\302\226*\013\221\006\0023\305\001\300Yi\2777=\214\352\210\264\335\220\272\351\000\000\000t\306=\201\354\356\276\212!\003\201UwOT\000\000+\354k\000\016\262\337\343\213<\000\000\272\246\230\001\000\200\350T\375\200/\005\364\345w\336n1j\377\003\020\243\377\206\213\235b\021\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
new file mode 100644
index 0000000..83f1fcb
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -0,0 +1,3 @@
+tile_key: "7"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364\241\311\247\272\014UR\270j\221E;\024\240R\342\227\024\355\271\244\3074\341\326\234)\340S\302\324\2121O\025\"\324\202\236\265*\324\213R\016\265 \247\221\300\247!\035;\324\312)\342\235\232p4\352\rFx\346\254B\341\206\r<\257j\257  \321\022\0265<\361\205N:\325#\2321IM84\204\n\215\224TD\n\211\361U\334Uw\025\003\212\201\305VqU\334T-P\270\252\322\016*\234\253Ud\025Y\305FV\242\"\241a\232\214\245D\351Q\024\250\235k\327\2323\236)\256\016\334\032\213m8\np\024\340)@\245\3058\nP\264\273i\341i\301i\340S\200\247\201O\024\360*E\251\026\244Z\221jE\251\224\016\365\030 \310qV\024\323\301\247\003N\024\340ii\254)\250v\265[S\221\232c.\346\346\254\300\2358\242\362<(\"\263\366\322m\244+LaL5\033TdT,*\027\250\\Ug\250\030Uy\005We\250XT\016*\264\202\252\312*\234\202\240qP\221Q\262\324L\265\031\024\306\025\013\n\211\205{\010\036\265\024\200T;y\245\013N\333K\266\234\005.)\300S\200\247\005\247\001N\002\224\np\024\360)\300S\305<T\202\236)\336b\257SR\t\227\037/&\201\276C\311\300\251\321v\212\224\032p4\341N\024\341N\024\244f\243#\006\254Dr\274\320\300\203V\255\337\212}\340-\030=\2534\214RQ\212c-F\302\243+Q\270\305@\365]\352\026\250XT\014*\007\025\013-@\353U\344\030\315U\220U9\005VqU\330Tei\2053Q\262\032\210\245D\313\216\265\013\212\205\305z\363q\322\240y0phV\rR\252\322\342\212)\300R\343\322\224\nx\247\001N\305-(\247\np\247\np\247f\202r1\232b\214I\216\306\257\244`\n\225i\340\323\201\247\003O\006\234\r8\032x\247\001HW4\014\257J\221X\265O\037\006\255I\363\304@\254\366\217\232aZLSH\250\312\324o\305Wz\201\352\273\212\210\212c\n\205\326\241+Q2Uy\027\025VAU$\030\315R\222\253=@\302\230E5\216:Tg\232\215\270\252\356j\007\250\034W\255<\200\n\243<\302\2515\367\224\371\315\\\203RW\003\232\224\335\016\306\234\267 \367\245\373H\315J\223\206\251D\202\234\034z\324\313\315;\024\264\003N\247R\212p\245\315;\265F\354W\232\204\334\342A\3175\247\004\333\30758\251\001\245\024\361O\035i\302\236;S\3058S\351\030qO\203\275J\006\rJ\200\223\365\250\245]\246\2414\322)\244Tn*\026\250\034T,*\007\025\t\025\033T-Q3TLj\0075RSTe9\252\216*\273\255DV\232\313Q2Tl0*\273\232\200\324l\265\023\255z4\267\003\007\232\313\270\270\311<\325\tK9\342\230\211*\037\224\232\260\262\314\007SR\255\304\242\247K\306\376!Z6wq\271\001\216+ac\211\324\020\334\3242\304\253\321\251\022M\275\352q>;\346\246W\rN\306zR\342\224\np\247\nQN\002\231*dV;\356[\234\023[V\317\200*\3720\"\244\007\024\361O\024\361\3058\032p4\360i\342\236)q\272\236\213\264\324\246\245\214\322N\205\206@\252\244\021I\214\323J\323\031j\026J\201\326\241e\250\035j\026Z\205\226\241u\252\3561P\265W\220\3259Nj\254\225Y\226\242e\250\310\250\330TNqU\2449\250\030TdS\010\250\334q]K\\\356\025\\\202\346\246H\272T\313\030\251\004c\322\235\345\n_!Oj>\317\267\225\342\246I$A\200\306\247Id\'\3469\0251\215\230d\032\201\244\222>\271\305Y\266\273$\326\224r\206\02584\340i\302\236\0058\nv*)\030(\254\231\030I?\313ZP\214\001V\343r\rZV\315H\0168\247\003N\006\234\r8\032z\232\224\032vjd\0250ZF\351\212lNw\n\270pW\212\253\"\342\241\351IM8\250\236\241l\032\211\222\241d\250\035qP\262\324n\234U9V\2538\252\262UI\005Wu\250Yj\026Z\215\226\253\310j\273\344\324%j6\025\031\024\322*\027\256\221b\247\252\000jA\307Jp85 l\323\301\247n\247\251\315<b\246A\307\322\247\004\355\310\374i\031C\212\245*\030\216S\212\236\326\364\202\003\032\325\216`\303\203S\t*EqR\253\n\220\032R@\352qY\367\327\001\020\205\357Tl\327s\3565\265\020\030\251\024s\305YQ\3005 j~iA\247\003N\006\244SO\006\236\234\232\270\215R\001L~\225[y\215\262zU\350\246WN\265\034\247\232\200\3223\201Q\026\315B\3715\001\342\234\016W\232cTL\265\021Z\205\305T\225j\243\255U\221j\254\213U\331j\026Z\205\226\241qU\235j\026Z\205\226\243aQ\221Q\267\002\253\270\256\246\202h\335F\352p|S\303\323\267\323\325\352Uz\231_<T\253&:R\207\301\241\300qT\336\022\255\225\251\340\270)\303U\305\272\351\315J\267$\036\265f;\241\336\246\373j\201\305V\236\367\336\263\036G\270\177j\321\266\214\240\025\240\215\212\231\033\326\254#v5&y\247\003J\r<\032p4\360i\340\324\261\034\346\255G\324U\2208\246\260\252w*q\305$\014Ur;U\304o1zTrD\303\'\025M\362\247\232\001\310\250\334\324M\355L$\212\214\271\357HZ\243cP\265W\220f\253\272\325Y\022\252\310\225Y\322\253\270\250\030TL\265\003\256*\006Z\205\205D\313L\331P\313\201U\034\327M\272\220\265&\3527\321\276\234\037\232xcN\017R+\324\311%J$\251\267d\0029\247+z\323\260\032\232a\006\221\240*F\r&]i\004\222zT\352]\307Jp\267.~cVa\267\013V\200\333\214S\325\252\302\002}\252\324})\344\323\201\247\212p4\340\324\360\302\235RBy\253\221\236EZV\315;\031\250\244\2175Q\334[\003\221\326\241\267\324B\313\216\325\260\223,\313\322\253\\@95A\270\250\330\324D\340\322\036j6\025\013\002)3Q\2675\013\324\017P8\315U\224b\251\311U\330T,*\026\025\023-B\313P\272f\2421\324R\035\242\250\310rj\006\025\321f\232Z\230^\215\364\273\250\335OY=i\373\307jz\275J\257\212\231d\310\305J\222\021R\254\204\234\324\350\371\025&jD\3062FE&\325\'\216\224\341\020\364\251Q@\343\025&=iA\247\356\243uH\2221 f\257\305#*\362*A \'$\322\203\371R\371\200w\247\007\317\265<\020\335\351\341G\367\215/\314:05$nC|\325z3\337\265ZF\251\224\322\221Y\372\224d\247J\310H\210`En[\202\261\256MYl2\237Z\314\230`\232\254MF\306\224\036)\254*\027\034\032\216\230\325\023{\324\016*\273\325Yj\243\216\265\003\212\205\224\324ej2\265\023\255@\303\025\014\214*\224\315Y\357\'\315Q\264\265\320\026\246\226\250\313P\032\215\364\340\364\340\324\360\324\365z\225Z\245G\305L\255S#\35550q\236*P\334T\261\313\216\243\212\220\225\352*h\361\267=\350i\001n)w\322\356\24058\032r6\r]\023\226@\270\351J\244\324\241\370\245\r\315;u85H\256j@\364\354\325\313yr\274\232\275\033\202*ej\225Z\251j\023\2026\016MS\215G\031\255 \000E\307J3\317\025R\344s\232\244z\324mB\232SP\310j#\322\243j\205\315D\306\242|Ui\024UWJ\256\313Le\250\\b\253\261\305W\222LU)\256\000\351T\244\270&\252\311)j\254\325\023WB\315L-L-I\272\215\324\007\247\207\247\206\247\206\251U\352D|\032\235\037\336\247V\315J\215\371\324\252\365 j\230>\341\322\236\257\371\324\204\250\307\257zqe*1\326\223u8\032pj\221\r^\205\224!\365\251RA\214b\220\236y\2434\240\323\305H\264\360i\340\324\3219C\317J\277\034\230\344T\253p\007=\252\031\365 \200\205\252P\271\231\2131\315]\333\307\025f\026\334\230=\250\'\006\240\270?-S&\243jh84\273\252\'94\302x\250\232\241~*\0065\023\032\211\252\273\340\346\240b\001\250]\200\252\322\275Q\232`\277Z\317\226Fj\254\365\003\212\205\205B\302\241j\333-L-L-M\335F\352]\324\340\325\"\265<5H\034c\336\236\216*Tz\260\262qS\307&\322\rK\346\2069\003\025 \223 T\310\343\036\364\360\370\346\237\36374\241\251\301\251\301\251\352j@jdsVT\232\221NMHq\332\200*@)\343\232P)\375*Tn0E=da\307QJ]\210;\016\rUh\345rwQ\004\255\013\355qZ\221I\270U\210\376\\\232Rj)\233(j\236)\255Q\221\315!\351Q=B\315\212\214\277\255F\346\252\274\233MFe\035\352\026\225}j\027\220\016\365VI@\351U$\230\325Ief\252\257\317Z\205\305@\342\240qP\260\250\036\241j\325-L-L-M-@jp4\006\251U\251\341\251\301\251\352\3252\265J\257S\253\344T\350\330\025*H\000 \363OW\251\225\263O\363\010\030\007\212\221Yvd\236i\003\023\323\245=^\244V\251T\324\311\326\256G\265\207&\226\236\rJ\244S\201\251\001\247\216\264\341\315=jE\247m\311\310\353OV*p\303\"\244\222\3329Wp\024\220\257\226p{U\235\324\205\252\263\271v\300\351JS\002\242aQ54\216*7\351U\332\241`s\315B\344\363\203U_\336\240r\rB@\'\236*\tT\212\254\365]\352\007\252\357P\260\250\034T\017P=B\325\003\n\320-L-M-L-@4\340\324\340i\352j@iwS\203T\252\365\"\265X\215\252\300$u\251\021\206EX\334:c\232Uz\2205.MM\023\200\016ic \223\272\245FS\327\212\224\020:\034\324\310julT\310sR\212z\340S\367\np>\224\360\364\360\324\365j\2205L\2074\346 \nXe\031\332MJ\024~4\355\330\246\034\277\002\200\241hcU\330\344\323\010\250\311\250\230\324-\315D\375*\273Uy\006j\263\214T\017\232\201\311\250O\0078\252\362rs\212\205\233\003\201U\236\241z\201\333\214Uw\250\032\241j\205\252\321jijaji4\252i\304\322\253T\252\324\355\324n\247\007\251\025\252Uj\231\032\254,\234T\210\334\324\301\360\335jM\340\236*Ej\2206i\301\260jEl\234\232x8\251\221\252ej\235\033\246j\326\365\000c\255;u(l\323\201\251\003S\203\323\203\324\201\351\352\3652\2759\233\"\230\215\207\253Fb0iL\231\031\024\365\223\tM\017\232k\034\323\010\342\243cQ6OJ\214\241=\351\214\204Uy3U\3335\003\325w\250^\240aP=@\340\212\254\365]\352\0075\003\232\201\315@\365\013T\017S\223L-M&\232Z\200\324\355\364\241\271\247\207\247\007\247\206\245\006\244V\305J\255R\243\325\200\371\002\246\215\307B)\340\374\330\0257\335\305J\215\305H\247&\244\347\322\234\032\244\014*U\315H\255\216\265:=L\036\244W\355R\003N\337N\337J\032\236\036\234$\251U\352tz\227vEF\371\007\"\246\211\303\'5*\256\345 \036\224\210\340\214\032yB9SI\311\353Mj\211\215D\336\324\336E1\213\n\205\333\332\253\276*&\013\216z\325f\343<Uw\250[\245B\302\241\224n\252\262%Uu\305WqU\334T/P5B\325\013\323\213S\t\246\223I\2323I\232pj]\364\340\364\360\365\"\265I\232z\265N\246\246\rS\241\356:\324\321\311\216\243\232\220\023\336\244V\251\003T\313\'@zT\203\004R\240\311\353S\253\355\340S\367\344\212\2205J\257\212\2208\251\026J\223viwR\207\247\007\247)\251\225\252Uj\234=;9\246\357h\217\003\"\246I\207U\342\225\324\246\037\261\251\221\362:\322\223\336\242f5\033\034\324g\"\232}\350b\010\252\354*&Z\201\205@\342\253\270\250XT\rP=W\220Ui\005V\220Ug\025\003\212\205\205@\342\241aL&\232M4\232L\321\272\2234\233\250\335NV\251CT\212\325(jz\265N\255S+\342\245V\251\327\234{\324\341\370\332{T\212}jQ\202x8\247\364\034\034\323\324\364\317J\221\037i\310\251\003\356l\232~\374\267\024\360\334\324\201\252da\216M<5<585;u\000\324\212\325*5J\257R\253\324\252\365/\017\326\223\312\301\371N)e\220\354\t\327\232\2263\201R\026\342\230G\031\246\026\035\206h\013\270g\275B\352W\255G\273\025\033\021Q7\025\023T\017U\336\240z\201\352\026\250\034Ui\005VqU\234T\014\265\003\212\205\205B\302\253\023M&\232M4\265&\3523HM\000\323\201\251\024\324\252\325\"\265H\032\244V\305L\255S\243T\350\370\251\223,x\251\225\261\326\244\rR\003\351R\253\345pi\350\t\351O\r\212pjr\265J\032\236\036\244W\251\003S\303S\303Q\272\236\032\236\032\246V\251\025\371\251\203T\361\276\0075(j\205\2372\014\366\251\321\352V8\034SKn\030\246\001\212_\247\024\016xj\2574E\016GJ\256y\250\330\324,j\007j\201\315WcP=D\331\250Z\241u\252\356\265^E\252\356\270\025]\305@\302\240qT\311\246\223L&\233\232J\\\322\023E(4\360\325\"\265J\246\244SR\003R\251\251\321\252tl\324\350\307\265L\030\2202jEj\225H\357Rdg\212\221\033\007\"\244$\261\317Jr\343<\363K\236x\247\003R+T\212\325 j\2205;u.\352pjxj\221Z\245V\251\321\252Uo\230\n\262\274\324\027\n\335S\255@\223\310\207\346SV\222\3600\301\340\324\261\271bI\351R\003\232:Q\327\353J\300H\244\036\265I\320\2515\013\212\201\205W\221j\006Z\205\326\240e5\013q\326\241qQ\267J\256\342\253\275Vz\256\302\241u\252\356+<\232i5\0314\334\321\232Pi\017Zu\024\340i\352jE5 5*\232\225ML\206\247F\305Y\211\271\346\246SOB;\323\267b\254\"\202\231\3174\261\261\006\254;p1K\021\014piH\305\001\251\342\244V\247\206\251CS\203S\303R\203O\006\244\rR+T\310\325:\034\221V\221\252P\271\243\310\004\321\366A\235\330\253\n\243n\010\305A(1\237Z\204\315\203\2021NI\206jRy\014\275)\222\000\334\325WZ\201\220T\016\225\013F{T\016\225\t\030\355P\270\007\255V\221qP0\250\331sP:qU\335*\273\255VqU%\006\262w\323K\323KSwQ\272\234\032\235\232\001\247f\224S\305<\034S\301\251\024\324\312jd5:\032\231Z\247V\251\024\323\301\311\251s\267\200sR!\365\342\237\270\376\025&\010\000\323\321\371\245lg\345\247\002@\346\236\017\24585H\036\236\036\234\032\236\255R\003O\006\244V\251\025\252\3025Z\211\363V\220\324\353R\nx\024\025\r\324T\022[+d\342\263\246\267x\330\225\351K\024\344|\257\305M\272\242j\201\205B\342\242\'\025]\352\273\324\016*\027\\\212\256\302\243\"\242qU\244\025ZN\365Y\305D#\311\311\2546U\r\301\310\246\230\201\357BC\273\275G2\252q\236j\024\345\261R\310\2331\212h4\274\216\264\3455 4\341N\3158\032\221ML\246\246SS\241\251\224\324\313\310\310\251\024\324\200\323\324\324\310A\353S#\214`\324\252\343\'#\212L\363\300\342\227w9\2517\226\306iA\247\251\247S\207\024\345lS\303T\201\352Ezxj\221Z\247\215\352\3227q\326\256D\371\025eMJ\246\244\006\226\203\315D\350\017Z\245s\007\004\257Z\253\034\204pz\212y9\250\332\241qQ0\250\034T\016*\006\034T\r\301\250Xu\250\033\203P\271\252\317U\334f\241)\232F\\q\\\256\r4\222)\003\262\3645\033e\2174\344m\224\346\224\2767S\323n\340j\304\221\243.\340qQ\004\\q\326\215\244S\226\235J\rH\246\246CR\255N\206\246\\\232\263\033\014R\347\232\2205=MJ\246\246SN\r\203R\371\271\024\356\n\217Z\010\333@jxjpz~\372pjx4\340jEj\221Z\244V\251\221\252\324OV\342c\273\025q\rL\246\244\006\236\r\031\246\232\212^\225\224\343\023\034S\373S\032\242j\211\205B\342\241qQ\025\315W\2250j\263\212\256\375*\007\346\240aQ\025\246\021\212\255!\346\271\242)\244S\010\244\333M+I\266\224\np\334x\315=r\rNHd\344\363LZ~(\035i\353R\245L\2650\251TT\311\301\342\245\335\223O\006\236\rJ\206\246SO\006\234\r<7\024\273\210\024\375\300\217zvW\034u\247.\336\364\270\317\335\244\r\203R+\324\200\323\201\251T\324\212je5b6\301\253\221\276q\216\265r6\316*u5\"\232x4\354\322\023PJx5\234>g&\236j3Q5F\302\242lTOP\236\rG\"\344UI\027\232\252\342\253\313\305V2`\323K\203U\335\362MW\222\271\242qHZ\232M&h4\001KN\002\237\212Jr\324\202\223\2758T\253S\251\251\243\035\315H*d4\360zT\200\323\301\251T\324\212\325 4\340\324\355\324\271\247\003N\006\237\237JP\304\016)\353\2029\353I\312\232z\275H\247\232\224\032\231\rL\246\245V\253\220\2660j\344g\025aZ\244V\251\003R\356\244-PN~CU\020b\221\2150\232\215\252&5\023TMQ\232i\344Uy\026\252H\265B\343\200k8\276Z\230\362\021ML\221\232d\225\3160\315DA\024\334\321E:\227\212QO\035)*@0\0058t\245\305-=*d\367\253\010r8\251\000\251\024\323\305J*E\305;#\265=MJ\r85874\270=\216i\300\221\326\234\0334\340y\247n\315(<\324\231\365\353H\016\rJ\017\245J\247\212\221\032\247SS+U\270\030\021W\021\252uj\225Z\236\032\227u\005\252\t\233<T$\342\230MFM0\232\215\252&\250\310\250\310\250\311\305B\346\253Hk>\353\2258\254\365\213\004\223PL2\334S\320aj\031k\234ja\250\310\301\246\232ZQN\353J\005<t\244\247\216i\353N\245\3059je5b/Z\2274\240\324\212jE5 4\340i\340\324\200\323\272\032:\322\362:S\203\032xl\323\201\247\003N\006\235\270\223N\316i\352qR\241\'\245H\207&\247SR\253U\250[\006\257#T\352\325*\265<r(-Lg\307\326\242-\334\323\t\246\023L&\230i\206\243ja\250\333\245@\365VW\305Vy*\214\244\263{T\017\307J\252\303-O\306\005V\224\327:\324\303L4\303IN\006\236\r8S\207JJr\324\203\212x\245\245\025*\325\210\317\002\236)\342\236\016\rH\r85<\032\220\032x8\247f\224\032v\352Pi\340\322\346\236\r8\036)A\247\203O\006\236\255R\247&\247\004v\251\024\325\3301\216z\325\244j\260\255R)\251C`\363Li\007\'\265G\273\'&\232[&\232M4\232a\246\323M0\323\010\250\330UyN\005e\\\312rqU\203\356\024\326\305V~j2\270\244n\225Rc\326\271\363Q\232a\246\232i\245\024\341O\006\237\221\212JU\342\245\316qNZu8\nz\365\251\326\244\006\236)\342\234\r8qRn\315<\032xjp4\354\322\346\234\r<\032\\\323\324\323\263J\r<\032vi\353R\243b\246F\343\025<c$\003V\224c\245N\222c\212\264\215\305J\207\232s?5\031$\375(\315%\024\204SH\246\232n3HV\2435\004\215\212\316\270\230\034\200k6f\004\324C\332\220\212aZ\211\352&\351T\346\357XMQ\032\214\232CGZAN\035i\302\226\224\032vx\251\242\0314\3420i\302\236\0059x5*\324\253O\024\341N\025 \247\np4\340i\340\323\301\245\245\025 \245\247\016)\331\245\31585(jxjz\232\231\032\254\304\325r6\350z\325\205\000\266EXSR+S\272\321\212P)qF)\n\323J\323N\0050\232\215\232\240\222P\240\344\326]\335\340\350\reKpOJ\215r\375jP\270\244j\215\215WsQ=T\232\260\232\243j\210\322Rt4\235)\342\234)M(\247\212\222#\206\253.\001\031\024\300*E\024\270\251\005H\225 \245\035i\342\244\024\354R\323\205<S\326\235@\251\001\247\nPih\245\006\224\032x5\"\232\231MXF\253P\276:\325\224|c5eZ\244\006\244\rN\006\234)\331\244\2445\031\2463\001U\345\235W\275g\334_\355\007\006\262\246\277f\316\rSfy\r\002/Z\225W\024\343\322\242cQ3T\016j&5Z^\225\204\325\033Tt\332i\344QJ*U\240\214\032QO\024\364\034\212\262y\300\245e\332i\313N\002\234\005H\240\212\224\014\323\261\306)G\025 \247\203E(4\340jU4\354\321\212u<\032v)(\024\352\0058\032z\232\235\032\247CS\251\351VCg\034\346\255\306\334\n\2305H\2475*\323\300\247b\230N*&\220Uy.@\3175Rk\265\037\305Y\267\027\231\350k9\313\312}\251V\337\035jA\020\024\245E0\323\030\324\016\325\0135D\306\231\236*\274\335+\021\205D\325\031\246\032m(\245\024\354\372R\203\332\235\214S\305H\275j\310q\263\035\350\352i\340T\202\224\n\221jT\031\2470\244\357O\024\340i\324\231\305(<\324\212\324\360j@iisK\232\013\021N\355\223A\342\200iA\251\024\324\252jt5:\266*\302\2779\035j\304.s\212\266\255R\251\251\224\324\242\231$\252\200\344\342\262\256\265UN\024\325\003~\362\0363P\310\362\037Z\200\243\267SJ \035\352A\030\035\250*\0054\212\215\215@\315\212\211\232\240f\250\031\271\2461\250\313qPL\334\032\310j\205\205Fi\246\232E%\024\242\234\016jE\367\251\027\322\245\013\307Zp\253p\306\031\t\364\244\3075 \031\247\005\247\201S(\305!\240S\251\302\235Hi)\342\236)\352i\340\322\346\224\032\0174\354\2221J)wQ\332\234\2652\032\260\225\"\236jp\340~U,r\200\3035z7\315XV\247\371\252\275N*95\024@y\254{\273\366\231\260\235*\232\304\\\345\3715ec\000T\235\260E!@G\035j\"0i\013b\230Z\242f\250\235\252\007j\201\332\240v\250\213S\031\252&n*\274\255Y\306\243aQ\232\214\322\036\224\332(\035i\342\236\265*\212\231E?mX\200\237\272;\323\366\025<\324\200S\200\251\024S\263IE8S\305-6\227\024\242\236:\323\205<\032\\\322\206\245\006\235\232Z^\264\202\236\t\251\227\246jdj\2234\370\316[\025$\352c\034u\244\217P\300\303pEL50\007\025^[\311%\345sP\215\357\367\215J\251\212\225x\247\206\240\2657v)\215%F\315\273\247Z\205\233\025\031z\215\232\241sP9\250\030\324,\324\315\325\023\265W\220\3253L5\023S\r!\246\232J)\342\236\2652\324\350*]\265f\331rH\003\232V\316\356i\352)\370\247\201\232\\`\321@\024\016)\353K\364\245\307\024\224\242\234)\302\226\224\032Zu.i\374\020(\317\006\200i\342\236\246\245\rR\006\247+\340\361VZA*\017QU\236\020\306\225b\003\265I\214\014P8\245\335F\372<\312<\312B\324\306j\210\276*3\'\2551\210\307\025\0215\023\034\324Lj\0075\003\032a5\023\232\255#T\rQ\032\214\323M4\323M7\275-<S\326\247J\260\202\245\002\254\333\022\255\305:C\227\310\247\250\247\342\227\265\004~\224\240\0222(#\201K\214\nQNQ\326\227\024\230\243\024\264\340i\324S\251A\245\245\351K\316y\242\234\032\236\r<\032xjxj\261\033|\206\231\346sN\337\232pl\365\246\027\246\357\244/I\276\215\364o\246\027\246\026\246\023Q\261\246\357\3051\271\372T.\010\252\356j\0265\0315\013\032\256\346\243j\211\2523M4\323M4\332Zx\251\026\246J\263\035N\242\254\333\256\343\216\346\245\222\023\030\311\246-IJ(\31794\240q\301\243\2674\270\247\001J(\245\024\237J\\R\201\232u(\240zR\347\024\341\322\227\255-\024S\201\247\006\247n\245\rOY\010\342\215\374\322\211)\376g\035i\205\350\337H^\223}&\3727\322n\246\226\246\026\246\226\246\023LcL-\236*\031\024\212\200\367\250\\\324,j\007\250\330\324f\2434\303\326\232M#SE8S\205H\265:\n\263\035N\2654Rl>\225l\376\372\"T\222E@\243\006\245\024\224R\212p\346\224S\251\324b\2121E(\245\242\234(\306iz\032ZQK\212(\245\006\215\324n\243u.\357J7R\357\240\271\305\001\350/M\335F\372]\324\233\251\013S\013SKSKSKTmM2z\363PI\355P1\250^\240z\215\215FM0\323\r4\323Z\212QO\025\"\324\311V#\253)O#5j\331\202\251\365\246\377\000\025?<SM&i\300\323\251\300\376\024\341O\300\365\245*G=i:\322\322\342\223\030\245\0034\001\353KI\322\226\234\264\352BqJ\r!\244\315\031\030\246\226\"\215\324n\243u.\3527Rn\243u\033\250\335I\272\227u4\2654\2657u\031\246\223LcQ1\246\026\343\006\241\221p2:T\rP\275D\306\243&\243&\232i)\244\346\212QR-H\005J\265a*\302\032\222\254[\270V\344f\234\347,H\242\220\322QN\006\234\r8\034T\213\3158t\243\003\034R\212\\Q\266\212)GJCE(4\374\322\023M\315\004\346\214R\036)\244\322d\322f\223u;<P\r\004\322n\243u&\3527Q\272\215\324\322i\244\323sHM4\232a5\023S7v=*\031\007\247J\201\252\273\032\215\2150\323I\244&\233\232\005<T\212jAR\255N\225a*A\322\246\204e\205J\343\r\315&i\t\246\320\r;4\340i\300\324\200\342\237\232\\\343\2458\014\232p\030\245\002\220\212L\322\322\036\264\224R\346\202sM&\214\322\203E!\024\302i\204\322\203\232\\\361\212L\321\2323M4\322h\335F\3527Rn\244&\232M!4\322i\204\323\t\250\332\243\'\326\242qU\030\324f\230M4\232i4\231\245\245\025*\324\200\324\250j\302\032\235jQRF\373H\305Lr\347\216i;\322\036\224\323I\232\\\323\201\247\251\365\247\203R\016\224\361\357R\014R\321\320\321\326\223\034\321Hi\010\315\035)3Fi\244\321\273\336\233\236iwzP^\230\315L\245\315\031\2434\240\320M74\322i\244\321\272\2234n\243u4\232nh\3154\323\032\243j\215\2522}j\2214\302j3M4\322h\2434\361OSR\003R\251\253\010j\302\032\224S\207Z\274\233V\023\317&\253\347\232Ri\246\231K\232p4\360i\352x\367\251\007J\220\032\220S\261K\214\321\212)1Hi;SM%!4\322i\245\2517Rf\2234\322h\315\031\367\244\335N\316G\024\252q\326\220\232ajBi\244\322\026\244\315\033\250\315\004\323I\244\315\031\246\236j6\250\215F\325@\232i4\302i\204\322f\214\322\323\201\247\003R)\251T\324\350j\312\032\235iI\305[\265\304\200\2065\034\213\261\310\240\021McL4\200\323\201\251\024\323\324\324\240\361O\006\244SO\006\235J\r.8\246\237jCHi\264\323L=)\244\323OZ\030\00085\036h\315!4\231\2434f\2246)X\372SwqL&\2234\322\324\233\2513Fh\315\031\244&\233\2327PM0\324mQ\232\316&\230M4\232a4\231\245\240S\307Zp\251\005H\246\247CV\020\325\2054\254x\251l\345\304\240\032\275=\271\220\3461\237\245Tu1\360\334T{\263M&\233\232p5 5\"\232\221MH\246\244\006\236\r8\032\\\323\267qIHi\246\222\232i\206\232i\215L&\2234\231\244\2434Rf\220\2327SI\244&\220\232i4\334\321\272\214\322f\2274\231\244&\2234f\220\323\rF\325\226M0\232i4\322i3KN\024\361N\024\361R-J\246\247F\253(\324\342j5b\216\010\255\233=McC\270U\033\253\257>B\325\016\352B\324\231\247\203R\003O\006\244SR\003R\003R\003K\232\\\322\346\2274f\220\323M4\323I\346\230M0\323\r4\323sFh\242\220\360i\271\2434\231\246\223I\232i4\334\321\2323KFi3M&\2234f\214\346\232MF\325\222Z\230Z\230Z\223u\031\245\006\236\rH)\300\323\205H*E5*\265L\257O\337HM cN\006\235\272\220\265\001\252E5 5\"\232\220\032\221MH\r<\032p4\264\240\322\321Fi\246\233Mja\246\232a\246\023L&\2234\240\322\346\220\236)\204\323sM-Fi\t\244\315!8\244\315\031\245\315\024\204\323I\244&\2234f\202i\215X\244\323\013SsIFi\300\323\324\324\200\323\301\247)\247\203O\006\244\rO\rO\017N\rK\232]\324n\2434\240\324\212jE5*\232\220\032x5\"\232x4\340i\300\323\250\315\024f\214\323MFi\244\323s\351L&\230\306\230i\271\2434\240\323\331~\\\212\210\234S\017\037Ji4\231=\251\271\2434QE&h\315\004\323I\2444\231\244\315\033\275\351\t\254&ja4\334\321\232\\\322\203R)\251\001\247\203N\006\236\r8\032x4\340i\301\251\341\251wS\203R\346\226\234\r<S\301\251T\324\212j@i\300\323\301\247\203N\006\236\r.i\t\244\315\031\244&\230M0\232a84\322\336\264\322i\215Q\232\006M(4\340\347\030\244q\305DM0\232M\330\246\226\240\032v}(\315\035h\351Hi3IHM0\322f\2234\026\254\026ni\244\322f\214\321\232p4\360jE4\361\322\236\r8\032p4\360i\300\323\201\247\003N\006\224\032p4\340i\340\323\201\247\203R)\251\024\324\200\323\301\247\203N\3158\032p4\354\321I\2323HM0\232\214\232a4\322i\244\323I\250\311\244\007\035i\336\342\223uI\346\002\270\"\241c\315DM4\232@\330\315(\351\305(4f\2274\271\244&\222\220\323I\246\223M4\334\322f\260Kd\322\023I\272\214\322\346\224\032z\232\221O5(4\340i\300\322\203N\006\234\032\234\032\234\r8\032vi\300\323\201\251\001\247\003O\006\244\006\236\r<\032\22058\032x4\360i\300\323\263Fi\t\244&\214\323\030\324li\204\323\t\246\223M&\230M4\236jEn9\2466G=\251\003qL-M&\230M&i\331\030\343\255.sFOz(\315\033\250\316h\246\032i8\244\246\236\264\206\271\342\334SKR\203FiA\245\006\236\rJ\206\244\006\234\r;4\340iwR\206\247\006\247\006\247\206\247\003N\006\234\247\232\220\032p5 4\360i\340\323\301\247\203O\006\236\246\234\r8\032v\352\\\321\232ni3HNj6\246\023L&\230M4\232i4\302iA\342\244\335\270TG\214\342\230M4\232ni\t\240\032x<\361O\335\232C\3056\2234f\214\322\023M\'4\204\322f\232k\233-I\272\215\324\240\322\346\234\r9NML\r<\032x4\240\322\346\215\324\240\323\203S\303S\203S\203S\301\251\024\323\301\247\203O\006\236\r<\032x4\360\324\360i\340\323\201\247f\234\r\031\2434\204\322f\220\232i4\303Q\232a4\322i\204\322f\214\322\206\301\346\2069\025\0214\334\342\233HzQ\236)CS\325\251\375E34\323II\2323Fi)\264\231\256_4f\224\032\\\323\201\247f\236\265 4\360i\300\323\201\245\315&i\300\322\203O\006\234\032\234\032\236\016jPi\340\323\301\247\203N\006\236\032\236\r<5=MH\032\22458\032vh\315.\352Bi3\357I\232L\323I\246\032\214\323\r4\323s\326\2234\023K\234\214S\032\230M%%&h\3158\032z\2659\271\346\230i\206\220\2323Fi\r!4\225\312f\2274\240\323\201\247\003N\006\244\006\234\r8\032x4\240\322\346\214\373\322\203N\rJ\032\234\032\244SR!\251\001\247\206\247\206\247\003N\rO\rOV\247\206\251\001\247\203J\032\234\032\235\2323J\032\202i3Fi3HM0\363L&\230i\206\233\330\323s\315-\004\323I\374i\207\332\232M&h\2434\003N\rO\rIM4\323\326\212(\244=i\246\271,\322\203N\006\234\r8\034\323\301\247\003N\006\234\r<\032]\324n\2434\271\245\006\234\032\234\rH\rL\r<\032p4\360\324\340\324\340\324\360\324\360\325 4\360i\341\251\331\245\rN\rK\232\001\245\315&h\315&i\t\247.0sM+\270dT\0140y\246\023M\355M\'\2323A4\323M&\233I\301\240\373RQ@4\340\324\340h\246\236i\017ZJPh4\206\270\372p\247\003N\006\234\r8\032Pi\331\245\006\235\232\\\322\356\240585.iA\247\203R\241\251\001\247\006\247\206\247\006\247\206\247\003O\rO\rR+S\301\247\206\247\006\245\31585.\352\\\321\2323Fh\315\006\223v(\017\214\212B\233\206sP\260\3052\230zSM\031\240\234\323M74\032LdqI\232J)sN\rK\234\321M4QE\025\306\212p4\340i\300\323\201\245\315(4\271\247\003K\272\227u.\352\003S\267R\203N\006\236\rJ\247\024\360i\301\251\301\251\340\323\203S\301\247\206\247\203O\006\244\rO\rN\rK\232pjPisF\352\\\373\322f\227>\364f\212m\001\261Ms\232\214\323\r0\232nisHM6\212L\322Q\232L\321\232\\\322\203KE\024\235\351h\2560\032\\\323\201\247f\224\032viA\245\315(4\003K\232]\324\003N\rN\rN\006\234\246\245\rO\rN\335N\rN\rR\003O\006\234\r<\032xj\220585<5(4\340iCR\356\245\315.i3K\2323Fi\t\244&\232M4\232\214\323M4\232L\321\232)\t\240s\305%4\234\321E\031\2434\340h\315.i(\242\270\300iA\247\003N\006\2274\240\322\346\214\322\356\245\315\033\250\315(jv\352Pi\341\251\340\324\201\251\301\251\300\323\303S\203S\303T\201\251\301\251\340\323\301\251\003S\201\247\003N\006\235\232\\\323\201\245\315\031\2434f\227>\364g\336\2234\231\244\3154\232a\246\323M!\244\315.i\r\024\037j@3IHi)s@4\354\322\321E\025\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\244IDATx^\355\335\211r\343\250\026\000\320\224\373\377?9\256\031\333\261\343\310Z@\002\304rN\275z5-K\010.\227EJ\267\363\365\325\266\353\364@\264\313\364\000\000\334\214\263@\034_L\001\240I{\227\300q6\tc\333\233\037\000\000\264\304\356\276\021:\352\240\016\236o.\222\000\000\000\006\322\301C\014\201<\354\301AM\017\242\313y\023\376Y\367\345\227>\030\234\004\340N\"\000E5\275u\206\303\256\327\nV\336\3470<\241&\'\334\362t\225\266\271T\265\336\356S\352\226T\306\302\017\000\3000\376<\365D>\377\337O\216\272\002\242\310\256l\016=\366\036\272\230:\351Tj\362\234\374\345\345\240\254\376\000\371\305\316\265?\347/^\266\370\301\016\347n\000R\266\244s\347v\024\345\\7G\205\\\200\256\254\217\371\365O\2419\326\260\235z\t\\/\355\330i\360\346\177\215\033\201\200w\357K\217\000+\227\354\267p\257_\233\'\354\223\251\330.=bu\375\372\367\236\001\007\003\370H\262\333\377gI\2537\007+\372!w}a0\251\207\350_\006lv\331B\23473\340E\252\001,Z\233\"#\267\0001\247Oo\033s-\211=\202\177\371\372\236\034\237\232v\322\264\023\243\035. \334\322\033\260\321M\373t\206\310\301\235\241\000\260\311TIE\244\343\246\303!\nx\226\370\343\360\r#\224\274W\007\256\361\275\031 C\221\201.2 Z\300\357\364l%\246\263-\271\037\014h$\351\265\2228\225\0216\240\214\214Kc\306\242\367\252\260J\220G\360Fbrb\372A\022\\\223dnw\374mG\371\3737\345\021\250G\220\362\206\352\377\322\237?\244\377\351\235\340d\313[\261:\004\007\2431s}7w\214\332\365\232\241\0041h\233\224\270\333\022\027G\313\316X\021\316\270\'U\221\002\305]oQ\177\306\375\300\032p+\342~\371L\031\227\231c\207L\0373S\227\237Yc\325\335V\313\317b#\177\267\306I*\t\026t\302\210:*\305\274\231\242\014\330 \3152\022\\Ng9/\357}\340\357\216\177\231\331c\366m\305\373\037\312T\243c\367\010\177<\324O\377\274\350-\376i\272\"M)l\272D\364\362\231jI\210&\202u\323LE\003\325\222\000\024\360\231\274\237G\252\325PU[!\244e\375|?\364\364\360\026\335D\016\323o\004\223g\235\233}\340g\034\021#<\342\324t\256\263wm\345a\272\025^\361\035\324S6N\022\240\227|\310\334E\231\213\317\245\321j\327b\272a>\333\376\356\334\177%pH\225\203\257\262\251\255\177\227\023b\036z\303\327y\211S5\364\376\275I\034\306Wy\251\313}\312U\356K\366\033\264`\324\321\000\037\014\006\306r0\343\017^\276\310\322\014\220T\360\264\032|bn\327\327\032SM\225\032\262\024\263\245\343i|\374[\2034\362\224\032\346v\357{\036\236Y\211\302\006j\352\036\231\262\234\212\215\326\345\271\036\3578\323%\274cC\317\243k\227\361\246>\350\313\357\243\364\333\221m5\014\374\245:\334\366\340A\215\240\037:\374\220\370\360-\215\276\342\342\253\016\300\274\364S{\372\022\311\350\325]\326V\302\370\301G_\214\374\341\214\333\345\346.\346\311\214<\262\3155:\214td\023c\371;1\313\377%\003E\246\305\246^\037\265\316\266\315\030\312F\002\314~|\213\374\354\007\231=\357\371\375\347\350\303\341l8\243Ag\t\014VHH\002\213\312b.\0178EH\252P\205T]u\346\310\'\241\310\216L\225?T\241\325\356\214\314Z\226\034H\200\003\227&TG-h\200Ic\215\350\214mi\"\275\347\305\334\267\020\317\035\203\362n\251\273\224\276\344UM\3347+b\276\n\323o\230\372m\031\214\252\245Q]w]\227\227\320\345O\316WwL\233Rs7/\370\255\262\177\250RLOC\256\247\266\000\000df\303\315\017\273h\316\"\367r\272O\362!!\356\352\347k=\265\005\010`\320\307\020\255A,\375`E\002\014h)\031*\325Vm\213\022\0326t\230\"\317&u\330\264\014\032\233\355C\255\356\\\356M\356\263\335\303\3534\237\353\266:\332h\307\336\216\334{\035\365\212\232H%\000\014-j\276(\305\274\224\334RH\257\313\0371\222*\277\267\257\253\277\273\000\000\241r\274\030]+\362\343\263\217\003\347\261\027H$GR\001\355iy*\270\327\335\252\020\353\362w\001h5\200\337\267\212\377\274 \270\264\333\214\363\265\033\271[\036\267[\373\032\264<\375\1775_}\356\222\366b\322\302\022\253\264n}M\241\247\006y3\224\247\326\256{uDw3\t\250\333\301\016\\\272|.;\347\216\245\260T\207x\351JjG\3126\317<k/\024\237+\027\206\260\020\323\363}V\354\363H]\374| \277\231\020\317\034\242\177\277\263A\235\tP\373l\005\344V\347\334D1\022\240\2544\361n\357/P?*\334h\265\017\230\351\357\303e>\335\313NV\0327\257p\212+\000\220X\242\355\305\314\366\222`\211:\201rtYaf\230\206u\327y\377\372k\0221.\022\340T\242\017\230\010`xf\001\350\310\216\367k\346\200\236D\'@\364\005\324\315x\236\032,\"\2035\027\210`~\200\\\306\332O\233K\022\020\304\303\204\020R0\222\000\000\310\256\350\246\263\350\315\200\342\306z\013\013\345\031c\000D\032\355\021l\264\366\206\312\025\227\327\326$\327\r\240\022R\034>\030\026\000\000\205\005\377x \370\304E\037\277O\341{\362g\212y\337w?\376\273\334N|\232\006P\251r\203\002\000\200\363\331\375u\306\223\347\311&\035\240?\006\327P\002X\014``\r\315U\000\225\351b\013\265s\031\350\242\355\275\320\031\033v&\371\207{93\205=;@G0\034IO;d+\274\271\030\021\207<\267\2033\333B\250A@j\0161\t<\246\272\350\246\006\204\217&\374\364d\262\016\215N%\316\225\252\347u|\363R\245B_\306J\354\261Z\233\226\3301\022\353EeL@{\255\246\362xa]\r\307\233\311y\327\351\001\232\264\222\360\037_\361\360i\373\014*\267\222\000\237\364\367P\002\272;\340\024\240I\177Fw\324JA\027\226\246w\271\360\264\024\241\023\235\3319\025\206\343\200\276Z3k%Yv\265~\327E\355\352\247\271+\211\020\255\237\250\214c\265\317V?\234J\231I-k>\016\3157\340d\342\007\264+j\335\357\226y\234\036\254\216\346\225\017\223\344\377\277\351\201v$i\177Y\025V\371Y\245\n\253\306\'\3354\234\253N\007\312ha\262Y\331\025\267\255\205\340\367C\264\233\367\234\t\032~\216\335\257\333i0?\241\013\362\372\327\006\325O\225\325W\020\000\000v\261\323]t\211y\254\21397\271\2147\317V\364\275\340l\245\0374\251\327V5\347>7\260\2327\327\255S!\347\320\255\327(7\334G\'\003\000\200B<\200\320\213\2507nP\234\207\274A\351x\300L02}?\274\200\257L\337\"\213\032\027\233\002:\034:b@\303\270b7\000\r1\265\225\2623\211v^\006\320\201z\226\250\357\235\223\361l\013f\017R\267\235\t\300nUE\274\252\3120(+\307\340$\000@\303\354%\001\000\000F\343u\036\264\307\270\005\362\363\256x0\226\226f\350*r(0\347\027\270\005\014\342{z\240vV\256\204\022|Q\004\0041p\273w\321\3074A\242\002\0000\252\337w@v\3050,/\203i\225\245+\0053\300\227T\202\202\016\016\267\304SV\342\342\032v\260_Z4`\223a\312\034\0100\260?\213\200\025a<\372|`\257\316\257<\013<\261\345u\275\377O\224a8\225\317\375\024\"\017\250\217MI1u\206\272\316ZAG\254\375\300@.\266\026\000\000\000\003\272.<\013z1\006\000\320\232\205\215\035\314\221.0,\303\037I\000\000\000}\332\263\325\337s\r\320\000\203\033\000\000\330\340wb\017L\337\003\000\000@\223<\322\017N\002\000\000\000\000\000\020\311w\n\003\214\3516\377\373\361b\333\364\037\000\000\300P\274\312\005\000\000\000\000\000\200\221\370\213\242\000\000\000\000\000\000\000\000\000\000\000\000\320\013\337\'\010\000\000\243\363T\000$\364\037\213Ab\036\317\"\"\006\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
new file mode 100644
index 0000000..5397cb37
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -0,0 +1,3 @@
+tile_key: "9"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363<\322\206\247f\234\r(4\271\2434\271\367\243u.h\rK\272\214\322\356\247\006\247\006\346\244\rO\rN\rO\rN\rO\rO\rO\rO\rO\rR\006\247\206\247\203N\rN\006\234\r8\032\\\321\232]\324\271\2434f\214\322f\214\322f\232M4\323I\244\2444\224QE\024\200\3434\235i\r%\024S\250\242\212up\364f\234\r(4\354\320\r.\3527Q\272\2274\003K\2327R\346\224585<5<5<5<585<5<5<5H\032\236\246\244\006\236\r<\032x4\340i\300\323\201\245\006\2274f\2274f\214\342\214\373\322f\214\320M%6\220\365\244\244=i(\244&\200sKM\246\232(\242\212QKE(\024\265\303\323sJ\r;4f\2274n\245\315\033\250\315(j\\\321\2323F\352pjxn)\301\251\301\252@\324\360\324\360\324\360\324\360\325\"\265J\255O\006\236\r<\032x4\360i\300\323\201\245\315;4f\214\322\347\336\212L\321\232\\\321HM%!\024\224\206\222\212i\245\024\264\332i\242\212(\245\024\264\270\245\351Hk\2074\224\231\346\234\r\031\2434f\214\321\2327R\356\243u.\3523F\352]\324\340\324\360\324\340\324\360\325\"\265<5<5H\rH\246\245V\251\003S\201\247\203R\003N\006\236\r8\032]\324\354\322\346\214\322\321I\232\\\321Fh\242\214\323M!\244\242\223\275-\024\230\346\222\233E\024S\251@\245\242\232k\2104\224\206\214\322\203HM&i3Fh\315(4\271\2434f\215\324\340iA\247\006\247\006\247\206\251\003S\325\252@\325 j\221Z\245SR\003O\006\236\r<\032x4\340i\300\323\263N\006\2274f\235E\031\242\224\032Z(\242\220\322\032LqII\336\226\212)\264\204RQJ\005-:\212CI\\I\024\332CIE\024\206\220\322f\223>\364\271\2434\271\2434n\245\006\224\032p4\340i\341\251\341\251\341\252@\325\"\265J\246\245V\251\024\323\301\247\203O\006\236\r<\032p4\340\324\340i\300\320\016)\300\321E(9\245\247QE\024Sh\2444\224b\227\336\222\220\322R\001\212Z)@\245\242\232h\256,\212a\024\323IE\024\332i\246\346\212L\322\346\214\322\346\214\322\203J\r(4\340i\341\251\301\252Ej\221Z\244\rR\251\251T\324\200\324\201\251\340\323\303S\301\247\003N\006\234\r<\032p4\354\320\r.h\3158S\251\324QE\024\204Rb\212LR\320zSh\246\236(\245\305.(\242\212CI\\{\n\214\212a\024\322))0i)\246\233Hz\322Q\2323Fi3N\006\2274\240\323\201\247\006\247\203OSR\003R+T\252\325*\232\221MH\r<\032x4\360i\340\323\201\247\203N\006\234\r<\032\\\322\346\226\234;S\251\302\212)qF)(\"\223\024b\223\024R\032J)\000\305-\024QHM%\025\313\2749\252\355\031\025\031\030\353M\"\230E%4\323H\246\323i\244\346\214\322f\214\321\232\\\322\346\234\r(4\240\323\301\247\203R)\251T\324\212jU5(5 4\360i\340\323\301\247\203N\006\236\r<\032x4\340ii\302\224S\351\324\3521N\242\227\024\224Rb\223\024PFi\264\204RQE\024\202\226\232z\n(\256}\201\035EB\342\241x\3628\250\031\010\250\310\246\221M\"\232i\246\230i\264\207\255%\024QJ\r(4\352Pi\340\323\301\251\024\324\212jE52\232\220\032\220\032x4\360i\340\323\301\247\203O\006\236\r<S\305.iE<S\305<R\323\251qKE\024b\223\024\224SH\2444\224\206\222\212CHh\315\024\231\2435\222\312\r@\360\372UwB\265\031\301\250Y*\026LS\010\3054\212a\024\322)\206\232Fi(\2444\264R\212\\\323\251\342\234*AR)\251V\245SR\n\220\032x4\360i\340\323\301\247\203O\006\244Zx4\360ii\324\341\326\244Zx\247\np\024\264\354QF(\244\"\222\220\212B)\246\233Hi(\2444\224R\032J+0\212a4\326P\303\232\255$\004Ur\204\036j9W\035*\"3Q\025\246\021M\"\230E4\212i\244\242\212)E(\024\352p\247\212x\355R-J\265*\324\202\236)\342\244\006\236)\340\323\305<S\305<S\305>\234;S\207Z\221i\342\236)\330\247\001K\212Z(\244\305%!\024\323M=i\207\255\024\332)\r%\024\332(\252\014\265\021Zk\014Te\210\250\\\346\240e\3109\250\031\010\250\310\250\312\324dSH\246\021M\"\223\024\230\243\024\264\240R\201K\212p\247\nx\025\"\324\253R-H)\342\244\024\361O\035\251\342\236\264\361R\nx\355O\024\361N\024\361OZ\220\nx\024\360)\300R\342\226\2121M#\024\204R\032a\246\236\264\323IHi)\017ZJ):\320\005-Se\250\312\324N8\250XT,\246\242<S\030\217Ja@\325\023\305\351P2z\324M\221\332\223\031\244\3054\212LQ\266\227\024b\235\212P)@\247\001O\002\236\005J\242\244Zx\251\005<S\305<S\305<v\247\212\220S\326\236)\342\236)\353R(\251\000\247\201O\002\235K\212Z)\010\244\246\322\032a\246\032CM\2444\224\206\222\212(\243\025]\205D\302\243e\250Yj\026\250XTL*\"1I\274\212C\206\250\312\343\202*\027\217o#\2450\212M\264\233h\333K\266\215\264\273i\301iB\322\201O\002\236\005H\005<\nx\024\360*AR\001N\002\236\005<S\305<T\202\236\005<S\300\251\024T\200T\200S\305<R\201K\212\\RQM\244=i\246\230i\246\233M4Sh\240\212LP\005\030\245\252\3548\250XS\010\250\331sQ\262rsQ2T\016\230\250J\324l\264\322(\353\301\246\225\364\346\2431\203\315FT\2126\321\266\227m\033iv\323\202\322\205\245\333N\013O\002\234\005H\0058\n\220\nx\024\360)\340S\300\247\201O\002\236)\340S\305<T\212*E\025 \024\361O\024\361J)\324R\021IHi\247\2554\323M0\323i\r%!\024\224QE\024\240T.*\"\274\323\nSJ\212\201\316\r0\340\365\250\23528\252\345i\245i\205j2\264\336E\033A\351HW\324S\nc\245&\332]\264\273iv\322\355\245\333J\026\234\026\234\026\236\026\234\026\234\005<\nx\024\360)\340S\300\247\201N\002\236\005<S\300\251\000\251\000\251\000\247\212x\247\212p\351N\024\264PzSi\246\220\322\032a\351M4\323IM=h\242\212LQ\212\\QQ\311\307QQ\2023\315D\347\346\"\241c\316\r0\25794\205A\250\210\347\035\252\'J\214\2554\255FV\230R\230W\024\017zw\226\010\342\231\266\227m.\332]\264m\245\333K\266\234\026\236\026\224-8-<-8\np\024\360)\300S\300\247\001O\002\236\005<S\300\247\212\220T\213O\035\251\342\234:S\307JQKE!\244\246\322\032Jm0\364\244\246\322b\222\212(\242\212*2\373\206\rDN)\215\202sLe\3151\224\324y\307\024\322\271\2462\344TEqM+\232C\0354\305Lh\361L)M\332iv\322\355\243m.\332P\264\241iv\323\202\323\202\340\323\266\322\205\247\001J\0058\np\024\360)\300S\200\247\001O\002\236\264\361O\025 \247\212x\355O\024\341N\035)GZu\031\246\223\232)\264\206\222\232i\246\233M\242\212m\024QE\030\250\260\001\246\355\004\320c\301\246\225\246\221\305Wd\301\246\036)\206\243jn)@\247b\220\307\232\215\242\305Fc\246\354\366\243\241\301\245\0034\340\224\241)BS\266R\354\245\333N\333F\332\\S\261J\0058\np\024\340)\300S\200\247\nx\247\212x\247\212x\353N\024\360i\342\234)h\242\212)\264R\021M4\303\326\220\322R\021IHE\030\244\305.(\305-T\rRdu\240\270\357L\334\017CLn{\323\0175\023\000)\207\024\306Q\330\3231F(\247\250\247c=E#F\010\250\214x\355Lhw\366\346\232ad\367\251\025r\005<%.\312]\224\273)v\320\026\227m\033iv\322\201K\212v)qJ\0058\np\247\n}<S\205<S\201\247\203N\024\340i\331\245\242\212)1A\024\206\230i\246\220\323h\244\305%\024QE\025L\214\032c=0\266i\003\342\232_\232\003f\230\355Qg4\034\201MV\311\346\236V\224\n\\S\305<-)L\320\023\006\234\321\002:Ub\2066\351\305<.E8-.\3126R\354\243m\033h\333F\3326\322\342\227\024\270\245\002\226\235N\024\352p\247\003N\006\234\r<\032x4\340i\300\361K\232Z(\242\212CL4\323M\242\223\024\230\244#4b\214PE\000R\342\263\013\346\232M0\234S\013S\013Q\277\024\302\364\201\251\373\251\274f\234\r<\nx\024\340\265*\212xZpJv\323\216)\222\306\032>\225]\007j\227m.\332]\264m\244\333F\3326\322m\243\024b\214Q\212ZP)i\324\240\322\203N\006\234\r8S\305<\032x\247\nZPih\243\024\270\246\232i\246\232i\024\224Q\212n1E\024QF+\017\1774\360\334SI\250\330\323sL&\231\234\232\t\305;w\024\3459\247\014\323\303S\325\215L\207&\246\002\244\002\236\005</\024\320\241\216\017z\202X\274\267\247\005\315;m\033h\333I\266\215\264\233h\333I\212M\264b\214QE(\024\264S\251E8S\205H:\323\205<S\251A\245\035i\324S\250\246\221L4\323\322\222\212)\010\346\222\212)1@\353K\\\343\032r\266E)5\0314\332k\032fy\245oZi5\"\032\234t\246\223J\257\212\235d\350EYI\001\353R\214v\251S\223S\252\323Ja\270\244\236=\313\357P \342\244\333F\332B)1F(\305&)\010\246\342\214Rb\212(\242\212u\024\341O\024\361\332\236)\342\224S\207ZQ\326\235J\0058RR\032a\246\232CIE\030\244\"\223\024Q\336\200=)Es&\2054\342i\204\323sMcH)\343\221\212\215\226\225\016*p\374S\013P\032\244Rj\312?\0305:\260\307Z\2327\301\253\321\020\302\234\313\363S\2312\275*\243.\327\305<\n1I\212LQ\212\010\246\342\220\212LSH\244\305\024\230\244\242\212u\002\234)\342\236;S\305<t\240u\247S\307ZQN\247R\021M\"\232i\246\232i1K\212\\sI\212LQ\214\322\025\346\224\016\324\240W.i\005\004\323O\025\0337\245&iA\247\251\024\244dR\005\243\245!\247-N\270\247n\002\234\262U\210\336\257[\311\203VD\300\232\262\010e\025Z\3456\2604\320(\"\223\024b\214R\021M\"\223\024\334R\021HE%6\220\322QJ:R\212x\353N\024\361O\024\361@\353O\035i\302\234)\302\224QM4\323M\"\233E\024\242\214Q\2121HG4Q\326\271L\323Kb\233\273&\232\317\315FM\000\323\363H_\025*6Fi\331\246\223@\245c\201M\363qHe\315H\222U\210\234\325\270\345\"\255\246H\334*\314R\236\206\211\244/\317\245*\034\212~(\305\030\244\"\223\024\322)\010\244\3054\212f)\010\244\246\322\021IJ\0058S\205<S\307Zp\247\322\212p\247\201N\024\340)\330\244#\232CL4\322)\244Q\212Z1K\2121F)\244P\005-q\346\233\324\320\307h\367\250I\246\323\226\245\003\212c\016jU\030\024\356\324\334\323\226\221\307\025\003)\246\200sS\306*\324b\255\306\271\305[\214\225\\T\360\214\346\247\n\010 \365\250\323\344}\265`\014\322\342\214R\021M\"\232E4\2121M\"\230E4\212i\024\322)(\242\224S\3058\nx\247\np\247\nx\247\212p\247\nZ)\010\246\221M\"\233\212JP)h\242\227\024\322)1K\212\343\013f\232[oJc1=i\271\244\247%L(\306M<\nZi\247-\0148\250\217\"\220/52\361V#Ry\253\221\n\266\243\002\254\300\234dT\354\234dT2/\000\216\325,|\201Rm\243\036\324\230\246\221M\"\233\2121M\"\230E0\212i\024\322)\244Rb\224\nZp\247\212p\351O\245\024\360)\342\236)\302\235N\244\305%!\024\322)\244RQE/jZ)\010\244\242\270|\342\202A\2461\246\347\232Zz\361R\nx\247QM<\323\322\207\351Q\nv)\311\311\2558\020\005\025aTT\3123Va;j\354 8\'\265C,xb;S\"\033X\255O\2121HE4\212i\024\334PE4\212aZi\214\216j2)\244SH\244\242\224\np\247\n\220R\342\235O\024\341O\024\361N\024\242\212)1M\"\232i\270\244\242\235J\005\006\222\212+\200-\315\001\251i;\323\205H\007\024\341O\035){P)\017Z\221(z\217\024\247\245I\002\026`kN1\200*\302\324\312*e\025j\014\203O\230g\004Uc\303\203V\227\221F)1M\"\232E7\024\021M\"\232T\0321\306*\006\\\032i\024\302)1E.)@\247\212p\247\322\201O\024\360)\302\236)\324\270\353F3@\024\224\323M4\332\r \024\264\341E\024\230\243\025\347D\363NZx\351KJ*U\247R\203JM\000\322\023\315J\235(\316M&)\247\322\257Z\307\205\031\253\212*d\025aEL\203&\254\'\007\212\224\214\212\254\313\301\366\251\3429QO\305!\024\322)\244Rb\233\212i\024\322)*6\03750\212a\024\230\243\024b\234\0058\np\024\341J\005<\nx\247\212p\247\nQE\024\206\232i\204R\021IE(\024\264\270\342\200(\305\030\2577\034\323\201\247\203N\245\035i\340\323\301\247\nF\342\2054w\247\226\302\323Q\262jZD\\\2775\243\021\342\254\245N\202\245\310\002\244G\253P\363\315X\306EVu\301>\364\260\235\247\006\254\342\202\264\322\264\322\264\334SH\246\021M\"\223\025\031\034\323H\246\021I\266\223\024b\235\212p\024\340)i\300S\200\247\212x\024\340)\324\275\250\305\030\246\232i\024\206\233I\2121KN\002\212P(\305\030\2575\002\236\0058Q\232\\\323\225\252@i\371\305!9\245QK\212\033\221L\037+U\205\344S\200\301\253q\036\225q\010\035iL\276\224\345rjt\255\033q\2003V\2052H\362*\020\207>\365:\036\306\244\3054\255&\332i\\Tl)\204SH\246\221M+M+M+M\333I\2121J\005.)\300P\0058\nx\024\340)\342\234)\300R\322\342\222\232z\322\032i\024\204Rb\214P\005-(\034S\200\245\244\305y\270Z\\b\212B9\2434\341\326\245Zu\024\364\024\204\363N\034\212FL\362)\312\330\353Rn\315O\023\0003\336\245\016O\322\246\217\236\265aMX\213\226\025\245\017J\262\265&2)\0259\351Oh\3062)\000\310\240\2554\214Tl)\204SJ\323\010\244\333M\333HV\230V\220\2554\255&\332\\R\342\214R\342\234\0058\nx\024\340)\340S\200\245\305\024\021M4\322)\270\244\305\030\240\n1K@\024\360)h\305y\2304\354\346\232N)A\315\004S\220z\324\271\300\245\003\024\243\232\223\240\246\232QS\001\305\030\024\030\373\212|C\'\025d\214b\245F\253\0108\311\253p\0163W\2438\002\254#T\312\325\"\363S\371{\223\"\242\t\212R\265\033\naZaZiZaZ6PR\230V\230V\232V\223m&\3326\321\2121@\024\340)\340S\200\247\201N\002\235\212\\z\320G4\204SqHE&)1F\3321F(\305(\024\340)qF+\313\305:\233\324\323\207\024\340sN\024\341\326\234[<S\201\30585\004\322\216\265(<S\224S\311\342\235o\313\223V$\351Dun3\221V\340\340U\3055:\032\225M<?5j\t{\036\206\245e\364\250\272\366\246\225\246\225\246\2244\302\264\233)|\272k-F\303\025\021\300\246\344QF(\333F\332M\264\273iB\323\302\323\200\247\001O\013F=)q\221JG4\322)1I\212L{Rb\227m&(\305\030\243\024\360)qF+\312\305-8R\232\000\251\000\300\240\236\324\243\2123J\032\236\r(5\"5H\r9\217\024\350\016\01754\215O\214\325\250\210\365\253\221\034U\244j\231Z\245V\245\316\rK\024\234\214V\222r\242\230\340\006\351H\303\212M\224\322\264\322\264\334\000ipOALh\237\322\253:\234\340\320\266\254\3434\326\262n\306\241h\335\017\"\220>:\361O\0074\360)vQ\266\235\262\224-8-8-;\003\246h\372R\201\216\224b\220\212LRm\244\305\033h\305\030\244\305\030\245\3058\n1K\212\362\214\322\212~8\245\002\236\006(&\221FNM8\2650\265&\352xzQ\'\255=^\245\022\nz\276jd \032{\216\364\364n*x\233\201W\242|u\251\325\252tz\225[4\254\330\2536\213\274\346\264\320\342\221\316\347\030\243\024\264\306 S0Z\244[|\363R\254[y\247\272\251N:\325#lY\262EZHF\334T2\302\312x\025]\243\335\324Tmh\030t\252\222@\321\036(G\365\253\n3N\tN\331@J]\264\270\243m\024\270\245\000\032B\264\233i\010\244\305*\216\264\230\240\212LQ\212]\264\340(\305\030\257%=jTZy\342\223v(\r\232w\035\351\245\361\322\243/L\336(\337\336\223\314\315H\032\246\215\207J\221\030d\324\350A\251\224`\324\200\3664\231\332\246\247\203$f\256+p*tj\2305L\217O-\232\275b\3406=kQ\024\036\225\033|\214sK\221Mg\364\241\020\267&\254EnX\360*\372[\205\030\"\234b@9\305FD=\360*\264\262 8Z\256\323m<S\326F\223\036\225(\205Xt\346\206\266\300\371j\264\266\233\263\221Td\262 \344\n\215T\241\346\247A\232\227e.\312M\264\322\264\230\243\024\270\245\333AZM\264\205i6\322\250\000\234\322b\215\264\230\243\024b\235\2121F+\3111\315H\274R\232h\031\247\022\0050\275F\315L\'4\302i\246OJE\220w\247\254\303\326\244YT09\247\254\336c\341x\253h\330\034\032\262\262|\2714\261\311\274\373\324\2238\001GsVm[\367d\032\235\0375*\271\035*d\223&\254+S\303\324\360\273\0021[6\363\344\014\365\253C\347\3523A\210t\002\221m\035\317\3355v\033=\277z\254\210\302\014\360\005C-\317e\252\31730\250\031\232\252\276\342i\2715<R\221S\213\254\034T\3510r0qW\002\006Q\300\346\230\326\201\272U9\3541\310\025I\255\3323\2208\247\'5!^)\205i\245i\245h\013K\266\224-\005i1I\266\215\264\230\306i\002\322\355\244\305&)qKE\030\257&\331\212pZk\373Sz\n\214\234\323\t4\302Oz\211\346\003\201P\371\244\236i\371\310\250\217ZQOZz\261C\221W\"\23389\347\275X3\340\343\265Ii \363y5=\311\314\253\212\263\013`}ju|T\252\364\365|\032\230MV#9\346\256F\330\034\325\353yFEn[H\205GL\325\245e\'\370sR\371\321\240\371\216\343\350)>\322\010\302.\rC$\245\270&\253\021\232M\264\322\225\023\307P\024\240)\024\354q\232\221\t\035+F\336^0\335*\364d\036\225ab\0140\302\240\237O\005IQX\3676\215\021\334\005B\215\232v\332B\264\322\264m\243m.\334Rm\244\333AZM\264\322\274\322\343\024\323\3054\232M\324n\245\315\'>\224\273I\257+lRg\216)\204z\324nj\026`*#%D\362Uwj\214\266)\313%?viA\247\006\305(z\221\037oJ\262\254\n\362y\244Y\2126Gj\320\211\314\307y\364\253L\333UO\255>9sS\253\322\371\230\245\216B^\267\355\255\013@\035O\024\004!\261V\243!:\346\256\3013\266\002\212\324\2066#.j\177\335G\313\234\232O47B\007\2654\234\367\243\024b\227\024\322\200\323\014T\337*\236\261\002)D\035\305H\250E_\266\343\025}\010\317<T\340c\336\253\\[\t\201\342\271\313\270\r\274\247\035(\214\206\034S\212\322\025\244\333N\333HG4\025\246\342\220\2121M\300\250\335\200\250\211,x\247\010\031\251\337g#\255 \217\232\221a8\247yT\206<W\224\025\036\224\323\201P\271\250\035\260*\263\022MF\306\241cQ1\250\215 5\"\276)w\322\356\247\006\247\207\251\222BF\321Ly\n\235\244V\345\236\014JE>v\373\253N\210\360*\33251\337\232\265n\200\200Ml\333]\210\343\nNG\245)\273\313p1Wm\246\014>`\010\255{f\207\031R\006:\324\263_\240\033R\253\t\313\036Nj\302>ju\346\237J\r\007\353J\r\024\340\231\247*S\300\301\251\000\366\251\020\340\325\373e\336G5\243\345qQ:\n\310\324\255<\300N+\017i\205\275\252\302:\260\340\322\221I\200)\t\024\231\024\322\342\2432\016\325\031v=\005&\347\364\246\222\347\265\013\003\261\311\253Q[\205\353S\355\013Ma\236\324\301\037=)\341\016)\014f\217.\274\204\212\211\205B\374UW\3115\013qQ\265D\325\013\na\024\323I\232\\\322\356\245\rO\rOI6\266jI\037p\007\336\266l\234yC\236\224\347\270\005\261\217\306\244\216L\324\302LSD\233\237\025~96\000\005Z\215\213\n\265o\t\221\376n\007z\272\322*\r\211NIXp\rL\256OSVcj\265\033\342\254\244\2250z]\364\273\251U\252@i\353RS\200\006\236\242\237\264\324\360\314b\"\265\355\356\026T\301\373\325#GU\346\2040<V%\345\230\311\300\254\326\264pr\271\024\236T\302\233\344\316{R\213Y\217\\\323\276\303.y\3158i\316z\223S\307`\243\250\315Y\032x#!x\2456 \034\025\250\332\311Gjg\223\216\202\220\300~\224\242\020:\322\024\002\220\250\024\224\323M\257\0365\023\036*\274\207\203U\232\241j\214\364\250\332\243\"\243\"\230E&(\305%\024\340iI\251\003.\337z\277gq\225\353\322\234\322\345\252\344-\305L[\212\215$\303\326\234\r\270\014\326\214X\002\255\tx\302\360)\312jej\231\036\254F\365e\036\247G\251\203\232xzxzP\365\"\266jd5.}\351\312\325(5\"\234\365\245#\232\275g(\215\206k\\:\272|\247\232\211\205A%\272\277QP\265\232\366\000\325w\266\307E\240D\000\345)\2050\331\002\246\t\274r)D\002\203\010ZM\341F\007J\202I\262x\246\371\343\0375D\323F\017\024\217.zTe\252\026j\214\275(9\2434\332\362\006\025\013\212\251)\346\253\275DE0\212a\024\302\264\322\264\302\264\322\264\205i\244SH\305(\245\305C<\276Z\222\016)\332d\345\263\223\326\264\201%\271\253\321>\005J_\212\213v\0335\255k\'\356\324\326\2042f\255!\251\224\323\267\372T\250MXF\305YI\005L\263\001R\254\242\236$\247\t(\022T\311%N\222T\242Z\221^\246G\251\224\346\245Q\2322T\3475b+\355\204\014\326\2147K(\031\353S\343\214\212\214\322\034S\n\003Q\030\2114\360\240S\361\212\2574\300dt\252NI\031\rU\244W\316s\232\202\\\2163LRE;y\0244\231\025\02350\265(zv\354\320My\023Uy\033\322\252?Z\205\2050\2554\2550\2554\2550\2554\212a\024\323L\"\232i\264\352\315\324$\302\220*\326\212\204\214\236\225\253\374uj3S\003\305D\347\006\264\355\030\030\226\264\242\224\001\201V#z\260\255R\241\031\353S\254\201{f\227\314$\344S\326CS\243\023S+\021\326\244\022S\303\323\203T\210\325:\275H\262T\213%X\216J\265\033U\225aMs\236\225]\301\007\"\254\332\335la\232\337\212ex\301\0074\034\032a\024\200\322\346\220\220*3(9\025J\347\'\221T\335\231V\242\016O\336\244`\r7g\025\033\014Td\323\r4\232@h\017\212R\365\344\2621\252\316j\026\031\246\225\246\025\246\221M+NX\201\004\261\305Wu\347\212\210\212a\250\3150\323\t\244\243\265f]\246\351\000\255{\030\374\250F:\325\241\367\252t5:\322\262f\255@\031\000\364\253)!\0075r)\263\336\256F\371\251\343 \036j~\033\356\324\253\220=\351y\0075<s`sN\363A\247\007\247\211)\302Z\221f\307z\224M\232\221d\251U\352x\344\305]\212^*e\222\246S\221Lz\205\362\274\325\375:\364\203\265\217\025\246\263\202z\323\314\312\007Z\210\334(\353Lk\324\025]\356\331\363\216\225\037\236Oz\212IK\036\264\306pG5\0162j@\243\034\322\021\201P\265B\324\323\315Fi\271\244&\232My[\324\014)\273x4\302*2)\244Rb\232\335*&\025\023\n\205\252\0264\302i\244\321JzV|\347\367\2035\253f\013F\t\253\241EH\270\251\227\024\375\352*\304rdd\364\246Ip\027\201V-\244\316+J90\005YG\3175b9MXY)\333\363F\352xjxz]\364y\224\tjT\224\325\210\336\255\306\331\251\324\212\263\033U\205z\231d\305!|\232q\033\326\230\243\3139\025g\355g\030\357A\271\'\2754\316i\245\311\245\016i\333\251\214i\264n\300\342\223\314\315!zal\324Li\264\323\315Fx\246\223M&\274\265\2522\264\230\3050\2550\2550\2554\212c\n\205\252\027\250\036\241ja\244\245\002\234G\025F\346>sW4\351\2066\261\300\025\246\010<\002)y\024\354\232P\244\375*O7h\300\252\345\213\311Zv\274\001Z\010\365z\006\335\305Y\300\035)\003\324\210\331\251\001\247\006\247n\244\337@|\323\3075\"\266*\302I\212\263\034\265a$\253\t-X\216J\225_4\375\325\"59\2153\275<c\024\022(\016)\300\323\267SI\246\223M&\230M4\265!4\231\2444\303Lja\024\323^`E \000R0\310\342\243+\212a\025\033\na\250\232\241j\205\352\006\250\310\246\221M\3058\np\\\324\0271\344b\250\020\361\234\212\261m~\361\261\3632kF\rAX\363W\005\302\205\310\002\241{\242N)V\\\212\236\021\223Z1\014\n\262\215\212\265\004\2705o\315\342\220?5\"\311R\211)\302J]\364\273\350\rS#f\235\272\244F\2531\275XW\251VLT\361KV\222Q\353R\t\005J\262S\367\344Rf\205\223\024\374\203M\350i\333\351w\346\215\324\322i\244\323\t\246\346\2234\271\244\242\242q\212`>\264W\231\225\246\221M\"\230\302\243\"\243e\250\332\242aP\260\250\\T,)\204SH\244\333J\026\244\013\212\216E\315Wh\001\250M\267=(0\024<U\310\203\005\000\232q\214\346\247\211=j\324\177)\253\321\276EN\246\246F\305N$\247\207\315<=J\257\305/\231N\363)C\323\203\324\321\2658\2775,oVQ\252Q%<IR\307.OZ\264\254}jt~9\247\2111R\ti\342L\364\245\316i\341\360(\363)\013\322\206\247n\244\335M&\220\232J(\244\006\202qMnj\"0h\2578+M+L+Q\260\250\330sLaP0\250\330TL*\026\025\021ZaZn\3326\323\325i\032\243\306z\323X`PW\214\323H\311\025\"\234S\267\323\225\352\302\034\325\250\016*\342\232\231N)\341\205<0\247\253T\201\2517S\267\323\325\362je\347\255I\274(\244W\311\251\325\361R\254\2250~)C\346\244W*\302\256\307>@\251\204\331\2453zS\322\\\324\352\365 z]\364\240\320[\024\t1N\017\232]\324\205\251\273\251A\247f\226\233Fr)\244\342\230Ni3^zV\232V\243+Q\262\324L*6\025\023-D\302\243aP\262\324ei\214\264\335\264m\243\024\322\271\244\333\212\215\305+\034\001L\003\234\324\200R\021J\2654mW\"j\266\207\"\234d\305\002\\\324\252\365a\017\024\354\321\232P\325\"6*p\331\034SY\215*\275J$\251VJ\235d\342\236\262`\322\274\26543\347\203S\211H\247\2113S#\342\246\022T\202Zw\235@\233=\351|\312O2\234\262{\323\304\224o\245\017OSO\006\2279\244&\243\'\232B\324\302i7W\020R\243+Le\250\231j&Z\211\226\243e\250\231j&Z\211\226\230V\230R\233\262\223m&\3326\323J\324,\274\323\030f\200\265 \024\021J\0059\001\'\212\267\020\351V\323\245)\024\321\301\251\220\324\352\324\360\324\354\321\232p\353\305N\206\225\31538\247\253T\252\325*\275J\0374\355\331\251\"85d7\024\252\325:\311\305<IN\022\342\227\315\245\363h\023R\211sR\007\247\t)\341\251\352jEj\2245.\3523Q\263S\t\315F\317\212a\222\271r\225\033%D\313Q2\324L\265\023-D\313Q2\324l\265\031Jc-FV\232V\233\266\232E&)\010\250XS6\322\355\247SM\024\36485n6\351V\220\346\224\232eH\271\251T\324\200\323\301\247\212p5 <SK\346\220\032x5*\324\200\324\210jPjDlT\241\251\301\252P\334S\303S\267SK\342\215\364\241\251\352\3252\275<75\"\236j`i\331\251\025\251wQ\277\212\214\266i\204\342\242w\250KV;-DW\332\243t\301\250\231*\026Z\211\226\242d\250\331*&JaJ\215\222\230R\232\313L)L+M+L\"\230V\233\266\223\024\230\246\221F)EH\214E[\211\315N\334\212h\251T\324\213\315H\005H\005;\024S\351\273y\247\001O\002\245Zx\251\024\342\244\006\2245J\255N\335\315L\215\232x4\271\246\226\244\335N\rR+T\212j\302sS(\342\234\033\265\005\251U\351\333\3517R\347\271\250\335\352\026j\210\265Qd\250\331j6N9\353P\262TL\225\023%F\311Q2Tl\225\031J\215\222\230R\243e\246\262\324ei\205i\245i\205i\214\2704\322)\204b\214Rb\235\212U\025:\034T\313\'cO\034\324\212*U\251T\324\200\322\356\245\3158\0323NZx\247\347\024\241\251\301\351|\312<\312\225$\251\224\346\246V\305H\032\224\2654\323i\300\323\301\251\243oZ\266\203\214\324\201\261N\006\224\234\324d\340\323\203f\234\010\035i\035\270\342\240g\250\331\251\205\251\205j\"\231\2462\324,\225\033%D\311Q\262TL\225\033%F\311Q2sL)Q\262S\nTei\205i\245i\205*7^j2\264\322)1F(\305(\025\"\324\212\271\2531\245K\267\024\n\220\032p4\341N\006\227u\004\320\254jA%.\362i\273\311\247\253\023R\014\232xZ\225\026\246\034S\267b\234\257\315)j\004\225 `i\331\245\006\244C\212\266\217\305K\270S|\316iZN8\244\031\357J[h\250\314\246\2173\212c5FZ\2235;&j6J\215\222\242d\250\312Tl\225\023%F\311Q\224\250\331*\"\225\031J\215\222\230\313Q\262S\nS\nSJ\324L\265\033%1\226\223m&\332\n\320\026\236\005J\202\254\245I\232\000\3158\nv)\302\235M4\240\323\205\004\322\203N\006\236\rH\246\245SN\335\212Q%;vi\312i\371\315\000T\200T\213\315?\024\341R\253\340T\201\263N\343\326\234\010\245/\212k8\"\240\335\315.\352Bi\231\2435\242R\243)Q\262TL\224\306J\210\245F\311Q\262Tl\225\033%BR\243e\250\331*2\224\302\225\031ZiZ\214\2550\245F\311Q\272\323v\321\266\215\264\233)\301jE\030\251\024\342\237\232@\3305:\020\325&(\002\226\212a8\243u(4\340i\324\3655\"\324\253N\306h\332i\301H\247\250\247\347\024\241\252e\251\007\255H\2434\360\224\275\005*\265\005\350\3631A\223\"\233\272\233\236i\331\244\3154\320\rm\024\250\312Tl\225\031J\215\222\243d\250\331*6J\211\226\242aQ2Tl\265\033-D\313Q0\246\021L\"\233\266\232R\243)Q\262S6Rm\245\333F\332\002\323\202\323\302\323\266\322\025\247\247\006\254\216E8\n\030T\014H4\204\346\201\326\234*E\024\354S\324T\310*P8\243<\324\213Rb\214SM*\324\240\342\244\00752\n\224P\335\351\203\212k5F\033&\235\232M\324f\234\0334\271\240\322W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\"IDATx^\355\335\333\266\342(\020\000\320\263\234\377\377\344q\315x;G1\367\020\002\324\336Om4\tP\005\001\264\273\177~h\307%=\000\000\000\000\004`G\000\000\000\310\315:\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\345/\013\003\000\000\000\000\324\315>.\000\000\000DdG\000\000\000\000\000\000\000\000\240]~\373\001\000\000\254b\021\001\000\000\000q\\\323\003\204\364\177\036\\\345\002\313\330=\214N\006\000\000t\3114\017\000\242\2623\314\274\213\311bl\022\000b2C\210I\334\001\000\000bJ\326\203v\004[\267r\201\177\013\370\312S\250\232h\002\000\000\000@\\\266\370\001\000X\301\3641\256{\354%\000\000\000\324+\347\277\362\221\363Z\344\347\267\337\214\320s\203\223\000\020\227\376\017\000\221\331%\000\350\226!\036\200\233\322\317\203\322\367#\221\006 }\235\231\315e\000\000\000\200\340\016\336\177\002\000\000\340 \326s\237\242\374\000\"S\3343]\006\000\000\000h\202\235\000\000\000\200p\236\337\241G\371*\2357\266\001\240\t\272*\000\333\230\341C([\273\374\326\363h\335\353?\233\277Zo\004\'\001\242\223\001\301I\200,\314\246\000h\215g\027\000\000\000@\313\354\356\000\000\000S\374(,\2364\346\351k\000\000B0\r\004\010\353\342+\344\310D?8\t\000\000\020\203\255\277\350~3\300\022\000\000\272c\246\307:2\006 \212\333\026\200Q\237\317\034\2601\004\000\315\362\030\007V\353vE\330m\305 \247\367\216\242\323\304!\326\335\261\n\000\000`-s\310\270\304\036\"3\002\000\000DgF\030\214\357\205\203\223\000\020\217\'=\000\360\306\324\240m\342\007\000\360db\024\234\004\240i\333\276\255\333v\326\235\036\323\236#b\266#\205\340|Gt\n\000\200\354rNZ\314\340\033\2645\001\266\2367\355\230\253RJ\215C\200\234\002hC\215\317\020\312\362\314\216N\006\004\367L\000y\020L\032\360\3645\264\3004\026\200\332\205\231c}U\364\353\000\347\021\014\010n|\335\264kx\030\277,\237\216j\251\r\341\033:\345\250\342\0015\032\032\005&\031\"j\366\027\316j\343\264:\343\350\313\241\tp\350\305Y\244\332\241\007z\226\253\343\355\271\316eh\004\23696\3646m\022\313S\355\351\2720+C\202e\270D.F\253\315\036Q\254(\226\034+\355+[B\177;g\313yT\350-\220b\332\274\264{\007P.k\017j\334W\005\376*\222\365F\213/\366\371\301\301-\200\345\336\316.\027\240\266\034\325.k\003\267\366\363\017\327G\361/\267?\354\314\025\010\355\264\356\263q\010z\225\367\275\334\317\361\000\210\313(P7\361\251J\301\'\377\\\3443\025e\3566a\2156L\246\206_\351\25589\013\220\363Zg\030\215\322N\023\355r9\352\236s&\312DnU5\366\2430\037\313\267\267?\347UU\305\3330\030\214\301\203\313\315\237\2763P;O\347\333|\314\026\273G\347~\275#{\375d\016\330!Z-\177\213\215E(\377\235\330\357\370\250<\362\341\370\373\260\322XG\205\355^Yu\371\353\363\217I\301?\333\307\200\3475G\022\366\357pz\203\221\023\342\032i\220\221\303\204\223\366\240\212\205N\332\232\3434X\266\301\203\031\035}\375\316u\333|\335V\354t#-;r\370\347\347\237\364@)\243%\"\261\252\245^_*\255:\351h\203\205\031<\310\200\365-\025z\026\326\201\201\345\372*kN\272\337+G\302\244\327x\177\235\276\307\264{\000s5Z\235?\323/V\250\341\316P\354\366\333\014\027z\205\327\267n\225\327\223#}\007\377\324/c/i\201\316,\014P\232\036\177\256\357\'B\020\022o\204\206i\317\242\230]\236+\277\267\017?W\202\'\016\002\023\267\236x\213E\021\037uo\332\312\332w\2668\373\252\314\237\340-9\233i\255Z\\\261SW\375;,,\366X;,<=\214z\333cc\311\346N\273\275?\367\231V\215%}O\216\036\267\016\276|niq\353\374b\'\217\216\253\266]\232\000[i\3346=\306\303g\364\326\006\361\231<\327\333E\326\236{\214\301ei\372z\314\327\227I\237n\225\374\374\300\324\253Pj\257\372\\\371\026\217\202\237\027\372=m\361\371\375\231k\332>\254\255\345\332\317\323\210\300=}Xo\r\322[}8\214T\201\272\035:\021\233\272\370\324{\034cn@\236{\177\316i\273\243\005\357[\360V\345$\201\337\233\007\257m\263\304\3201j$Rls\037\036\273\034#\013i\275\353\365\036\373\326\343\303N\317\004\350)\017~\353\322S\245\250\305\340b \210\337\252\367\376\\<\303\231y\2253\251\037\227\272\234[\241\r\032+.p4\203B\007\0041<)\000\254d\235\373-c\233\344\\v\316\333R\360\353\343\311\361*g\321\362\226\266\265r[\317k\307\226\314\351[\3771gJ\323\361o\272\360\203\014P\253\364\227\000\265Jfx\265\344iw\t\320]\205*QK\302Nk\243\2244\301P\302\"i\242\224\032\2052\335\'\323ez\244i\356\322\004\207\030\226\016\000\317\317\351(\301I\000\000ZP\366\207\007wK\247T\243N+\362X\311G\217\217\275\001\264\245\374\250CU$\000\363<\362\001\272t\375\367\371C\370e\032~\032\254\250e0\227[\323\274\375\017\022\327\206\243\014l\363\335\355\215\231\000\224\366\3754\002\330\302L\026\240\010\303-\000\205y\364\000\000\204\347\357-\001\000,c\332\004\020\234\357T\000\000\370f\273\200A\022\003\000\200\305\354=\003\000\000\000\000\361\330\031\355\204@\002\000\000\300\371\254\317\001\000\200I\345\376\322\263\345IY\325\265wu\005\nF\373\003\000PX\271\325f\353\252\233\254WW \000\000\000\000\000\000:\344\013E\000\000\000\000\000\250\312\177]O\370\366;\244\233\330\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
new file mode 100644
index 0000000..b04a194
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -0,0 +1,3 @@
+tile_key: "b"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\310\315-\004\346\212(\245\315-\024\003O\006\234\r;4\273\251sFh\335F\352v\374\014S7SKRn\243}(jP\364\340\364\341%<I\357N\022R\371\264\276m;\315\367\245\022S\204\224\3572\227\314\243\314\243\314\251c\2235)l\212Bx\254\373\221\345\266}j5|\323\213SKS\013Rn\240\265\033\2517Q\272\233\272\224\034\325\210\370\024\354\322n\244-\201Q4\300TfqQ\231\3523=!\270\250\332\350Uv\271$\325\243I\232\\\321\2323Fih\315\024S\201\247\003K\232PisK\232Bizu\246\226\246\226\244-I\272\223u.\3527\322\357\245\017K\346R\371\264\242JQ%H\254M<\023N\334iw\032]\364n\243}H\222b\247\022\344u\240\311\216\365\005\313\007_\245T\007\322\235\272\230Z\232Z\233\272\227u\033\2517Rn\240\232\222>\265eM\014@\250ZP;\325y.3\300\250\014\204\323K\323\013\323\013\324l\365\0235D\317\212\326\242\212(\242\2123K\232L\322\203N\315\031\245\315.isK\232\032L\214S7SKRf\223u!jM\324n\243}\033\350\337F\372pzp~j\302?\025&\372M\364o\245\337K\272\223u(z\221d\247\027\310\252\357)\246+f\224\265F[\024\205\2517Q\272\214\322n\245\rAl\232\2323\201Ry\230\250%\237\035\rViI\357Q\226\315&i\t\2463Tl\325\031j\215\232\242c[tRf\214\321\232J(\315.h\315-\031\245\315\031\245\317\275(4n\3054\2654\2654\265&\3527SKRn\244\335I\272\215\324\233\350\337OS\232v\354S\326lT\202l\322\371\236\364\242J\220IK\276\215\324\233\251\301\351\346L\n\202Bv\223H\207\212R\325\03357u\033\250\335F\352M\324\273\250\335R\tp)\217)\250K\023IHM4\232i4\302\325\033\032\214\232\214\232a5\273A\246\321E&h\315%\024\271\2434\264f\2274f\214\321\232ijajn\352M\324\233\251\013R\026\246\226\243u!jizM\364\365\233\024\246\\\322y\224\242Zp\232\244YI\357R\254\224\361%8=.\352pj\035\270\244V\334\244Ss\216)\245\251\205\251\273\250\335J\032\215\324u\245\346\212Bi\271\244\315!4\204\323I\246\023Q\223Q\261\250\311\246\023L&\272\n\r6\212L\322QHh\315\031\244\315(4\271\2434\243\212L\346\202i\205\251\205\251\245\2517f\220\2650\265&\352izM\364\205\351\205\351\273\350\337K\346R\371\224\273\351C\323\226J\235e\315J\036\236\036\234\036\234$\244y:sJ\222\000iX\367\024\302\324\302i\271\245\315.iG4\361J[\212\215\232\2234\204\322f\222\220\232a5\0314\302j64\302j2i\244\327EHi(\246\321E!4\334\321\232L\322\346\214\322\346\202\324\200\322\026\2463S\t\246\026\243<SKS\013SKSKSK\323\013\323w\322\027\244\337F\372<\312]\364\340\364\242J\231%\305L\262\324\213%?\314\364\240=#>M9^\244W\310\347\2654\2674\322h\315-.}i\333\370\342\223u\033\250\244\315!4R\023L&\230MFM0\232a5\0314\323L&\272J\017Jm\006\233E!\246\232JL\321\2323Fh\315\004\320N)\244\324d\323I\3053=\350-Q\226\246\026\246\026\246\226\246\026\246\226\246\026\246\227\244\337I\276\200\364\355\364\273\351|\312Q-H\263\221\336\247I\263R\254\224\340\364o\346\244W\247\253\340\020)7R\356\245\315.h\006\2274f\224Q\232L\321\326\202i\244\323\t\250\311\246\023L&\230M0\232a\246\232\351h\2444\224\323E!\244\246\322\032J)3@4w\244&\232M0\232c\036\324\204\323\t\246\023Q\263Te\251\205\251\205\251\205\351\245\351\205\351\273\350\337I\276\227}.\372<\312]\364\241\352T\223\336\254$\324\363/\245*\311\236jUz\2326\371I\245\315(4\340is\315.is\3158R\346\220\232\000\240\232i4\302i\204\323\t\246\023L&\230i\246\230M0\327OE\024\204b\222\220\212i\244\244\"\230h\244\244<P\017\024\236\364\204\323I\246\023L\246\223L&\243cQ\226\250\331\2526j\214\2650\2650\2750\265&\372iz7\322o\245\337F\372]\364\340\365\"\275H\257R\253f\246CS\003R\253\343\212x4\360isN\006\2279\247\201KFi:\322\346\2234\322i\204\323I\246\032a4\303M4\323Q\232i\353]0\245\242\212LRR\021\212i\246\232a\246\232)\t\243\266(4\323LcQ\223\232Bj64\302j65\023\032\214\232\211\232\243-L-Q\223M&\220\232ijM\324\233\250\335F\372]\324\360\325\"\265J\246\246SS\306\325:\234\014\232\"$\2615`\032p4\360iA\315H\264\354\322\023Fh\315\031\244&\232M4\323\t\246\232a\246\232i\246\032a\246\036\365\324QK\2121IHFi\r4\212i\024\303L4\206\232i\302\203L&\243<\323I\250\330\323\t\250\330\324Lj&5\0335D\306\243&\243&\232M0\232B\324\322\324\334\322f\214\322\346\224\032\221MH\246\245Z\231jelu5 \334\374t\025:|\243\025(4\360qN\0074\360i\333\251wQ\272\2234\271\2434f\232M4\232i\246\232i\246\236\264\303\326\232i\206\232k\247\245\002\226\212)1IM\"\230E0\212a\246\232h\031j\220\323\032\243cQ\261\246\026\250\313Tl\325\033\032\211\215F\306\242f\250\311\250\311\3150\232c\032a4\322i\244\322\023I\232)sN\006\244PML\213\212\235EJ\242\246LT\240\323\303T\212qO\rN\rN\rJ\032\215\364\273\251sJ\r.h\315%6\233M4\206\232i\246\230i\244S\ru\000R\321E\024Rb\220\212a\024\302)\204S\010\305\"\214sJM0\232\211\215BO4\306j\214\265F\315Q3Tl\325\0335DZ\243-L-L-L-L-L&\220\232Bh\315\002\236\005H\270\025 j\221Z\245V\251U\252Uj\2205H\255\212v\372P\364\340\364\340\324\355\364\241\251\300\323\201\245\006\235\2323HM%%!\246\232CM4\303M4\303]E\024\nv)\010\243\024\224\206\232i\204S\010\250\330Rt\246\223Q\261\250]\252\"j2\325\0235F\315Q3Tl\325\0235F\317Q3\324e\351\205\351\245\351\245\251\273\2517Q\270R\027\002\200\364\242J\220=H\255R\253T\252\325*\275<IR\007\305;\314\247\007\247\207\247\007\247\207\247\006\247\203O\006\234\r8\032vh\315\031\243\024b\222\232E!\024\322)\204S\r4\212\3521F)h\242\212)\246\232i\244S\010\246\021\315Fj65\023\032\205\2175\023\032\205\232\242f\250\231\2526j\211\232\242g\250Y\3526z\214\2750\2754\2754\2757}4\312\0057\315\317J\003\323\203S\203T\201\252Uj\221Z\244\017R\007\247\207\247\211)\301\351\341\351\341\351\341\252@\324\365j\221MH\r<\032p4\340ii@\245\243\024b\232E!\024\302)\244S\010\246\221]=\024R\342\222\212(\246\221L4\302*6\3435\0215\033T.x\250\030\324.j\0265\023\032\211\232\242f\250Y\252\026j\211\232\243f\250\313S\013Te\351\205\351\215%G\270\232z\265(l\323\303S\324\324\201\275\352Ea\232x\223\322\236\257R+\032P\3478\024\375\304S\225\352A%H\032\234\034\346\247V\251\024\324\252j@jAO\024\361N\002\234\005(\024\270\244\"\220\212i\024\322)\204SH\246\021]-\024\242\226\212CA4\224\207\2554\323\010\250$<\324MQ1\250\\\324.j\0075\003\032\205\215D\306\242cP\261\250\035\252&lTL\325\031jaz\215\232\243g\246\023M\3159I\251\001\247\255H\r.\352p$\324\203#\255J\204S\214\224\345\222\245\022\003\326\234\007\245<T\252i\370\364\251\025\252u5*\232\225jQR\001O\002\236\0058\n\\R\342\214R\021M\"\232V\232E0\2554\255t4\240R\321E!4\224\231\244\246\223McU\334\345\252&\250X\324\016y\250\234\325w5\003\032\205\215D\306\242f\250]\252\273\265B\306\242cQ\026\246\223Q\226\246\023M\335\353H95*\2169\247\n\220t\251\025sO\333\266\233\346`\361O\014M.\354Q\27352\014\324\350\265a\022\244\333\353J\023\322\237\202)\313\326\247Z\235*u\251\024T\240S\300\247\201N\002\234\005.(\333F\332iZiZiZa\024\322\265\277E\024\334\321Hh&\222\220\232i\2461\252\354y\250\330\324,j\274\206\241sP5B\365\003\324Lj\0275\003\232\201\215D\306\241cQ\023M&\230MFM&iG\006\246S\232pZ\225H\305L\224\366\\\216*\002\2304\340\330\244\335\232\221Fj\304B\256\304\231\251\200\305<.iB\363R\252\344sM)\203R\240\342\246Z\235*e\025*\212\220\nx\024\340)\300S\202\322\355\244\333HV\232V\232V\230V\230V\266\350\240\323h\246\232)3Hi\244\324NqP1\344\324lj&\252\362T-P\265D\302\241aP\270\250\034Uw\025\003T\017Q1\250\311\246\032a\024\302(\301\245\305J\275*E\346\236\006\rJ\247\025(a\212\211\332\241\316M=x\251\343\346\254\307\305\\\215\300\025 l\232\260\203\212SOC\232\221\223\214\322(\305L\242\246AV\024T\252*@\264\360\264\360\264\360\264\340\264\273i\n\322m\246\225\246\225\246\025\246\025\255|RQHz\322\032m!\244\2444\302j\'<T\016pj65\023\032\205\352&\250Z\243aQ0\250\\Uw\025\003\212\256\342\253\270\250\231j\"\264\335\264\205i\002d\323\304x\245\362\351v\323\325qO\333K\332\230X\212o&\234\006)GZ\265\020\251~\225b%&\255\"\342\246\007\002\234\243uXH\352m\234Sv\324\212\265*\n\260\202\246U\251B\323\302\323\302\323\302\323\266\321\266\215\264\233i\245i\245i\205i\205kO\024\204RR\032Jm!\244\246\232a\250^\241z\214\324MQ\265B\325\033TMQ\232\205\205@\342\240qU\335*&J\215\2435\023Di\236Y\357Hc4\2011N\000\367\245\002\224\nz\2558\256ivf\223\312\2441\342\230i\312\271\2531-YT\315Z\215@\251@\251V2j\314Q\342\255\004\000P\027\232\177\223\306E*\307O\013\212\231\026\254*\324\241i\341i\341i\341iv\322\355\243m&\332B\264\322\265\031ZiZ\277M\2444\230\246\221Hi\246\222\230i\215Q=B\342\242j\214\324mQ5F\325\023\n\215\205D\374\032\205\306j\022\264\206,\216j3\020\035\005D\321f\243x\261P\025\305&\332B\224\233iB\322\204\247\205\247\205\245\002\206\342\241v\250\361\223S\306\265:\220*d5e\rX\214d\325\330\323\212\231V\245U\342\227o5<c\326\234W\024\201jdZ\260\253R\205\247\205\247\205\247\205\247m\243m\033i6\322m\246\225\246\025\246\025\253dRSM6\220\322SH\246\323Z\243j\215\273\324M\322\242aQ\021Q\260\250\330TL)\214*&\025\013\324{\t\240\246)\205i\276]4\245E\"qUY2i\2451M\305&\332]\264\241i\301i\333i\002\320W\212\201\322\232\253S\242\324\351\0215f8qRc\025<\0035\241\030\300\251\325sS\250\247\204\315H\251N\307\024\212\2715aW\0252\255J\253O\013O\013O\013N\013F\3326\322m\244\333HV\230V\243e\251\373\320E0\212B)\010\246\221M\"\232E4\212c\n\215\205B\325\021\250\330TdS\010\250\330TMQ0\250\266n5(@\242\241q\223L\331K\266\242q\212\201\305W\"\243aL\331K\266\227m(Z~\332\002\323\212TL1Q\260\315\013\0375f8\352\322\000*a\216\324\205sV-\327\006\264\020T\350*U\025:\255<\n\220G\271iV<S\302\346\246E\251@\247\252\323\302\323\302\323\266\321\266\215\264\233i6\322\025\246\025\250\312\324\204z\322\032i\244\246\342\220\212n)\244SM1\252&\025\013\n\215\205FEFE0\212\215\252&\025\031\024*c\232k\nn\314\320S\024\302*\027\025\003\255B\313Q\225\244\333I\266\234\022\227e<&i\342*y\217\212\201\342\246\010\252E\212\246T\247\2044\365CR\355\251#\030\253h\3252\275Z\213\232\265\032\346\246\020\232\231#\300\240\2474\241qR\001R*\346\245\013R\005\247\005\247b\227m&\3326\322m\246\225\246\025\2462\320E4\212a\024\224Si\244b\220\361Q\232cTl*6\025\023\n\214\212\214\212c\n\210\212\215\205\"\246\346\251\0311\232\204\2574b\230\302\243e\250Yj&Z\211\226\243)I\345\322\354\245\tR\2549\251\026\034S\366`S\010\244\362\363M1\342\224%H\221\022j\312C\221R\255\267\265I\366ojQm\216\324\361\021\251\022#\232\275\024\\\n\273\004\\\325\321\030\305/\227Ha$dTe1NU\251\225jP\264\365ZxZv\337Z]\264\233h\333HV\232V\232V\243+Qb\220\323H\244\246\323i\r4\323M0\212\215\205F\302\242aQ\260\246\021Q\260\250\310\246\025\247F\270\247\025\250\231)\230\2468\250\215F\302\243+\232\215\222\233\266\223m(\214\232zE\315ZH\270\240\250\024\322*\"\274\323\325i\nd\324\211\025N\221b\254\307\035YH\200\251\2260jO \021\322\233\366nzT\211m\203\322\255$X\025*\361S\246MJ\005H\203\"\242t\346\220%J\253R\252\324\201i\341iB\321\266\215\264m\244+M+L+L+U\351\010\246\221M\"\232i\246\232i\206\233F)\214*&\025\031\024\306\025\031Z\215\226\243\"\2435\"/\002\244\333Q\262\324%i\214\265\036\312\215\222\231\262\220\2453\313\245\021T\213\020\251\004x\247\355\2462\324dSv\344\324\251\036jA\016jd\207\0252\303S\244X\251B\032\22649\253h\234S\304U:CNh\261Dp\222j\332\333\340t\250\331pjh\327\212\215\226\220/5\"\255L\253R\005\245\013K\266\227m\033h\333M+M+M+L+T\361F)\244SH\246\021M\"\223\024\326Zn)\010\2460\250\310\2460\250\312\323Yx\315D\302\243e\250\314D\236\265(B)\300R2\361P\025\250\331j<Rl\315!JB\224\337.\234#\245\013\212~))\215Q\021\232UJ\263\032U\224\2175*\305Vc\213\332\246\021S\2045*E\212\235R\245H\352\312G\201N\362\362jx\240\002\244u\300\252\254\274\323\324`R\025\246\355\346\244U\251\225j@\264\241iv\321\266\227m&\332B\264\322\264\302\264\302\265G\024b\220\212i\024\302)\244SqHE0\322c\212a\024\302*2)\245i\2733Q\025\244\t\223La\264\342\244\333\3057o42\361P\225\246\024\315\'\223I\263\024\306Z\214\212P)M%!\246Q\214\322\210\352E\212\254G\035ZH\352\314qf\255$5*\303O\021R\210\361R\254u2&*`\265\"\'5aW\002\230\353\232\204\245.\332M\264l\247\252\324\212\265 Zv\3326\322\355\243m!ZB\264\322\264\302)\205k?\024b\220\323H\246\021M\"\233\212\010\250\310\244\3054\212\214\2557\024\322)\204SJ\323v\323\014yjy\030\030\246\201JW\212\211\226\230\026\203\214TLj6\250\361F1H\0015 N)\254\265\021\030\245Q\232\260\221\346\254$5:\307\212\235#\315[\212:\270\221\324\242:]\224\361\030\247*T\241)\352\2252GRc\024\326Zf\312M\224l\243m8-=V\236\005;\024\273h\305\030\244\333HV\232V\230E0\255f\342\222\220\212i\031\244\"\230E7\024\204S\010\243\024\302)\245j2)1HV\233\266\233\266\225S\223H\313\3157m\005j6Zc-@\347\025\021\250\230\322\016i\304S\220\n\221\230\001P3f\230FjX\243\315\\\216<U\224Z\231S5f8\252\334q\212\262\211S\254|S\035v\322)\251\000\251TT\250\234\325\205N)\n\342\220\255&\332n\332\002\363K\262\227e(Z\220\n]\264\270\243m.\332B\264\322\264\322\264\302)\214\265\225\2121I\212LSH\246\221M\"\223\024\322\264\335\264\204S\010\246\025\244\333I\266\223m7o5 L\niJn\312\nT.\2705\033t\252\3169\250XTei1K\214\323\225M)\214\232A\001\251\004\0252E\212\262\211R\205\305K\020\346\256 \342\254GVR\246\007\002\243\220f\242PA\253(3S\252\324\310\265:\216(\"\231\266\214S@\245T\251\004y\247\210\263M1b\232\026\234\0058-.\3326\322\025\244+L+L\"\243aYX\346\223\024\230\244\"\223\024\322)\245i6\322b\232V\232V\232V\233\266\215\264l\246\224\244\331\315H\023\"\232\311HR\233\266\232\321f\252J\230\252\314*\026Zi\025\0369\251\2213R\252\n\235c\024\024\024\004\024\270\305I\035ZT\310\247*`\325\210\305[\215ju\247\n~\320EF\311\203O\214\342\254\241\251T\324\300\321\232\\f\232\374p)\025x\247\252\340\324\312\265(Z\0313P\262`\322\005\311\245\000\347\024\360\264m\244\333M+L+L+Q\262\326F(\305&)1HE!\024\233i\245i\245i\010\246\225\246\225\243m\033)Lt\335\224\276VjA\036\0055\243\246\224\250\366sJW\002\251N:\3257Z\201\205&\334\322yx4\365\030\251\024f\246Piv\223K\214R\355\315*\2575z\001\220*\307\227OH\361V\024b\236)\342\2363K\264\232@\270\251V\244S\315YS\221F)\340`SB\344\344\324\212\264\355\265*\212\224\n]\264\307L\212\213f)\3120y\247\355\346\215\264\205i\205i\205i\204Tl+\037\024b\223\024\230\366\244\305\033i6\322\025\246\225\246\225\244+I\262\200\224\355\224\273)\273*E\217\212pJC\036j6\212\231\345\342\243\227\201Y\362\214\232\254\353P\224\247*P\311L\3075*T\312E8\221\212i\346\225EJ\211\223W\240\217\025mR\244\013\212x\024\241jEL\324\311\0259\223\024\300\2315\"\305S,5*\304E=c\251V>\016j=\2704\365\024\375\264\345Z\231V\237\267\212M\264\315\234\323\n\342\244\333\362\203AZaZiZaZ\215\205F\302\261\261F)\270\243\024\230\245\305!\024\230\244\"\232V\223m\033iBS\202\322\354\245X\362jA\035;\313\243\313\250\335@\252\322\034UII5U\320\232\201\2434\317.\232W\024\021\221L\331\315<Fi\353\031\247\2244\251\021&\254,<T\311\026\rZE\000T\302\237R*\323\302T\350\2252\256)\031sO\216,\324\342*\262\221\002)\306,Rm\305!<`S6f\234\027\024\340)\300T\250*R\0061M\333J\0274yt\246<-0\212iZaZ\215\205F\302\243aX\300Q\212B)\247\212i4\224s\351Hr(\006\227\031\245\331J\026\234\022\227e8%H\261\324\201)\257\305B\317P;f\240u\315B\321\324m\025B\321\324f:\212H\361L\013\232<\263\232z\256*E\025(\214R\205\000\324\212EJ\274\324\312*E\025*\214\325\204Z\224-J\242\236\0058.jdZ\235W5b4\342\225\226\230\313P\343$\342\236\005.)B\322\343\024\365\342\2369\247\342\225EH\026\224\247\025\013/&\230E1\205F\302\242aQ\262\3268Z1Q\265\'\226Z\244X\t\352)\342\000)Lb\2436\340\3645\004\260\2249\024\211\351S*\346\237\262\224%8%8GS,t\245p*\274\2435]\226\241\220\005\025_\314\346\224\020z\320\312*\026Ni\236^j)b\342\253\252`\324\342,\212C\r\013\021\025(\030\024\335\244\323\225\rX\215\rN\026\244QS\"\325\205Z\225V\245T\251\002S\325*eJ\231R\254\"\361C\n\215\207\025\010\0304\354Q\212p\024\340\264\354S\220sR\020\0059EH\203&\236W\025\023\256~\265\t\025\033\n\211\205F\302\243aX\324c4\242.y\251\200P0\005\033I\351A\030\035j2i\t\305!\001\3075\031\267\364\247F9\301\353S\005\247\205\245\331\212@~lU\205Zd\202\2532\346\242\223\n*\224\274\325r\244\032L\322\026\"\243g4$\274\363S\262\007Z\254a\301\247\205\300\247*\344\324\242.)\246,R\010\375\252d\216\246\010\005;mH\261\346\247H\352\302\307\305=W\025:-H\022\235\214T\211S\245Y@1Lu\246\021\305BW\232\\R\355\245\003\024\360)H\342\235\030\317J~9\245\305H\215\203R1\365\2460\250\030sQ\260\250\330TL*6\025\214\027\361\251Q=i\342<\363I\267\007\245\014\307\030\250Y\252\"rh\353R\'\002\244\002\232@\317J\225E<\n\\S#\\\271\253J\274Tn*\007\030\025J^M@V\242t\250Yi\204TL\264\314\02552\271\3059>c\203S\371y\024\320\2305f5\004S\214Y\240AO\021b\236#\245\021\324\250\230\251\221j\302\247\024\204`\324\321\221R\323s\223S\306\001\024\376\2254oRu\246?J\213\024\001K\212\\P)\325,k\201Hz\323\2513\203S!\336>\224\036\265\033\016j&\025\023\n\215\205B\302\263\025*M\240\n:\212a5\033\232\205\2174\312z\322\223\216i\301\370\342\234\274\324\252\265(ZR\274S!\341\315Y\003\212\216A\200MRv\'5\013.j\"\265\023\255DV\243d\250\212\323\nf\200\270\247\257\006\256Dw\n$\\sI\023`\342\257F\273\205K\260\n<\274\323\204F\234\"5\"\305O\n\027\255J\035@\250$q\232X\344\251\374\314\212n\376j\304r\020)\373\362jd\247\356\244\334qL\357O\024\242\234\005.)@\251\001\3055\251i\010\247+\025\351S*\356\346\221\222\242d\342\240qP\260\250\330U\000\270\240\340Td\322\032\205\315B{\323i\340P\307\212E<U\204\025:\212~8\245\347\024\221\240\337\232\230\374\242\243s\221U\0359\246\024\250\331*&Z\210\255F\313P\262\3236\322\354\243mM\017\006\255\024\334\265\017\225\264\325\230\344\332*_754L;\325\215\353@\221hi@\351P<\271\351M\014\306\234\020\236\265*\307\212\220%<GR\252\201\326\234\000\365\251\024\343\245J\017\024\204\322\nx\247S\200\247b\227\024\264\032@9\305-8\n\236#\306)\314*\'\025Y\305B\302\242aT\rF\306\243cLg\342\241&\232i@\247\342\232\343\"\210\200<U\225\\qR\255<\032ZU\340\346\234[ \324f\230\302\242e\250\231j&Z\210\212c-D\311L+I\217j6\323\227\203\305Z\211\370\301\251\366\006\246\371T\341\035H\024\216\224\034\322\000\324\340\204\323\304t\274\npjQ!\025\"K\353R\356\310\310\245\334M9sS(\342\236)\373sJ\006)@\247\201N\002\234\005;\024b\227\024\322)@\247/\314i\377\000t\361O\316E1\371\025Y\352\026\250\232\263\230\324Lj\026j\214\234\323M&9\247\216\00586i\257\351P\243lz\274\247#5 jpjZp4\200\201\301\247\210\375i\256\230\250H\250\330Tl*&\025\021\024\302)\204Sv\321\266\227\024\345\342\246Y\rI\346R\211i\302Zp\2234\360\364\340\374Q\346\032n\354\321\232PsOZ\221IS\355S\251\317J\225EL\265 \024\340)\330\245\002\234\0058\nx\024\340(\305\024\204Q\212T8jRrh\017\212Fj\201\315@\324\306\254\206j\211\232\242c\232BqM\242\236)qM5\024\243\034\323\240\270\354j\322I\232\2247\255;9\247\nF\007\250\247\307q\3742\017\306\236\314\276\265\001\031\246\260\250\231j&Z\215\226\243+M+L\"\214Q\2121N\024\360)v\323\200\247\201R\001O\035(\002\214S\266\322\205\247\201R\001OPGJ\235:T\353R\250\247\201N\002\234\026\234\026\234\026\234\026\234\005.\3326SJ\321\212n(5\031\342\230Z\230y\250\3150\326\031j\215\2156\220\232J3J\r8\032By\250\2449\025\014C\347\253\253\362\324\352jA\355N\006\235\232f\321\232v\320p{\322\201\305#.FEF\313Q\262\324L\265\031Za\024\302\264\334R\342\214R\201O\002\234\005=P\323\302\323\200\247\250\247\205\030\240\256)@\247\001N\002\244\002\236\005H\243\0252\324\313R-<\nx\024\340\264\340\264\354T\2423\212i\\Q\212n)\n\323J\323H\250\315F\302\243\246\032i\256|\232a4\332BqH[\024\302\364\241\351\333\250\r\232k\232\205N\032\255+\344T\361\265J\017\024\360iA\247u\245\035)\312)\330\3151\226\243e\364\250\231j&Z\214\2550\212n)1K\212P)\300S\224T\352)\373h\333J\005<\n~(\331J\026\236\0058\n\221EH\005H\242\245Z\221jP*@)\340T\201iv\340\212pbh#\024\230\244\"\232E4\212k\n\211\205D\302\243\"\230i\206\271\322i\244\322TO%D_4\205\351\003\342\235\346P$\346\234[5\033t\342\235n\371m\254j\370\030\251\025\251\340\323\3058S\324S\205<R5V\226P\247\024\335\331\355HT\032\215\226\242+M\333M\333K\266\227\024\001N\025\"\234T\201\251\302\234\005H\253O\013N\333K\266\224\nx\025\"\212x\024\345\251\205H\242\246QR(\251Uj@\270\245\003\232VQ\214\212i\311\034\322Rb\223\024\204S\010\250\331j\026Z\211\205FEFk\234\244<T.\335\205Ws\3150\2750\275&\372\013R\253\363\315J\257RpED\312U\262\265b+\223\300z\270\2370\342\245\034S\325\252AO\034\323\305>\232\325\237u\301\247\301\312\214\324\333\007jk!\250\231)\205)6\322m\244\333F\3321N\247\212\221MH*E\251\005-8R\201N\003\322\234*AR(\251\005H\2652\324\350*eZ~\332\220\240\t\232\217\024\302)1F)\r!\024\322)\214*\027\025\013-D\302\242a\\\331\353Lv\300\252\254\325\0136j3M&\233\2327S\263R+T\310sO\333L+\203\232\225.\031:U\313k\201\'\r\326\257\252\251\035\205\007`\007\232\024\203\322\244\006\224\265C$\241G\275R\226O9\300\035\005Y\215p\005J)OJaZaZaZiZM\264m\244\305.)@\247\212x5\"\232\22058\032vi\302\236:S\2058T\253R\255J\242\245Z\235\rN\206\245\003=($\3644\303L4\224Q\214\323\274\2763Q\021Lj\211\205B\302\242aP\260\256d\232\2573UGja4\302i\204\323I\244\31585H\246\246\215\271\253jF)\255\3157fjH\243 \344U\261+\001\201Mb\344pi\251<\221q\326\245K\341\2347\024\367\277\215W\206\346\251\231\344\270l(\300\253pC\264s\326\254\201\212Z\0014\204\2323\352(\3004\322\264\233(\331I\345\321\262\215\224\270\245\002\226\236\016)\352i\342\245QR\001N\013\351O\002\236\0075\"\324\212je5*\232\235MH\255\212R\324\302i\231\346\226\212T\344\325\223\235\234UV\0305\031\025\023\n\205\205D\325\013\n\345\\\340U)\033&\240&\230M4\234\323M4\322P*E\251\220\324\353%.\374\232\225Njej\225\006j\\\niAPI\000jbY\202y\253\321@\2508\025>1K\364\240\nZ1\232]\264\340\202\227`\240\306)\273\005&\3126\322l\3154\255\030\305\030\315.)\352*U\025*\212\225V\244QO\013\232v\302)\3123O\003\024\365\251T\324\252\325 j\\\322SM(4\3602)\007\312r*_;#\232\205\216M0\323\030T\016*\026\025\013\n\343fz\250\315\232\214\232a4\224\206\232i)@\247\003OSR\203J\t\006\247F\251\224\214\325\204z\225H\247\022(\306i\350\2650\030\034R\321\214\322\205\247b\214R\201N\002\234\005.)\010\244\305&\3326\322\024\246\354>\224m=\305.\332r\255H\253R\250\251TT\252\265*\257\2558\014PW\373\264\003\353O\003\322\234)\340\323\324\323\203R\203A\244\006\244CO#5\031\244=i\244S\032\242a\232\205\205D\302\270)\0375\t4\302i\264R\023M4\224\023@4\3655\"\265J\246\244Z\231ML\206\246\006\236\274\324\300qO\035)\324\352v)@\247b\224\np\024\270\247\001K\212\000\245\332)\245Gj\002\203K\264\nLz\322\021\352(\013N\tO\013R*\324\252\265\"\373S\305;u\031\346\203\203\365\241\01685(\347\2458S\300\245\242\235\326\234\200g\2321\203\305;4\322i3Hi\206\243j\211\205B\302\274\355\215FM4\232m\031\244\315!4\334\321@\247\212z\232\231\rL\265(\036\225\"\261\251\326\246Z\220\032x\247\212p\024\361N\024\340)\300S\261@\024\354R\342\212Bh\244\2434\023FE\002\244\002\236\277J\220})\340\372\323\263F\352\\\322\346\224\032p9\353N\\\212\231H>\306\236\005?\024\252\007~izg\024\001A\024\224\206\222\220\232i\246\032\215\207\245D\302\274\321\2150\232i4\231\367\244\315!ji4\231\245\006\234)\342\236\265*\n\235je\251\224T\252*E\251\224T\200S\200\247\201O\002\236\005.)pi\324\240R\321Hi)3F\352JL\322\322\203N\r\212\221\\T\201\251wR\203N\315(4\271\245\315(jz\265J\0105*\277@\177:\224\032x\366\245\002\220\320y\024\322\010\246\322u\244\244\"\230E4\212k.k\313\t\246\223M&\232M!4\231\244\242\234\0058T\200T\212*e\0252\n\231EL\242\246QR\001R(\251\005<\nx\024\360)\340R\212ZZ3Fi3Fi\t\246\223I\2328\307\275\007\2123\212\001\247\003\232^\247\212\220\034S\303S\203S\201\247Q\232\\\321\232p8\247\253\324\253&jU\223\037J\225^\244\017\232RE&\352BsM4\235\250\372\323M\030\246\342\232Ey94\204\323i\264\332\r\024\240S\300\247\201R(\251\024T\310\265:-L\242\245QR\250\251\024T\252*@)\340S\300\247\001O\002\227\024b\203HM4\232L\322\023I\232L\321\272\214\323\272\320\t\034v\241\207qJ\275)\303\245<\021J\r<S\201\247R\216\264\264R\346\200i\301\252E\222\245W\251\003\323\304\236\264\273\2517Q\272\214\363\305\005\251\013Rn\244$SI\025\344\331\244\244&\232M%\024\240S\300\247\001R\001R(\251\225jeZ\225EL\242\245QR(\251TT\212*@*@)\340S\261K\214t\245\006\203M&\232i\244\322f\233\232\t\244\2434\231\247\003N\004\366\247\014\343\236iW\234\346\227\214P)\300\323\305<S\2058R\321E\024S\263N\r\212\221^\236\036\224=.\3727R\357\245\335I\272\232M!jaj\362\274\322\023M&\222\212P)\300S\300\247\201R(\251\024T\352*U\025*\212\225EJ\242\245QR\250\251\000\247\201O\002\236)sE\031\246\2264\026\3154\232L\323sIE!4\206\200sJ\r=i\331\305\024\2434\341N\247\003O\006\236\r8\032Z)GJR(\244\"\200is\212P\364\355\364\273\350\337J\036\227u\033\350\335A4\302k\313(\'\024\332)E-<\nx\024\360*E\0252\255L\242\245QR\250\251\024T\312*U\025\"\324\202\236\005>\2274\231\245\315&i3IHE4\212L\322f\215\302\223\"\223\255\030\305/\326\224\023N\025 \245\247R\212u8\032p4\340isN\024\242\224\n\\PE2\227&\214PM&qF\352\003R\357\240\275 zx|\322\023_\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003,IDATx^\355\335\335r\263 \020\000\320\214\357\377\314\231\357\247^\264:QQ(\260{\316]\2155\215\254\013,\306\276^\324\360\336n\000\000\000\000\242R\006\000\000\200r\306\321P`\331n\210@\026\270+d8\000\000\000\000\300\240\324\343\000\000fb\035\032\000\000\000 \253\307\353zJK\220\316\343\274\001\000\000\000\000@\037\n\274@\t9#\261\326\215\357F\003\000\000\000\"2\337\375\311\371\000\230C\353z0\000\320\231\311\331=\261\007I\261?\035\000\000\000\337\230\002\002\000\000@\036ow\010\000@J\326\002 )\343\377\334\264?\000\000\000\300\324\324\366\001\000\000 0\367u\000\000\000\000\000\000\000\300)\313\353y\371N\00506Y*=!\000\000\211x\252?\000\000\000\307L\034\271\302\322\002\000\000\000\000\000\000\000\000\000\000\000\304\347\373\003\000\000\000\000\000\323Q\332\005\000\200\346\026\343n\000\000\000\000\000\000\000\306gu\033\000\000\000\000\000\000\000\000\350\316\215L\000\000$b\370\233\331{\273\001\000\000\000\000\000\022\361\317|\000V\026\0169\262\215\217\355\317\3718\003\000\000\000\000\000\000\201Y\014\312\300MC\214\357N\224\272\037\016\000\000\000Z0\341\006\000\000\000\202Q\356\000\000\000\000\210F\305\007\000\000(\342\271\032\344 \322\001\000\000B3\355\003\370D\206\004\000\200\021\271\343\233j\004\023\3004\244l\000\000\332\263>\014\000\000\220K\257\325\207\306\357kz\013<\3208C\001\000\300\2573\306\005\000\000\340\216O\363\311\375r\354\317-\333\337\333\357?\276\345\257\355\266i\254\'\374\376\007(j\261\363\2679\337\003\310K\206\000\350\257h\360\307\320\264e \006I1]\276Hk\004@\215cp\317\345\206\006\000\000\000xB\021\002\200\036\364?\311\t\000\340\036\353\327\305$\\\000f\243\273\007\000\010\242h`W\2643\000\014M\257\266\223k\251B\000\000_r\345\276\007\244\315\272v\201\267\333P\231\006\344\221\377\001\324:J\031\334[\004\260\022\n\311\t\000\200tL\'!%\227>\000[\372\006 \275\2654\232\257B\252\007\000\000\000\000\000RQ\024\005HNG\000\337\344[ \377]\303\236\337\257?\314\327I\233\321\325\000\000\00000\005\201\030\224\037\000\370Hg\237\233\366\207C\023_\"\2139\000\'&\016o\036\221\034r8\270\302\017^b<\325\233\253\372\001\211L\2171\213\253\027\266\026\315\355j\234\000\323\223\356os\352\206\246#\003\000\200r\2469\311M\033\000\255g\200N\0140\245i\223\027Uh\177`X\022TX\0361GWM\002\260\3051y\350\270\0279~\025\200n\364\251\000\3008L\035\353i5\312ku\\xI\001\2759\377\364!\362\206\241\217ON\000\000\000P\235\t\037+\217\320\316N\000d\367/\002N\352\016\313\351\036\244 \n !\027>@R:\000\000\200F\024\3449d$\016\000\361U\350\357+\034\002\200N\344p\000\000\000\000\000\000\000\000\000\000\000\006\362\007Wv]\t\272\351Z\n\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6aea32f..39012ae 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6407,4 +6407,22 @@
 
     <!-- Display content to tell the user the sim card name and number-->
     <string name="default_card_name">CARD <xliff:g id="cardNumber" example="1">%d</xliff:g></string>
+
+    <!-- Strings for CompanionDeviceManager -->
+    <!-- Title of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permlab_companionProfileWatch">Companion Watch profile permission to manage watches</string>
+    <!-- Description of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_companionProfileWatch">Allows a companion app to manage watches.</string>
+    <!-- Title of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_observeCompanionDevicePresence">Observe companion device presence</string>
+    <!-- Description of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_observeCompanionDevicePresence">Allows a companion app to observe companion device presence when the devices are nearby or far-away.</string>
+    <!-- Title of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_deliverCompanionMessages">Deliver companion messages</string>
+    <!-- Description of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_deliverCompanionMessages">Allows a companion app to deliver companion messages to other devices.</string>
+    <!-- Title of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permlab_startForegroundServicesFromBackground">Start foreground services from background</string>
+    <!-- Description of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permdesc_startForegroundServicesFromBackground">Allows a companion app to start foreground services from background.</string>
 </resources>
diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java
index 52aafeb..7fd199a 100644
--- a/core/tests/fuzzers/FuzzService/FuzzBinder.java
+++ b/core/tests/fuzzers/FuzzService/FuzzBinder.java
@@ -34,12 +34,12 @@
         fuzzServiceInternal(binder, data);
     }
 
-    // This API creates random parcel object
-    public static void createRandomParcel(Parcel parcel, byte[] data) {
-        getRandomParcel(parcel, data);
+    // This API fills parcel object
+    public static void fillRandomParcel(Parcel parcel, byte[] data) {
+        fillParcelInternal(parcel, data);
     }
 
     private static native void fuzzServiceInternal(IBinder binder, byte[] data);
-    private static native void getRandomParcel(Parcel parcel, byte[] data);
+    private static native void fillParcelInternal(Parcel parcel, byte[] data);
     private static native int registerNatives();
 }
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
index dbeae87..264aa5f 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
@@ -38,7 +38,7 @@
     return registerFrameworkNatives(env);
 }
 
-JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
     size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
     uint8_t data[len];
     env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
index bc18b2f..c96354a 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.h
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
@@ -24,5 +24,5 @@
     // Function from AndroidRuntime
     jint registerFrameworkNatives(JNIEnv* env);
 
-    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
+    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
 }
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
new file mode 100644
index 0000000..b71a06e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_fuzz {
+    name: "java_binder_parcel_fuzzer",
+    srcs: [
+        "ParcelFuzzer.java",
+        "ReadUtils.java",
+        "FuzzUtils.java",
+        "FuzzOperation.java",
+        "ReadOperation.java",
+        ":framework-core-sources-for-fuzzers",
+    ],
+    static_libs: [
+        "jazzer",
+        "random_parcel_lib",
+        "binderReadParcelIface-java",
+    ],
+    jni_libs: [
+        "librandom_parcel_jni",
+        "libc++",
+        "libandroid_runtime",
+    ],
+    libs: [
+        "framework",
+        "unsupportedappusage",
+        "ext",
+        "framework-res",
+    ],
+    native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
new file mode 100644
index 0000000..033231d
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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 parcelfuzzer;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface FuzzOperation {
+    void doFuzz(FuzzedDataProvider data);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
new file mode 100644
index 0000000..5e9e5ec
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class FuzzUtils {
+    public static FuzzOperation[] FUZZ_OPERATIONS =
+            new FuzzOperation[] {
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Append
+                        int start = provider.consumeInt();
+                        int len = provider.consumeInt();
+                        Parcel p1 = null;
+                        Parcel p2 = null;
+
+                        try {
+                            p1 = Parcel.obtain();
+                            p2 = Parcel.obtain();
+
+                            byte[] data =
+                                    provider.consumeBytes(
+                                            provider.consumeInt(0, provider.remainingBytes()));
+                            FuzzBinder.fillRandomParcel(p1, data);
+                            FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
+
+                            p1.appendFrom(p2, start, len);
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            p1.recycle();
+                            p2.recycle();
+                        }
+                    }
+                },
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Read
+                        // Use maximum bytes to generate read instructions and remaining for parcel
+                        // creation
+                        int maxParcelBytes = provider.remainingBytes() / 3;
+                        byte[] data = provider.consumeBytes(maxParcelBytes);
+                        Parcel randomParcel = null;
+
+                        try {
+                            randomParcel = Parcel.obtain();
+                            FuzzBinder.fillRandomParcel(randomParcel, data);
+
+                            while (provider.remainingBytes() > 0) {
+                                provider.pickValue(ReadUtils.READ_OPERATIONS)
+                                        .readParcel(randomParcel, provider);
+                            }
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            randomParcel.recycle();
+                        }
+                    }
+                },
+            };
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
new file mode 100644
index 0000000..688c812
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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 parcelfuzzer;
+
+import android.util.Log;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ParcelFuzzer {
+
+    static {
+        // Initialize JNI dependencies
+        FuzzBinder.init();
+    }
+
+    public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
+        // Default behavior for Java APIs is to throw RuntimeException.
+        // We need to fuzz to detect other problems which are not handled explicitly.
+        // TODO(b/150808347): Change known API exceptions to subclass of
+        // RuntimeExceptions and catch those only.
+        try {
+            provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
+        } catch (RuntimeException e) {
+            Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
+        }
+    }
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
new file mode 100644
index 0000000..5c227e3
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface ReadOperation {
+    void readParcel(Parcel parcel, FuzzedDataProvider provider);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
new file mode 100644
index 0000000..0eff5f2
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2022 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 parcelfuzzer;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import parcelables.EmptyParcelable;
+import parcelables.GenericDataParcelable;
+import parcelables.SingleDataParcelable;
+
+public class ReadUtils {
+    public static int MAX_LEN = 1000000;
+    public static int MIN_LEN = 0;
+
+    private static class SomeParcelable implements Parcelable {
+        private final int mValue;
+
+        private SomeParcelable(Parcel in) {
+            this.mValue = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mValue);
+        }
+
+        public static Parcelable.Creator<SomeParcelable> CREATOR =
+                new Parcelable.Creator<SomeParcelable>() {
+
+                    @Override
+                    public SomeParcelable createFromParcel(Parcel source) {
+                        return new SomeParcelable(source);
+                    }
+
+                    @Override
+                    public SomeParcelable[] newArray(int size) {
+                        return new SomeParcelable[size];
+                    }
+                };
+    }
+
+    private static class TestClassLoader extends ClassLoader {
+        TestClassLoader() {
+            super();
+        }
+    }
+
+    private static class TestInterface implements IInterface {
+        public Binder binder;
+        private static final String DESCRIPTOR = "TestInterface";
+
+        TestInterface() {
+            binder = new Binder();
+            binder.attachInterface(this, DESCRIPTOR);
+        }
+
+        public IBinder asBinder() {
+            return binder;
+        }
+
+        public static TestInterface asInterface(IBinder binder) {
+            if (binder != null) {
+                IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+                if (iface != null && iface instanceof TestInterface) {
+                    return (TestInterface) iface;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static ReadOperation[] READ_OPERATIONS =
+            new ReadOperation[] {
+                    (parcel, provider) -> {
+                        parcel.setDataPosition(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataCapacity(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataSize(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataSize();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataPosition();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataCapacity();
+                    },
+
+                    // read basic types
+                    (parcel, provider) -> {
+                        parcel.readByte();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBoolean();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readInt();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readLong();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readFloat();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readDouble();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString8();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString16();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBlob();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readStrongBinder();
+                    },
+
+                    // read arrays of random length
+                    (parcel, provider) -> {
+                        byte[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new byte[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readByteArray(array);
+                    },
+                    (parcel, provider) -> {
+                        char[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new char[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readCharArray(array);
+                    },
+                    (parcel, provider) -> {
+                        int[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new int[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readIntArray(array);
+                    },
+                    (parcel, provider) -> {
+                        double[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new double[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readDoubleArray(array);
+                    },
+                    (parcel, provider) -> {
+                        float[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new float[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readFloatArray(array);
+                    },
+                    (parcel, provider) -> {
+                        boolean[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBooleanArray(array);
+                    },
+                    (parcel, provider) -> {
+                        long[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new long[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readLongArray(array);
+                    },
+                    (parcel, provider) -> {
+                        IBinder[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBinderArray(array);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
+                        parcel.readBinderList(arrayList);
+                    },
+
+                    // unmarshall from random parcel data and random bytes
+                    (parcel, provider) -> {
+                        byte[] data = parcel.marshall();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        byte[] data = provider.consumeRemainingAsBytes();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+
+                    // read AIDL generated parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SingleDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        EmptyParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, EmptyParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        GenericDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            int len = provider.consumeInt(MIN_LEN, MAX_LEN);
+                            array = new GenericDataParcelable[len];
+                        }
+                        parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
+                    },
+
+                    // read parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SomeParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SomeParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+
+                    // read lists
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, SomeParcelable.class);
+                    },
+
+                    // read sparse arrays
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSerializable(loader, Object.class);
+                    },
+
+                    // read interface
+                    (parcel, provider) -> {
+                        TestInterface[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readInterfaceArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        int w = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        int h = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        TestInterface[][] array = new TestInterface[w][h];
+                        parcel.readFixedArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<TestInterface> array = new ArrayList<TestInterface>();
+                        parcel.readInterfaceList(array, TestInterface::asInterface);
+                    },
+
+                    // read bundle
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readBundle(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBundle();
+                    },
+
+                    // read HashMap
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader, String.class, String.class);
+                    },
+                    (parcel, provider) -> {
+                        HashMap<String, String> hashMap = new HashMap<>();
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readMap(hashMap, loader, String.class, String.class);
+                    },
+            };
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
index 625de14..6acb198 100644
--- a/core/tests/fuzzers/java_service_fuzzer/Android.bp
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -37,4 +37,12 @@
         "framework-res",
     ],
     native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
 }
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 1a81dda..6536e43 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -138,6 +138,16 @@
      * provisioning server refuses key issuance, this is a permanent error.</p>
      */
     public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
+    /**
+     * This device requires a software upgrade to use the key provisioning server. The software
+     * is outdated and this error is returned only on devices that rely solely on
+     * remotely-provisioned keys (see <a href=
+     * "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
+     * >Remote Key Provisioning</a>).
+     *
+     * @hide
+     */
+    public static final int ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION = 17;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -157,7 +167,8 @@
             ERROR_INCORRECT_USAGE,
             ERROR_KEY_NOT_TEMPORALLY_VALID,
             ERROR_KEY_OPERATION_EXPIRED,
-            ERROR_ATTESTATION_KEYS_UNAVAILABLE
+            ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+            ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION,
     })
     public @interface PublicErrorCode {
     }
@@ -184,6 +195,16 @@
      * This value is returned when {@link #isTransientFailure()} is {@code true}.
      */
     public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
+    /**
+     * Re-try the operation that led to this error when the device has a software update
+     * downloaded and on the next reboot. The Remote provisioning server recognizes
+     * the device, but refuses issuance of attestation keys because it contains a software
+     * version that could potentially be vulnerable and needs an update. Re-trying after the
+     * device has upgraded and rebooted may alleviate the problem.
+     *
+     * <p>This value is returned when {@link #isTransientFailure()} is {@code true}.
+     */
+    public static final int RETRY_AFTER_NEXT_REBOOT = 4;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -191,6 +212,7 @@
             RETRY_NEVER,
             RETRY_WITH_EXPONENTIAL_BACKOFF,
             RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+            RETRY_AFTER_NEXT_REBOOT,
     })
     public @interface RetryPolicy {
     }
@@ -217,6 +239,13 @@
      * when the device has connectivity again.
      * @hide */
     public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
+    /**
+     * The RKP server recognizes the device, but the device may be running vulnerable software,
+     * and thus refusing issuance of RKP keys to it.
+     *
+     * @hide
+     */
+    public static final int RKP_FETCHING_PENDING_SOFTWARE_REBOOT = 4;
 
     // Constants for encoding information about the error encountered:
     // Whether the error relates to the system state/implementation as a whole, or a specific key.
@@ -236,7 +265,7 @@
     private static int initializeRkpStatusForRegularErrors(int errorCode) {
         // Check if the system code mistakenly called a constructor of KeyStoreException with
         // the OUT_OF_KEYS error code but without RKP status.
-        if (errorCode == ResponseCode.OUT_OF_KEYS) {
+        if (isRkpRelatedError(errorCode)) {
             Log.e(TAG, "RKP error code without RKP status");
             // Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
             return RKP_SERVER_REFUSED_ISSUANCE;
@@ -272,7 +301,7 @@
         super(message);
         mErrorCode = errorCode;
         mRkpStatus = rkpStatus;
-        if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
+        if (!isRkpRelatedError(mErrorCode)) {
             Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
         }
     }
@@ -309,10 +338,11 @@
     public boolean isTransientFailure() {
         PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
         // Special-case handling for RKP failures:
-        if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
+        if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
             switch (mRkpStatus) {
                 case RKP_TEMPORARILY_UNAVAILABLE:
                 case RKP_FETCHING_PENDING_CONNECTIVITY:
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
                     return true;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                 default:
@@ -346,6 +376,11 @@
         return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
     }
 
+    private static boolean isRkpRelatedError(int errorCode) {
+        return errorCode == ResponseCode.OUT_OF_KEYS
+                  || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
+    }
+
     /**
      * Returns the re-try policy for transient failures. Valid only if
      * {@link #isTransientFailure()} returns {@code True}.
@@ -362,6 +397,8 @@
                     return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                     return RETRY_NEVER;
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
+                    return RETRY_AFTER_NEXT_REBOOT;
                 default:
                     return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
                             ? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
@@ -620,5 +657,8 @@
                 new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
                 new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index afec830..2830d7eff 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -648,6 +648,7 @@
         // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
         // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
         // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
+        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
         public final int rkpStatus;
         @Nullable
         public final KeyPair keyPair;
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae282..2994593 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
 <?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.
--->
+  ~ Copyright (C) 2022 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="48dp"
         android:height="48dp"
@@ -25,13 +25,12 @@
         android:fillAlpha="0.8"
         android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
     <group
-        android:translateX="12"
-        android:translateY="12">
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="10"
+        android:translateY="10">
         <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
-        <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+            android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+            android:fillColor="@color/compat_controls_text"/>
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 6e116b9..c836b95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -51,8 +51,6 @@
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.SurfaceUtils;
 
-import java.util.function.Consumer;
-
 /**
  * Handles split decor like showing resizing hint for a specific split.
  */
@@ -72,17 +70,18 @@
     private SurfaceControl mIconLeash;
     private SurfaceControl mBackgroundLeash;
     private SurfaceControl mGapBackgroundLeash;
+    private SurfaceControl mScreenshot;
 
     private boolean mShown;
     private boolean mIsResizing;
     private final Rect mBounds = new Rect();
-    private final Rect mResizingBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
 
     private int mIconSize;
     private int mOffsetX;
     private int mOffsetY;
+    private int mRunningAnimationCount = 0;
 
     public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
             SurfaceSession surfaceSession) {
@@ -173,7 +172,6 @@
             mIsResizing = true;
             mBounds.set(newBounds);
         }
-        mResizingBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
@@ -227,33 +225,41 @@
                 t.setVisibility(mBackgroundLeash, show);
                 t.setVisibility(mIconLeash, show);
             } else {
-                startFadeAnimation(show, null /* finishedConsumer */);
+                startFadeAnimation(show, false, null);
             }
             mShown = show;
         }
     }
 
     /** Stops showing resizing hint. */
-    public void onResized(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing) {
-            mTempRect.set(mResizingBounds);
-            mTempRect.offsetTo(-mOffsetX, -mOffsetY);
-            final SurfaceControl screenshot = ScreenshotUtils.takeScreenshot(t,
-                    mHostLeash, mTempRect, Integer.MAX_VALUE - 1);
+    public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+        if (mScreenshot != null) {
+            t.setPosition(mScreenshot, mOffsetX, mOffsetY);
 
             final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
             final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
             va.addUpdateListener(valueAnimator -> {
                 final float progress = (float) valueAnimator.getAnimatedValue();
-                animT.setAlpha(screenshot, progress);
+                animT.setAlpha(mScreenshot, progress);
                 animT.apply();
             });
             va.addListener(new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    mRunningAnimationCount++;
+                }
+
+                @Override
                 public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
-                    animT.remove(screenshot);
+                    mRunningAnimationCount--;
+                    animT.remove(mScreenshot);
                     animT.apply();
                     animT.close();
+                    mScreenshot = null;
+
+                    if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                        animFinishedCallback.run();
+                    }
                 }
             });
             va.start();
@@ -285,10 +291,34 @@
             mFadeAnimator.cancel();
         }
         if (mShown) {
-            fadeOutDecor(null /* finishedCallback */);
+            fadeOutDecor(animFinishedCallback);
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
+            if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                animFinishedCallback.run();
+            }
+        }
+    }
+
+    /** Screenshot host leash and attach on it if meet some conditions */
+    public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (!mShown && mIsResizing) {
+            mTempRect.set(mBounds);
+            mTempRect.offsetTo(0, 0);
+            mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+                    Integer.MAX_VALUE - 1);
+        }
+    }
+
+    /** Set screenshot and attach on host leash it if meet some conditions */
+    public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+        if (screenshot == null || !screenshot.isValid()) return;
+
+        if (!mShown && mIsResizing) {
+            mScreenshot = screenshot;
+            t.reparent(screenshot, mHostLeash);
+            t.setLayer(screenshot, Integer.MAX_VALUE - 1);
         }
     }
 
@@ -296,18 +326,15 @@
      * directly. */
     public void fadeOutDecor(Runnable finishedCallback) {
         if (mShown) {
-            startFadeAnimation(false /* show */, transaction -> {
-                releaseDecor(transaction);
-                if (finishedCallback != null) finishedCallback.run();
-            });
+            startFadeAnimation(false /* show */, true, finishedCallback);
             mShown = false;
         } else {
             if (finishedCallback != null) finishedCallback.run();
         }
     }
 
-    private void startFadeAnimation(boolean show,
-            Consumer<SurfaceControl.Transaction> finishedConsumer) {
+    private void startFadeAnimation(boolean show, boolean releaseSurface,
+            Runnable finishedCallback) {
         final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
         mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
         mFadeAnimator.setDuration(FADE_DURATION);
@@ -324,6 +351,7 @@
         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
+                mRunningAnimationCount++;
                 if (show) {
                     animT.show(mBackgroundLeash).show(mIconLeash);
                 }
@@ -335,6 +363,7 @@
 
             @Override
             public void onAnimationEnd(@NonNull Animator animation) {
+                mRunningAnimationCount--;
                 if (!show) {
                     if (mBackgroundLeash != null) {
                         animT.hide(mBackgroundLeash);
@@ -343,11 +372,15 @@
                         animT.hide(mIconLeash);
                     }
                 }
-                if (finishedConsumer != null) {
-                    finishedConsumer.accept(animT);
+                if (releaseSurface) {
+                    releaseDecor(animT);
                 }
                 animT.apply();
                 animT.close();
+
+                if (mRunningAnimationCount == 0 && finishedCallback != null) {
+                    finishedCallback.run();
+                }
             }
         });
         mFadeAnimator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4ea8a5d..dc1634a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -697,10 +697,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeController> providesDesktopModeController(
-            @DynamicOverride Optional<DesktopModeController> desktopModeController) {
+    static Optional<DesktopModeController> provideDesktopModeController(
+            @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
         if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeController;
+            return desktopModeController.map(Lazy::get);
         }
         return Optional.empty();
     }
@@ -711,10 +714,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
-            @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
+    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+            @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
         if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeTaskRepository;
+            return desktopModeTaskRepository.map(Lazy::get);
         }
         return Optional.empty();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f1670cd..6be8305 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -189,7 +189,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            @DynamicOverride DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
         return new CaptionWindowDecorViewModel(
                     context,
                     mainHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index abc4024..5824f51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -64,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -99,7 +100,9 @@
         mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
-        shellInit.addInitCallback(this::onInit, this);
+        if (DesktopModeStatus.isSupported()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -258,18 +261,36 @@
 
     @NonNull
     private WindowContainerTransaction bringDesktopAppsToFront() {
-        ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-        ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+        final List<RunningTaskInfo> taskInfos = new ArrayList<>();
         for (Integer taskId : activeTasks) {
             RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
             if (taskInfo != null) {
                 taskInfos.add(taskInfo);
             }
         }
-        // Order by lastActiveTime, descending
-        taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
-        WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        if (taskInfos.isEmpty()) {
+            return wct;
+        }
+
+        final boolean allActiveTasksAreVisible = taskInfos.stream()
+                .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+        if (allActiveTasksAreVisible) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                    "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+            return wct;
+        }
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                "bringDesktopAppsToFront: reordering all active tasks to the front");
+        final List<Integer> allTasksInZOrder =
+                mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+        // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+        // in the WCT.
+        taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 2fafe67..e3eb2b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -37,6 +37,13 @@
             "persist.wm.debug.desktop_mode", false);
 
     /**
+     * Return {@code true} if desktop mode support is enabled
+     */
+    public static boolean isSupported() {
+        return IS_SUPPORTED;
+    }
+
+    /**
      * Check if desktop mode is active
      *
      * @return {@code true} if active
@@ -54,4 +61,5 @@
             return false;
         }
     }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index b7749fc..600ccc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -33,6 +33,8 @@
      */
     private val activeTasks = ArraySet<Int>()
     private val visibleTasks = ArraySet<Int>()
+    // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+    private val freeformTasksInZOrder = mutableListOf<Int>()
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
     // Track visible tasks separately because a task may be part of the desktop but not visible.
     private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@
     }
 
     /**
+     * Whether a task is visible.
+     */
+    fun isVisibleTask(taskId: Int): Boolean {
+        return visibleTasks.contains(taskId)
+    }
+
+    /**
      * Get a set of the active tasks
      */
     fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@
     }
 
     /**
+     * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+     */
+    fun getFreeformTasksInZOrder(): List<Int> {
+        return freeformTasksInZOrder
+    }
+
+    /**
      * Updates whether a freeform task with this id is visible or not and notifies listeners.
      */
     fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@
     }
 
     /**
+     * Add (or move if it already exists) the task to the top of the ordered list.
+     */
+    fun addOrMoveFreeformTaskToTop(taskId: Int) {
+        if (freeformTasksInZOrder.contains(taskId)) {
+            freeformTasksInZOrder.remove(taskId)
+        }
+        freeformTasksInZOrder.add(0, taskId)
+    }
+
+    /**
+     * Remove the task from the ordered list.
+     */
+    fun removeFreeformTask(taskId: Int) {
+        freeformTasksInZOrder.remove(taskId)
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 0000000..926cfb3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 44bcdb2..8a9b74f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -38,7 +38,8 @@
  * {@link ShellTaskOrganizer.TaskListener} for {@link
  * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
  */
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+        ShellTaskOrganizer.FocusListener {
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@
 
     private void onInit() {
         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+        if (DesktopModeStatus.IS_SUPPORTED) {
+            mShellTaskOrganizer.addFocusListener(this);
+        }
     }
 
     @Override
@@ -86,13 +90,16 @@
             t.apply();
         }
 
-        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
-                if (repository.addActiveTask(taskInfo.taskId)) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                            "Adding active freeform task: #%d", taskInfo.taskId);
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+                if (taskInfo.isVisible) {
+                    if (repository.addActiveTask(taskInfo.taskId)) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                                "Adding active freeform task: #%d", taskInfo.taskId);
+                    }
+                    repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
             });
         }
     }
@@ -105,6 +112,7 @@
 
         if (DesktopModeStatus.IS_SUPPORTED) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.removeFreeformTask(taskInfo.taskId);
                 if (repository.removeActiveTask(taskInfo.taskId)) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                             "Removing active freeform task: #%d", taskInfo.taskId);
@@ -140,6 +148,18 @@
     }
 
     @Override
+    public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+                "Freeform Task Focus Changed: #%d focused=%b",
+                taskInfo.taskId, taskInfo.isFocused);
+        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) {
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+            });
+        }
+    }
+
+    @Override
     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
         b.setParent(findTaskSurface(taskId));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 0000000..0c2d5c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 7908f35..db0f0bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -377,6 +377,8 @@
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(prefix + " mListener=" + mListener);
+        pw.println(prefix + "Tasks:");
         ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
         for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc..2f2bc77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
+    /** Called when requested to go to fullscreen from the current active split app. */
+    void goToFullscreenFromSplit();
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index b20125d..400039b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -123,6 +123,7 @@
     public static final int EXIT_REASON_SCREEN_LOCKED = 7;
     public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+    public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -134,6 +135,7 @@
             EXIT_REASON_SCREEN_LOCKED,
             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
             EXIT_REASON_CHILD_TASK_ENTER_PIP,
+            EXIT_REASON_FULLSCREEN_SHORTCUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
@@ -418,6 +420,10 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    public void goToFullscreenFromSplit() {
+        mStageCoordinator.goToFullscreenFromSplit();
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -860,9 +866,12 @@
 
         @Override
         public void onFinishedWakingUp() {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onFinishedWakingUp();
-            });
+            mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+        }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
     }
 
@@ -918,33 +927,25 @@
         @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
-                    (controller) -> {
-                        controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
-                    });
+                    (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
         }
 
         @Override
         public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
-                    (controller) -> {
-                        controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
-                    });
+                    (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
         }
 
         @Override
         public void removeFromSideStage(int taskId) {
             executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
-                    (controller) -> {
-                        controller.removeFromSideStage(taskId);
-                    });
+                    (controller) -> controller.removeFromSideStage(taskId));
         }
 
         @Override
         public void startTask(int taskId, int position, @Nullable Bundle options) {
             executeRemoteCallWithTaskPermission(mController, "startTask",
-                    (controller) -> {
-                        controller.startTask(taskId, position, options);
-                    });
+                    (controller) -> controller.startTask(taskId, position, options));
         }
 
         @Override
@@ -1036,19 +1037,16 @@
         public void startShortcut(String packageName, String shortcutId, int position,
                 @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcut",
-                    (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, position, options, user,
-                                instanceId);
-                    });
+                    (controller) -> controller.startShortcut(packageName, shortcutId, position,
+                            options, user, instanceId));
         }
 
         @Override
         public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
                 @Nullable Bundle options, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntent",
-                    (controller) -> {
-                        controller.startIntent(intent, fillInIntent, position, options, instanceId);
-                    });
+                    (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+                            instanceId));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 21a1310..1cf3a89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -47,6 +47,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@
     DismissTransition mPendingDismiss = null;
     TransitSession mPendingEnter = null;
     TransitSession mPendingRecent = null;
+    TransitSession mPendingResize = null;
 
     private IBinder mAnimatingTransition = null;
     OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -177,6 +179,43 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+            @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        mFinishCallback = finishCallback;
+        mAnimatingTransition = transition;
+        mFinishTransaction = finishTransaction;
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+                final SurfaceControl leash = change.getLeash();
+                startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+                        change.getEndAbsBounds().top);
+                startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                        change.getEndAbsBounds().height());
+
+                SplitDecorManager decor = mainRoot.equals(change.getContainer())
+                        ? mainDecor : sideDecor;
+                ValueAnimator va = new ValueAnimator();
+                mAnimations.add(va);
+                decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+                decor.onResized(startTransaction, () -> {
+                    mTransitions.getMainExecutor().execute(() -> {
+                        mAnimations.remove(va);
+                        onFinish(null /* wct */, null /* wctCB */);
+                    });
+                });
+            }
+        }
+
+        startTransaction.apply();
+        onFinish(null /* wct */, null /* wctCB */);
+    }
+
     boolean isPendingTransition(IBinder transition) {
         return getPendingTransition(transition) != null;
     }
@@ -193,6 +232,10 @@
         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
     }
 
+    boolean isPendingResize(IBinder transition) {
+        return mPendingResize != null && mPendingResize.mTransition == transition;
+    }
+
     @Nullable
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
@@ -201,11 +244,14 @@
             return mPendingRecent;
         } else if (isPendingDismiss(transition)) {
             return mPendingDismiss;
+        } else if (isPendingResize(transition)) {
+            return mPendingResize;
         }
 
         return null;
     }
 
+
     /** Starts a transition to enter split with a remote transition animator. */
     IBinder startEnterTransition(
             @WindowManager.TransitionType int transitType,
@@ -258,6 +304,21 @@
                 exitReasonToString(reason), stageTypeToString(dismissTop));
     }
 
+    IBinder startResizeTransition(WindowContainerTransaction wct,
+            Transitions.TransitionHandler handler,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+        setResizeTransition(transition, finishCallback);
+        return transition;
+    }
+
+    void setResizeTransition(@NonNull IBinder transition,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced Resize split screen");
+    }
+
     void setRecentTransition(@NonNull IBinder transition,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionFinishedCallback finishCallback) {
@@ -324,6 +385,9 @@
             mPendingRecent.onConsumed(aborted);
             mPendingRecent = null;
             mPendingRemoteHandler = null;
+        } else if (isPendingResize(transition)) {
+            mPendingResize.onConsumed(aborted);
+            mPendingResize = null;
         }
     }
 
@@ -340,6 +404,9 @@
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
+        } else if (isPendingResize(mAnimatingTransition)) {
+            mPendingResize.onFinished(wct, mFinishTransaction);
+            mPendingResize = null;
         }
 
         mPendingRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a04..1016e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -38,6 +39,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +182,8 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
             case EXIT_REASON_UNKNOWN:
                 // Fall through
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index eb7b0d7..38fb2df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -49,6 +49,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -1115,15 +1116,8 @@
      * Exits the split screen by finishing one of the tasks.
      */
     protected void exitStage(@SplitPosition int stageToClose) {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            StageTaskListener stageToTop = mSideStagePosition == stageToClose
-                    ? mMainStage
-                    : mSideStage;
-            exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
-        } else {
-            boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
-            mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
-        }
+        mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                EXIT_REASON_APP_FINISHED);
     }
 
     /**
@@ -1157,6 +1151,9 @@
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 // User has unlocked the device after folded
             case EXIT_REASON_DEVICE_FOLDED:
+                // The device is folded
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                // User has used a keyboard shortcut to go back to fullscreen from split
                 return true;
             default:
                 return false;
@@ -1670,15 +1667,29 @@
     public void onLayoutSizeChanged(SplitLayout layout) {
         // Reset this flag every time onLayoutSizeChanged.
         mShowDecorImmediately = false;
+
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            // Only need screenshot for legacy case because shell transition should screenshot
+            // itself during transition.
+            final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+            mMainStage.screenshotIfNeeded(startT);
+            mSideStage.screenshotIfNeeded(startT);
+            mTransactionPool.release(startT);
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         sendOnBoundsChanged();
-        mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> {
-            updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
-            mMainStage.onResized(t);
-            mSideStage.onResized(t);
-        });
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+        } else {
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+                mMainStage.onResized(t);
+                mSideStage.onResized(t);
+            });
+        }
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
@@ -2032,6 +2043,12 @@
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+        } else if (mSplitTransitions.isPendingResize(transition)) {
+            mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+                    finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+                    mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+                    mSideStage.getSplitDecorManager());
+            return true;
         }
         if (!shouldAnimate) return false;
 
@@ -2119,6 +2136,16 @@
         return true;
     }
 
+    public void goToFullscreenFromSplit() {
+        boolean leftOrTop;
+        if (mSideStage.isFocused()) {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+        } else {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        }
+        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+    }
+
     /** Synchronize split-screen state with transition and make appropriate preparations. */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 358f712..8a52c87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -292,7 +292,13 @@
 
     void onResized(SurfaceControl.Transaction t) {
         if (mSplitDecorManager != null) {
-            mSplitDecorManager.onResized(t);
+            mSplitDecorManager.onResized(t, null);
+        }
+    }
+
+    void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (mSplitDecorManager != null) {
+            mSplitDecorManager.screenshotIfNeeded(t);
         }
     }
 
@@ -304,6 +310,10 @@
         }
     }
 
+    SplitDecorManager getSplitDecorManager() {
+        return mSplitDecorManager;
+    }
+
     void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
         // Clear overridden bounds and windowing mode to make sure the child task can inherit
         // windowing mode and bounds from split root.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e7036c7..5655402 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
 import java.util.function.Supplier;
 
 /**
@@ -74,7 +75,7 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
-    private DesktopModeController mDesktopModeController;
+    private Optional<DesktopModeController> mDesktopModeController;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -90,7 +91,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
         this(
                 context,
                 mainHandler,
@@ -110,7 +111,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            DesktopModeController desktopModeController,
+            Optional<DesktopModeController> desktopModeController,
             CaptionWindowDecoration.Factory captionWindowDecorFactory,
             Supplier<InputManager> inputManagerSupplier) {
 
@@ -246,10 +247,10 @@
             } else if (id == R.id.caption_handle) {
                 decoration.createHandleMenu();
             } else if (id == R.id.desktop_button) {
-                mDesktopModeController.setDesktopModeActive(true);
+                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
-                mDesktopModeController.setDesktopModeActive(false);
+                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 decoration.closeHandleMenu();
                 decoration.setButtonVisibility();
             }
@@ -304,9 +305,9 @@
          */
         private void handleEventForMove(MotionEvent e) {
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
-            int windowingMode = mDesktopModeController
-                    .getDisplayAreaWindowingMode(taskInfo.displayId);
-            if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            if (mDesktopModeController.isPresent()
+                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+                    == WINDOWING_MODE_FULLSCREEN) {
                 return;
             }
             switch (e.getActionMasked()) {
@@ -331,7 +332,7 @@
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight
                             && DesktopModeStatus.isActive(mContext)) {
-                        mDesktopModeController.setDesktopModeActive(false);
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                     }
                     break;
                 }
@@ -471,7 +472,7 @@
                     int statusBarHeight = mDisplayController
                             .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                     if (ev.getY() > statusBarHeight) {
-                        mDesktopModeController.setDesktopModeActive(true);
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         return;
                     }
                 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 89bafcb..dad9133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -36,7 +36,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -50,6 +50,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
 
 import androidx.test.filters.SmallTest;
 
@@ -99,15 +100,14 @@
     @Before
     public void setUp() {
         mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isSupported()).thenReturn(true);
         when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
 
         mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
-        mController = new DesktopModeController(mContext, mShellInit, mShellController,
-                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
-                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+        mController = createController();
 
         when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
 
@@ -124,7 +124,17 @@
 
     @Test
     public void instantiate_addInitCallback() {
-        verify(mShellInit, times(1)).addInitCallback(any(), any());
+        verify(mShellInit).addInitCallback(any(), any());
+    }
+
+    @Test
+    public void instantiate_flagOff_doNotAddInitCallback() {
+        when(DesktopModeStatus.isSupported()).thenReturn(false);
+        clearInvocations(mShellInit);
+
+        createController();
+
+        verify(mShellInit, never()).addInitCallback(any(), any());
     }
 
     @Test
@@ -222,25 +232,29 @@
         // Check that there are hierarchy changes for home task and visible task
         assertThat(wct.getHierarchyOps()).hasSize(2);
         // First show home task
-        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
         assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
 
         // Then visible task on top of it
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
         assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
     }
 
     @Test
-    public void testShowDesktopApps() {
-        // Set up two active tasks on desktop
+    public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+        // Set up two active tasks on desktop, task2 is on top of task1.
         RunningTaskInfo freeformTask1 = createFreeformTask();
-        freeformTask1.lastActiveTime = 100;
-        RunningTaskInfo freeformTask2 = createFreeformTask();
-        freeformTask2.lastActiveTime = 200;
         mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask1.taskId, false /* visible */);
+        RunningTaskInfo freeformTask2 = createFreeformTask();
         mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask2.taskId, false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
                 freeformTask1);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
@@ -248,27 +262,66 @@
 
         // Run show desktop apps logic
         mController.showDesktopApps();
-        ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
-        } else {
-            verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
-        }
-        WindowContainerTransaction wct = wctCaptor.getValue();
 
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
         assertThat(wct.getHierarchyOps()).hasSize(2);
 
-        // Task 2 has activity later, must be first
-        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        // Task 1 appeared first, must be first reorder to top.
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+        assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
 
-        // Task 1 should be second
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        // Task 2 appeared last, must be last reorder to top.
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+        assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+    }
+
+    @Test
+    public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // No reordering needed.
+        assertThat(wct.getHierarchyOps()).isEmpty();
+    }
+
+    @Test
+    public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // Both tasks should be reordered to top, even if one was already visible.
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+        final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+        final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
     }
 
     @Test
@@ -309,6 +362,12 @@
         assertThat(wct).isNotNull();
     }
 
+    private DesktopModeController createController() {
+        return new DesktopModeController(mContext, mShellInit, mShellController,
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+    }
+
     private DisplayAreaInfo createMockDisplayArea() {
         DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
                 mContext.getDisplayId(), 0);
@@ -355,6 +414,17 @@
         return arg.getValue();
     }
 
+    private WindowContainerTransaction getBringAppsToFrontTransaction() {
+        final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+        } else {
+            verify(mShellTaskOrganizer).applyTransaction(arg.capture());
+        }
+        return arg.getValue();
+    }
+
     private void assertThatBoundsCleared(Change change) {
         assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
         assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a..1e43a59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@
         assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
     }
 
+    @Test
+    fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks[0]).isEqualTo(7)
+        assertThat(tasks[1]).isEqualTo(6)
+        assertThat(tasks[2]).isEqualTo(5)
+    }
+
+    @Test
+    fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        repo.addOrMoveFreeformTaskToTop(6)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks.first()).isEqualTo(6)
+    }
+
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeTaskChangedCalls = 0
         override fun onActiveTasksChanged() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 9b37b97..ad6fced 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -54,6 +54,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Supplier;
 
 /** Tests of {@link CaptionWindowDecorViewModel} */
@@ -101,7 +102,7 @@
                 mTaskOrganizer,
                 mDisplayController,
                 mSyncQueue,
-                mDesktopModeController,
+                Optional.of(mDesktopModeController),
                 mCaptionWindowDecorFactory,
                 new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
         mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
new file mode 100644
index 0000000..506128e
--- /dev/null
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 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.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.location.Location;
+
+import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.S2CellIdUtils;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
+ * into ones above Mean Sea Level.
+ */
+public final class AltitudeConverter {
+
+    private static final double MAX_ABS_VALID_LATITUDE = 90;
+    private static final double MAX_ABS_VALID_LONGITUDE = 180;
+
+    /** Manages a mapping of geoid heights associated with S2 cells. */
+    private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+
+    /**
+     * Creates an instance that manages an independent cache to optimized conversions of locations
+     * in proximity to one another.
+     */
+    public AltitudeConverter() {
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the {@code location} has an invalid latitude,
+     * longitude, or altitude above WGS84.
+     */
+    private static void validate(@NonNull Location location) {
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLatitude(), MAX_ABS_VALID_LATITUDE),
+                "Invalid latitude: %f", location.getLatitude());
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLongitude(), MAX_ABS_VALID_LONGITUDE),
+                "Invalid longitude: %f", location.getLongitude());
+        Preconditions.checkArgument(location.hasAltitude(), "Missing altitude above WGS84");
+        Preconditions.checkArgument(Double.isFinite(location.getAltitude()),
+                "Invalid altitude above WGS84: %f", location.getAltitude());
+    }
+
+    private static boolean isFiniteAndAtAbsMost(double value, double rhs) {
+        return Double.isFinite(value) && Math.abs(value) <= rhs;
+    }
+
+    /**
+     * Returns the four S2 cell IDs for the map square associated with the {@code location}.
+     *
+     * <p>The first map cell contains the location, while the others are located horizontally,
+     * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
+     * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
+     * corresponding ID is set to zero.
+     */
+    @NonNull
+    private static long[] findMapSquare(@NonNull MapParamsProto params,
+            @NonNull Location location) {
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+
+        // (0,0) cell.
+        long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+        long[] edgeNeighbors = new long[4];
+        S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
+
+        // (1,0) cell.
+        int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
+        long s1 = edgeNeighbors[i1 + 2];
+
+        // (0,1) cell.
+        int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
+        long s2 = edgeNeighbors[i2 + 1];
+
+        // (1,1) cell.
+        S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+        long s3 = 0;
+        for (int i = 0; i < edgeNeighbors.length; i++) {
+            if (edgeNeighbors[i] == s0) {
+                int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
+                s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
+                break;
+            }
+        }
+
+        // Reuse edge neighbors' array to avoid an extra allocation.
+        edgeNeighbors[0] = s0;
+        edgeNeighbors[1] = s1;
+        edgeNeighbors[2] = s2;
+        edgeNeighbors[3] = s3;
+        return edgeNeighbors;
+    }
+
+    /**
+     * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
+     * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
+     * accuracy; otherwise, does not add a corresponding accuracy.
+     */
+    private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+            @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
+        long s0 = s2CellIds[0];
+        double h0 = geoidHeightsMeters[0];
+        double h1 = geoidHeightsMeters[1];
+        double h2 = geoidHeightsMeters[2];
+        double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+
+        // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
+        // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
+        // employ the simplified unit square formulation.
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+        double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
+        double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+        double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
+
+        location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
+        if (location.hasVerticalAccuracy()) {
+            double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
+            if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
+                location.setMslAltitudeAccuracyMeters(
+                        (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+            }
+        }
+    }
+
+    /**
+     * Adds a Mean Sea Level altitude to the {@code location}. In addition, adds a Mean Sea Level
+     * altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
+     * otherwise, does not add a corresponding accuracy.
+     *
+     * <p>Must be called off the main thread as data may be loaded from raw assets. Throws an
+     * {@link IOException} if an I/O error occurs when loading data.
+     *
+     * <p>Throws an {@link IllegalArgumentException} if the {@code location} has an invalid
+     * latitude, longitude, or altitude above WGS84. Specifically:
+     *
+     * <ul>
+     *     <li>The latitude must be between -90 and 90, both inclusive.
+     *     <li>The longitude must be between -180 and 180, both inclusive.
+     *     <li>The altitude above WGS84 must be finite.
+     * </ul>
+     */
+    @WorkerThread
+    public void addMslAltitude(@NonNull Context context, @NonNull Location location)
+            throws IOException {
+        validate(location);
+        MapParamsProto params = GeoidHeightMap.getParams(context);
+        long[] s2CellIds = findMapSquare(params, location);
+        double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
+        addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
new file mode 100644
index 0000000..6430eb4
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2022 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.internal.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
+ * level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidHeightMap {
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    @Nullable
+    private static MapParamsProto sParams;
+
+    /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
+    private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+
+    /**
+     * Returns the singleton parameter instance for a spherically projected geoid height map and its
+     * corresponding tile management.
+     */
+    @NonNull
+    public static MapParamsProto getParams(@NonNull Context context) throws IOException {
+        synchronized (sLock) {
+            if (sParams == null) {
+                try (InputStream is = context.getApplicationContext().getAssets().open(
+                        "geoid_height_map/map-params.pb")) {
+                    sParams = MapParamsProto.parseFrom(is.readAllBytes());
+                }
+            }
+            return sParams;
+        }
+    }
+
+    private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+    }
+
+    @NonNull
+    private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getToken(
+                S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+    }
+
+    /**
+     * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+     * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
+     * returns false and adds NaNs for absent values.
+     */
+    private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction,
+            @NonNull long[] s2CellIds, @NonNull double[] values) {
+        int len = s2CellIds.length;
+
+        S2TileProto[] tiles = new S2TileProto[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] != 0) {
+                tiles[i] = tileFunction.getTile(s2CellIds[i]);
+            }
+            values[i] = Double.NaN;
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (tiles[i] == null || !Double.isNaN(values[i])) {
+                continue;
+            }
+
+            mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+            mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+            mergeBytePngValues(params, s2CellIds, tiles, i, values);
+        }
+
+        boolean allFound = true;
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            if (Double.isNaN(values[i])) {
+                allFound = false;
+            } else {
+                values[i] = (((int) values[i]) & 0xFF) / 255.0;
+            }
+        }
+        return allFound;
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        byte[] bytes = tiles[tileIndex].byteBuffer;
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+        int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+        int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+            int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+            int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+        }
+    }
+
+    private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+                values);
+    }
+
+    private static void mergeBytePngValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+        if (bitmap == null) {
+            return;
+        }
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+                    getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+        }
+    }
+
+    /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+    private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+        return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+    }
+
+    /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+    private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+        return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+    }
+
+    private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+        return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+    }
+
+    /**
+     * Returns the geoid heights in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
+     * non-zero ID.
+     */
+    @NonNull
+    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+            @NonNull long[] s2CellIds) throws IOException {
+        Preconditions.checkArgument(s2CellIds.length == 4);
+        for (long s2CellId : s2CellIds) {
+            Preconditions.checkArgument(
+                    s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+        }
+
+        double[] heightsMeters = new double[s2CellIds.length];
+        if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+
+        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
+        if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+        throw new IOException("Unable to calculate geoid heights from raw assets.");
+    }
+
+    /**
+     * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+     * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
+     * otherwise, returns false and adds NaNs for absent heights.
+     */
+    private boolean getGeoidHeights(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+            @NonNull double[] heightsMeters) {
+        boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
+        for (int i = 0; i < heightsMeters.length; i++) {
+            // NaNs are properly preserved.
+            heightsMeters[i] *= params.modelAMeters;
+            heightsMeters[i] += params.modelBMeters;
+        }
+        return allFound;
+    }
+
+    @NonNull
+    private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+            @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+        int len = s2CellIds.length;
+
+        // Enable batch loading by finding all cache keys upfront.
+        long[] cacheKeys = new long[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+        }
+
+        // Attempt to load tiles from cache.
+        S2TileProto[] loadedTiles = new S2TileProto[len];
+        String[] diskTokens = new String[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+                continue;
+            }
+            loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+            diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                    diskTokens[j] = diskTokens[i];
+                }
+            }
+        }
+
+        // Attempt to load tiles from disk.
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+                continue;
+            }
+
+            S2TileProto tile;
+            try (InputStream is = context.getApplicationContext().getAssets().open(
+                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+                tile = S2TileProto.parseFrom(is.readAllBytes());
+            }
+            mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+        }
+
+        return s2CellId -> {
+            if (s2CellId == 0) {
+                return null;
+            }
+            long cacheKey = getCacheKey(params, s2CellId);
+            for (int i = 0; i < cacheKeys.length; i++) {
+                if (cacheKeys[i] == cacheKey) {
+                    return loadedTiles[i];
+                }
+            }
+            return null;
+        };
+    }
+
+    private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
+            @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
+            @NonNull S2TileProto[] loadedTiles) throws IOException {
+        int len = cacheKeys.length;
+        int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+        // Reusable arrays.
+        long[] s2CellIds = new long[numMapCellsPerCacheTile];
+        double[] values = new double[numMapCellsPerCacheTile];
+
+        // Each cache key identifies a different sub-tile of the disk tile.
+        TileFunction diskTileFunction = s2CellId -> diskTile;
+        for (int i = diskTokenIndex; i < len; i++) {
+            if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+                    || loadedTiles[i] != null) {
+                continue;
+            }
+
+            // Find all map cells within the current cache tile.
+            long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                s2CellIds[j] = s2CellId;
+                s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+            }
+
+            if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+                throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+            }
+
+            loadedTiles[i] = new S2TileProto();
+            loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+            }
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                }
+            }
+
+            // Side load into tile cache.
+            mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+        }
+    }
+
+    /** Defines a function-like object to retrieve tiles for map cells. */
+    private interface TileFunction {
+
+        @Nullable
+        S2TileProto getTile(long s2CellId);
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
new file mode 100644
index 0000000..5f11387
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2022 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.internal.location.altitude;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+    /** The level of all leaf S2 cells. */
+    public static final int MAX_LEVEL = 30;
+
+    private static final int MAX_SIZE = 1 << MAX_LEVEL;
+    private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+    private static final int NUM_FACES = 6;
+    private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+    private static final int SWAP_MASK = 0x1;
+    private static final int LOOKUP_BITS = 4;
+    private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+    private static final int INVERT_MASK = 0x2;
+    private static final int LEAF_MASK = 0x1;
+    private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+    private static final int[][] POS_TO_IJ =
+            {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+    private static final double UV_LIMIT = calculateUvLimit();
+    private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+    private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+
+    // Used to encode (i, j, o) coordinates into primitive longs.
+    private static final int I_SHIFT = 33;
+    private static final int J_SHIFT = 2;
+    private static final long J_MASK = (1L << 31) - 1;
+
+    static {
+        initLookupCells();
+    }
+
+    /** Prevents instantiation. */
+    private S2CellIdUtils() {
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * degrees.
+     */
+    public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+        return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+    }
+
+    /**
+     * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+     * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+     * [0, {@code getLevel(s2CellId)}[.
+     */
+    public static long getParent(long s2CellId, int level) {
+        long newLsb = getLowestOnBitForLevel(level);
+        return (s2CellId & -newLsb) | newLsb;
+    }
+
+    /**
+     * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+     * cells adjacent across the specified cell's four edges. This array must be of minimum
+     * length four, and elements at the tail end of the array not corresponding to a neighbor
+     * are set to zero. A reference to this array is returned.
+     *
+     * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+     * neighbors are guaranteed to be distinct.
+     */
+    public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+        int level = getLevel(s2CellId);
+        int size = levelToSizeIj(level);
+        int face = getFace(s2CellId);
+        long ijo = toIjo(s2CellId);
+        int i = ijoToI(ijo);
+        int j = ijoToJ(ijo);
+
+        int iPlusSize = i + size;
+        int iMinusSize = i - size;
+        int jPlusSize = j + size;
+        int jMinusSize = j - size;
+        boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+        boolean iMinusSizeGteZero = iMinusSize >= 0;
+        boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+        boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+        int index = 0;
+        // Down direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+                level);
+        // Right direction.
+        neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+        // Up direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+        // Left direction.
+        neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+                level);
+
+        // Pad end of neighbor array with zeros.
+        Arrays.fill(neighbors, index, neighbors.length, 0);
+    }
+
+    /** Returns the "i" coordinate for the specified S2 cell. */
+    public static int getI(long s2CellId) {
+        return ijoToI(toIjo(s2CellId));
+    }
+
+    /** Returns the "j" coordinate for the specified S2 cell. */
+    public static int getJ(long s2CellId) {
+        return ijoToJ(toIjo(s2CellId));
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * radians.
+     */
+    private static long fromLatLngRadians(double latRadians, double lngRadians) {
+        double cosLat = Math.cos(latRadians);
+        double x = Math.cos(lngRadians) * cosLat;
+        double y = Math.sin(lngRadians) * cosLat;
+        double z = Math.sin(latRadians);
+        return fromXyz(x, y, z);
+    }
+
+    /**
+     * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+     * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    static int getLevel(long s2CellId) {
+        if (isLeaf(s2CellId)) {
+            return MAX_LEVEL;
+        }
+        return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+    }
+
+    /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+    static long getLowestOnBit(long s2CellId) {
+        return s2CellId & -s2CellId;
+    }
+
+    /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+    static long getLowestOnBitForLevel(int level) {
+        return 1L << (2 * (MAX_LEVEL - level));
+    }
+
+    /**
+     * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+     * level, in Hilbert curve order.
+     */
+    static long getTraversalStart(long s2CellId, int level) {
+        return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+    }
+
+    /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+    static long getTraversalNext(long s2CellId) {
+        return s2CellId + (getLowestOnBit(s2CellId) << 1);
+    }
+
+    /**
+     * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+     * lower levels (i.e., larger cells) are encoded into fewer characters.
+     */
+    @NonNull
+    static String getToken(long s2CellId) {
+        if (s2CellId == 0) {
+            return "X";
+        }
+
+        // Convert to a hex string with as many digits as necessary.
+        String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+        // Prefix 0s to get a length 16 string.
+        String padded = padStart(hex);
+        // Trim zeroes off the end.
+        return padded.replaceAll("0*$", "");
+    }
+
+    private static String padStart(String string) {
+        if (string.length() >= 16) {
+            return string;
+        }
+        return "0".repeat(16 - string.length()) + string;
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+    private static long fromXyz(double x, double y, double z) {
+        int face = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[face];
+        double u = uvTransform.xyzToU(x, y, z);
+        double v = uvTransform.xyzToV(x, y, z);
+        return fromFuv(face, u, v);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+    private static long fromFuv(int face, double u, double v) {
+        int i = uToI(u);
+        int j = vToJ(v);
+        return fromFij(face, i, j);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+    private static long fromFij(int face, int i, int j) {
+        int bits = (face & SWAP_MASK);
+        // Update most significant bits.
+        long msb = ((long) face) << (POS_BITS - 33);
+        for (int k = 7; k >= 4; --k) {
+            bits = lookupBits(i, j, k, bits);
+            msb = updateBits(msb, k, bits);
+            bits = maskBits(bits);
+        }
+        // Update least significant bits.
+        long lsb = 0;
+        for (int k = 3; k >= 0; --k) {
+            bits = lookupBits(i, j, k, bits);
+            lsb = updateBits(lsb, k, bits);
+            bits = maskBits(bits);
+        }
+        return (((msb << 32) + lsb) << 1) + 1;
+    }
+
+    private static long fromFijWrap(int face, int i, int j) {
+        double u = iToU(i);
+        double v = jToV(j);
+
+        XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+        double x = xyzTransform.uvToX(u, v);
+        double y = xyzTransform.uvToY(u, v);
+        double z = xyzTransform.uvToZ(u, v);
+
+        int newFace = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[newFace];
+        double newU = uvTransform.xyzToU(x, y, z);
+        double newV = uvTransform.xyzToV(x, y, z);
+
+        int newI = uShiftIntoI(newU);
+        int newJ = vShiftIntoJ(newV);
+        return fromFij(newFace, newI, newJ);
+    }
+
+    private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+        if (isSameFace) {
+            return fromFij(face, i, j);
+        }
+        return fromFijWrap(face, i, j);
+    }
+
+    /**
+     * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+     * on a face boundary, the returned face is arbitrary but repeatable.
+     */
+    private static int xyzToFace(double x, double y, double z) {
+        double absX = Math.abs(x);
+        double absY = Math.abs(y);
+        double absZ = Math.abs(z);
+        if (absX > absY) {
+            if (absX > absZ) {
+                return (x < 0) ? 3 : 0;
+            }
+            return (z < 0) ? 5 : 2;
+        }
+        if (absY > absZ) {
+            return (y < 0) ? 4 : 1;
+        }
+        return (z < 0) ? 5 : 2;
+    }
+
+    private static int uToI(double u) {
+        double s;
+        if (u >= 0) {
+            s = 0.5 * Math.sqrt(1 + 3 * u);
+        } else {
+            s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+        }
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vToJ(double v) {
+        // Same calculation as uToI.
+        return uToI(v);
+    }
+
+    private static int lookupBits(int i, int j, int k, int bits) {
+        bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+        bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+        return LOOKUP_POS[bits];
+    }
+
+    private static long updateBits(long sb, int k, int bits) {
+        return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+    }
+
+    private static int maskBits(int bits) {
+        return bits & (SWAP_MASK | INVERT_MASK);
+    }
+
+    private static int getFace(long s2CellId) {
+        return (int) (s2CellId >>> POS_BITS);
+    }
+
+    private static boolean isLeaf(long s2CellId) {
+        return ((int) s2CellId & LEAF_MASK) != 0;
+    }
+
+    private static double iToU(int i) {
+        int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+        return Math.max(
+                -UV_LIMIT,
+                Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+    }
+
+    private static double jToV(int j) {
+        // Same calculation as iToU.
+        return iToU(j);
+    }
+
+    private static long toIjo(long s2CellId) {
+        int face = getFace(s2CellId);
+        int bits = face & SWAP_MASK;
+        int i = 0;
+        int j = 0;
+        for (int k = 7; k >= 0; --k) {
+            int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+            bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+                    - 1)) << 2;
+            bits = LOOKUP_IJ[bits];
+            i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+            j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+            bits &= (SWAP_MASK | INVERT_MASK);
+        }
+        int orientation =
+                ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+                        : bits;
+        return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+    }
+
+    private static int ijoToI(long ijo) {
+        return (int) (ijo >>> I_SHIFT);
+    }
+
+    private static int ijoToJ(long ijo) {
+        return (int) ((ijo >>> J_SHIFT) & J_MASK);
+    }
+
+    private static int uShiftIntoI(double u) {
+        double s = 0.5 * (u + 1);
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vShiftIntoJ(double v) {
+        // Same calculation as uShiftIntoI.
+        return uShiftIntoI(v);
+    }
+
+    private static int levelToSizeIj(int level) {
+        return 1 << (MAX_LEVEL - level);
+    }
+
+    private static void initLookupCells() {
+        initLookupCell(0, 0, 0, 0, 0, 0);
+        initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+        initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+        initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+    }
+
+    private static void initLookupCell(
+            int level, int i, int j, int origOrientation, int pos, int orientation) {
+        if (level == LOOKUP_BITS) {
+            int ij = (i << LOOKUP_BITS) + j;
+            LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+            LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+        } else {
+            level++;
+            i <<= 1;
+            j <<= 1;
+            pos <<= 2;
+            for (int subPos = 0; subPos < 4; subPos++) {
+                int ij = POS_TO_IJ[orientation][subPos];
+                int orientationMask = POS_TO_ORIENTATION[subPos];
+                initLookupCell(
+                        level,
+                        i + (ij >>> 1),
+                        j + (ij & 0x1),
+                        origOrientation,
+                        pos + subPos,
+                        orientation ^ orientationMask);
+            }
+        }
+    }
+
+    private static double calculateUvLimit() {
+        double machEps = 1.0;
+        do {
+            machEps /= 2.0f;
+        } while ((1.0 + (machEps / 2.0)) != 1.0);
+        return 1.0 + machEps;
+    }
+
+    @NonNull
+    private static UvTransform[] createUvTransforms() {
+        UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+        uvTransforms[0] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return y / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / x;
+                    }
+                };
+        uvTransforms[1] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / y;
+                    }
+                };
+        uvTransforms[2] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -y / z;
+                    }
+                };
+        uvTransforms[3] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return y / x;
+                    }
+                };
+        uvTransforms[4] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / y;
+                    }
+                };
+        uvTransforms[5] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -y / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / z;
+                    }
+                };
+        return uvTransforms;
+    }
+
+    @NonNull
+    private static XyzTransform[] createXyzTransforms() {
+        XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+        xyzTransforms[0] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[1] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[2] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return 1;
+                    }
+                };
+        xyzTransforms[3] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[4] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[5] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -1;
+                    }
+                };
+        return xyzTransforms;
+    }
+
+    /**
+     * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+     * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+     * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+     * coordinate equal to (0, 0).
+     */
+    private interface UvTransform {
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToU(double x, double y, double z);
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToV(double x, double y, double z);
+    }
+
+    /**
+     * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+     * resulting vectors are not necessarily of unit length.
+     */
+    private interface XyzTransform {
+
+        /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+        double uvToX(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+        double uvToY(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+        double uvToZ(double u, double v);
+    }
+}
diff --git a/media/java/android/media/AudioHalVersionInfo.aidl b/media/java/android/media/AudioHalVersionInfo.aidl
new file mode 100644
index 0000000..a83f8c8
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2022, 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.media;
+
+parcelable AudioHalVersionInfo;
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
new file mode 100644
index 0000000..985a758
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Defines the audio HAL version.
+ *
+ * @hide
+ */
+@TestApi
+public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHalVersionInfo> {
+    /**
+     * Indicate the audio HAL is implemented with HIDL (HAL interface definition language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/hidl/">HIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_HIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#HIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_HIDL = 0;
+
+    /**
+     * Indicate the audio HAL is implemented with AIDL (Android Interface Definition Language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/aidl/">AIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_AIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#AIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_AIDL = 1;
+
+    /** @hide */
+    @IntDef(
+            flag = false,
+            prefix = "AUDIO_HAL_TYPE_",
+            value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+    public @interface AudioHalType {}
+
+    /** AudioHalVersionInfo object of all valid Audio HAL versions. */
+    public static final @NonNull AudioHalVersionInfo AIDL_1_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_AIDL, 1 /* major */, 0 /* minor */);
+
+    public static final @NonNull AudioHalVersionInfo HIDL_7_1 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 1 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_7_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_6_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 6 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_5_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 5 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_4_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 4 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_2_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 2 /* major */, 0 /* minor */);
+
+    /**
+     * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
+     * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
+     */
+    // TODO: add AIDL_1_0 with sAudioHALVersions.
+    public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
+            List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+
+    private static final String TAG = "AudioHalVersionInfo";
+    private AudioHalVersion mHalVersion = new AudioHalVersion();
+
+    public @AudioHalType int getHalType() {
+        return mHalVersion.type;
+    }
+
+    public int getMajorVersion() {
+        return mHalVersion.major;
+    }
+
+    public int getMinorVersion() {
+        return mHalVersion.minor;
+    }
+
+    /** String representative of AudioHalVersion.Type */
+    private static @NonNull String typeToString(@AudioHalType int type) {
+        if (type == AudioHalVersion.Type.HIDL) {
+            return "HIDL";
+        } else if (type == AudioHalVersion.Type.AIDL) {
+            return "AIDL";
+        } else {
+            return "INVALID";
+        }
+    }
+
+    /** String representative of type, major and minor */
+    private static @NonNull String toString(@AudioHalType int type, int major, int minor) {
+        return typeToString(type) + ":" + Integer.toString(major) + "." + Integer.toString(minor);
+    }
+
+    private AudioHalVersionInfo(@AudioHalType int type, int major, int minor) {
+        mHalVersion.type = type;
+        mHalVersion.major = major;
+        mHalVersion.minor = minor;
+    }
+
+    private AudioHalVersionInfo(Parcel in) {
+        mHalVersion = in.readTypedObject(AudioHalVersion.CREATOR);
+    }
+
+    /** String representative of this (AudioHalVersionInfo) object */
+    @Override
+    public String toString() {
+        return toString(mHalVersion.type, mHalVersion.major, mHalVersion.minor);
+    }
+
+    /**
+     * Compare two HAL versions by comparing their index in VERSIONS.
+     *
+     * <p>Normally all AudioHalVersionInfo object to compare should exist in the VERSIONS list. If
+     * both candidates exist in the VERSIONS list, smaller index means newer. Any candidate not
+     * exist in the VERSIONS list will be considered to be oldest version.
+     *
+     * @return 0 if the HAL version is the same as the other HAL version. Positive if the HAL
+     *     version is newer than the other HAL version. Negative if the HAL version is older than
+     *     the other version.
+     */
+    @Override
+    public int compareTo(@NonNull AudioHalVersionInfo other) {
+        int indexOther = VERSIONS.indexOf(other);
+        int indexThis = VERSIONS.indexOf(this);
+        if (indexThis < 0 || indexOther < 0) {
+            return indexThis - indexOther;
+        }
+        return indexOther - indexThis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel out, int flag) {
+        out.writeTypedObject(mHalVersion, flag);
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioHalVersionInfo> CREATOR =
+            new Parcelable.Creator<AudioHalVersionInfo>() {
+                @Override
+                public AudioHalVersionInfo createFromParcel(@NonNull Parcel in) {
+                    return new AudioHalVersionInfo(in);
+                }
+
+                @Override
+                public AudioHalVersionInfo[] newArray(int size) {
+                    return new AudioHalVersionInfo[size];
+                }
+            };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9c5313a..ae0d45f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8502,13 +8502,14 @@
     }
 
     /**
-     * Returns the audio HAL version in the form MAJOR.MINOR. If there is no audio HAL found, null
-     * will be returned.
+     * Returns an {@link AudioHalVersionInfo} indicating the Audio Hal Version. If there is no audio
+     * HAL found, null will be returned.
      *
+     * @return @see @link #AudioHalVersionInfo The version of Audio HAL.
      * @hide
      */
     @TestApi
-    public static @Nullable String getHalVersion() {
+    public static @Nullable AudioHalVersionInfo getHalVersion() {
         try {
             return getService().getHalVersion();
         } catch (RemoteException e) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
old mode 100755
new mode 100644
index 2e766d5..ee453a4
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -22,6 +22,7 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioFormat;
 import android.media.AudioFocusInfo;
+import android.media.AudioHalVersionInfo;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
@@ -571,5 +572,5 @@
             in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
             boolean handlesvolumeAdjustment);
 
-    String getHalVersion();
+    AudioHalVersionInfo getHalVersion();
 }
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
new file mode 100644
index 0000000..388d098
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
new file mode 100644
index 0000000..9e4f424
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2022 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="21dp"
+    android:height="17dp"
+    android:viewportWidth="21"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 4 2.941 L 20 2.941 L 20 0.941 L 4 0.941 C 2.9 0.941 2 1.841 2 2.941 L 2 13.941 L 0 13.941 L 0 16.941 L 11 16.941 L 11 13.941 L 4 13.941 L 4 2.941 Z M 20 4.941 L 14 4.941 C 13.45 4.941 13 5.391 13 5.941 L 13 15.941 C 13 16.491 13.45 16.941 14 16.941 L 20 16.941 C 20.55 16.941 21 16.491 21 15.941 L 21 5.941 C 21 5.391 20.55 4.941 20 4.941 Z M 15 13.941 L 19 13.941 L 19 6.941 L 15 6.941 L 15 13.941 Z"
+        android:fillColor="#5f6368"
+        android:strokeWidth="1"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
new file mode 100644
index 0000000..b6ee4f9
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2022 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="19dp"
+    android:height="21dp"
+    android:viewportWidth="19"
+    android:viewportHeight="21">
+    <path
+        android:name="path"
+        android:pathData="M 0.25 8.591 C 0.133 8.508 0.058 8.408 0.025 8.291 C 0.008 8.158 0.05 8.025 0.15 7.891 C 1.183 6.475 2.475 5.375 4.025 4.591 C 5.592 3.808 7.258 3.416 9.025 3.416 C 10.792 3.416 12.458 3.8 14.025 4.566 C 15.592 5.316 16.9 6.408 17.95 7.841 C 18.067 7.991 18.1 8.125 18.05 8.241 C 18.017 8.358 17.95 8.458 17.85 8.541 C 17.75 8.625 17.633 8.666 17.5 8.666 C 17.367 8.65 17.25 8.575 17.15 8.441 C 16.233 7.141 15.05 6.15 13.6 5.466 C 12.167 4.766 10.642 4.416 9.025 4.416 C 7.408 4.416 5.892 4.766 4.475 5.466 C 3.058 6.15 1.883 7.141 0.95 8.441 C 0.85 8.591 0.733 8.675 0.6 8.691 C 0.467 8.708 0.35 8.675 0.25 8.591 Z M 11.85 20.916 C 10.117 20.483 8.7 19.625 7.6 18.341 C 6.5 17.041 5.95 15.458 5.95 13.591 C 5.95 12.758 6.25 12.058 6.85 11.491 C 7.45 10.925 8.175 10.641 9.025 10.641 C 9.875 10.641 10.6 10.925 11.2 11.491 C 11.8 12.058 12.1 12.758 12.1 13.591 C 12.1 14.141 12.308 14.608 12.725 14.991 C 13.142 15.358 13.633 15.541 14.2 15.541 C 14.767 15.541 15.25 15.358 15.65 14.991 C 16.05 14.608 16.25 14.141 16.25 13.591 C 16.25 11.658 15.542 10.033 14.125 8.716 C 12.708 7.4 11.017 6.741 9.05 6.741 C 7.083 6.741 5.392 7.4 3.975 8.716 C 2.558 10.033 1.85 11.65 1.85 13.566 C 1.85 13.966 1.883 14.466 1.95 15.066 C 2.033 15.666 2.217 16.366 2.5 17.166 C 2.55 17.316 2.542 17.45 2.475 17.566 C 2.425 17.683 2.333 17.766 2.2 17.816 C 2.067 17.866 1.933 17.866 1.8 17.816 C 1.683 17.75 1.6 17.65 1.55 17.516 C 1.3 16.866 1.117 16.225 1 15.591 C 0.9 14.941 0.85 14.275 0.85 13.591 C 0.85 11.375 1.65 9.516 3.25 8.016 C 4.867 6.516 6.792 5.766 9.025 5.766 C 11.275 5.766 13.208 6.516 14.825 8.016 C 16.442 9.516 17.25 11.375 17.25 13.591 C 17.25 14.425 16.95 15.125 16.35 15.691 C 15.767 16.241 15.05 16.516 14.2 16.516 C 13.35 16.516 12.617 16.241 12 15.691 C 11.4 15.125 11.1 14.425 11.1 13.591 C 11.1 13.041 10.892 12.583 10.475 12.216 C 10.075 11.833 9.592 11.641 9.025 11.641 C 8.458 11.641 7.967 11.833 7.55 12.216 C 7.15 12.583 6.95 13.041 6.95 13.591 C 6.95 15.208 7.425 16.558 8.375 17.641 C 9.342 18.725 10.583 19.483 12.1 19.916 C 12.25 19.966 12.35 20.05 12.4 20.166 C 12.45 20.283 12.458 20.408 12.425 20.541 C 12.392 20.658 12.325 20.758 12.225 20.841 C 12.125 20.925 12 20.95 11.85 20.916 Z M 3.5 3.366 C 3.367 3.45 3.233 3.475 3.1 3.441 C 2.967 3.391 2.867 3.3 2.8 3.166 C 2.733 3.033 2.717 2.916 2.75 2.816 C 2.783 2.7 2.867 2.6 3 2.516 C 3.933 2.016 4.908 1.633 5.925 1.366 C 6.942 1.1 7.975 0.966 9.025 0.966 C 10.092 0.966 11.133 1.1 12.15 1.366 C 13.167 1.616 14.15 1.983 15.1 2.466 C 15.25 2.55 15.333 2.65 15.35 2.766 C 15.383 2.883 15.375 3 15.325 3.116 C 15.275 3.233 15.192 3.325 15.075 3.391 C 14.958 3.458 14.817 3.45 14.65 3.366 C 13.767 2.916 12.85 2.575 11.9 2.341 C 10.967 2.091 10.008 1.966 9.025 1.966 C 8.058 1.966 7.108 2.083 6.175 2.316 C 5.242 2.533 4.35 2.883 3.5 3.366 Z M 6.45 20.566 C 5.467 19.533 4.708 18.483 4.175 17.416 C 3.658 16.333 3.4 15.058 3.4 13.591 C 3.4 12.075 3.95 10.8 5.05 9.766 C 6.15 8.716 7.475 8.191 9.025 8.191 C 10.575 8.191 11.908 8.716 13.025 9.766 C 14.142 10.8 14.7 12.075 14.7 13.591 C 14.7 13.741 14.65 13.866 14.55 13.966 C 14.467 14.05 14.35 14.091 14.2 14.091 C 14.067 14.091 13.95 14.05 13.85 13.966 C 13.75 13.866 13.7 13.741 13.7 13.591 C 13.7 12.341 13.233 11.3 12.3 10.466 C 11.383 9.616 10.292 9.191 9.025 9.191 C 7.758 9.191 6.667 9.616 5.75 10.466 C 4.85 11.3 4.4 12.341 4.4 13.591 C 4.4 14.941 4.633 16.091 5.1 17.041 C 5.567 17.975 6.25 18.916 7.15 19.866 C 7.25 19.966 7.3 20.083 7.3 20.216 C 7.3 20.35 7.25 20.466 7.15 20.566 C 7.05 20.666 6.933 20.716 6.8 20.716 C 6.667 20.716 6.55 20.666 6.45 20.566 Z M 14 18.866 C 12.517 18.866 11.225 18.366 10.125 17.366 C 9.042 16.366 8.5 15.108 8.5 13.591 C 8.5 13.458 8.542 13.341 8.625 13.241 C 8.725 13.141 8.85 13.091 9 13.091 C 9.15 13.091 9.267 13.141 9.35 13.241 C 9.45 13.341 9.5 13.458 9.5 13.591 C 9.5 14.841 9.95 15.866 10.85 16.666 C 11.75 17.466 12.8 17.866 14 17.866 C 14.1 17.866 14.242 17.858 14.425 17.841 C 14.608 17.825 14.8 17.8 15 17.766 C 15.15 17.733 15.275 17.758 15.375 17.841 C 15.492 17.908 15.567 18.016 15.6 18.166 C 15.633 18.3 15.608 18.416 15.525 18.516 C 15.442 18.616 15.333 18.683 15.2 18.716 C 14.9 18.8 14.633 18.85 14.4 18.866 L 14 18.866 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
new file mode 100644
index 0000000..61800f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2022 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="20dp"
+    android:height="17dp"
+    android:viewportWidth="20"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 2 16.941 C 1.45 16.941 0.975 16.75 0.575 16.366 C 0.192 15.966 0 15.491 0 14.941 L 0 2.941 C 0 2.391 0.192 1.925 0.575 1.541 C 0.975 1.141 1.45 0.941 2 0.941 L 18 0.941 C 18.55 0.941 19.017 1.141 19.4 1.541 C 19.8 1.925 20 2.391 20 2.941 L 20 14.941 C 20 15.491 19.8 15.966 19.4 16.366 C 19.017 16.75 18.55 16.941 18 16.941 L 2 16.941 Z M 4.5 11.941 L 5.65 11.941 L 5.65 5.941 L 4.75 5.941 L 3 7.191 L 3.6 8.091 L 4.5 7.441 L 4.5 11.941 Z M 7.6 11.941 L 11.5 11.941 L 11.5 10.941 L 9.15 10.941 L 9.1 10.891 C 9.45 10.558 9.733 10.275 9.95 10.041 C 10.183 9.808 10.367 9.625 10.5 9.491 C 10.8 9.191 11.025 8.891 11.175 8.591 C 11.325 8.291 11.4 7.975 11.4 7.641 C 11.4 7.158 11.217 6.758 10.85 6.441 C 10.483 6.108 10.017 5.941 9.45 5.941 C 9.017 5.941 8.625 6.066 8.275 6.316 C 7.925 6.566 7.683 6.891 7.55 7.291 L 8.55 7.691 C 8.633 7.475 8.75 7.308 8.9 7.191 C 9.067 7.058 9.25 6.991 9.45 6.991 C 9.7 6.991 9.9 7.058 10.05 7.191 C 10.217 7.325 10.3 7.491 10.3 7.691 C 10.3 7.875 10.267 8.05 10.2 8.216 C 10.133 8.366 9.983 8.558 9.75 8.791 L 8.95 9.591 C 8.6 9.941 8.15 10.391 7.6 10.941 L 7.6 11.941 Z M 15 11.941 C 15.6 11.941 16.083 11.775 16.45 11.441 C 16.817 11.108 17 10.675 17 10.141 C 17 9.841 16.917 9.575 16.75 9.341 C 16.583 9.108 16.35 8.925 16.05 8.791 L 16.05 8.741 C 16.283 8.608 16.467 8.441 16.6 8.241 C 16.733 8.025 16.8 7.775 16.8 7.491 C 16.8 7.041 16.625 6.675 16.275 6.391 C 15.925 6.091 15.483 5.941 14.95 5.941 C 14.533 5.941 14.142 6.066 13.775 6.316 C 13.425 6.55 13.2 6.841 13.1 7.191 L 14.1 7.591 C 14.167 7.391 14.275 7.233 14.425 7.116 C 14.575 7 14.75 6.941 14.95 6.941 C 15.167 6.941 15.342 7.008 15.475 7.141 C 15.625 7.258 15.7 7.408 15.7 7.591 C 15.7 7.825 15.617 8.008 15.45 8.141 C 15.283 8.275 15.067 8.341 14.8 8.341 L 14.35 8.341 L 14.35 9.341 L 14.85 9.341 C 15.183 9.341 15.442 9.408 15.625 9.541 C 15.808 9.675 15.9 9.858 15.9 10.091 C 15.9 10.308 15.808 10.5 15.625 10.666 C 15.442 10.816 15.233 10.891 15 10.891 C 14.717 10.891 14.5 10.833 14.35 10.716 C 14.2 10.583 14.067 10.358 13.95 10.041 L 12.95 10.441 C 13.067 10.925 13.3 11.3 13.65 11.566 C 14.017 11.816 14.467 11.941 15 11.941 Z M 2 14.941 L 18 14.941 L 18 2.941 L 2 2.941 L 2 14.941 Z M 2 14.941 L 2 2.941 L 2 14.941 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 8c9023c..870d983 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -18,9 +18,13 @@
   <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
   <string name="string_save_to_another_device">Save to another device</string>
   <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
-  <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+  <string name="passkey_creation_intro_title">Safer with passkeys</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
   <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
   <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
   <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 1db1d1c..2780c3c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -40,12 +40,9 @@
 import android.os.Bundle
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
-import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.RemoteInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -146,20 +143,30 @@
       providerDisabledList, context)
     var defaultProvider: EnabledProviderInfo? = null
     var remoteEntry: RemoteInfo? = null
+    var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
       if (providerInfo.isDefault) {defaultProvider = providerInfo}
       if (providerInfo.remoteEntry != null) {
         remoteEntry = providerInfo.remoteEntry!!
       }
+      if (providerInfo.createOptions.isNotEmpty()) {
+        createOptionSize += providerInfo.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = providerInfo
+      }
     }
     return CreateCredentialUiState(
       enabledProviders = providerEnabledList,
       disabledProviders = providerDisabledList,
-      toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
+      CreateFlowUtils.toCreateScreenState(
+        createOptionSize, false,
+        requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
       false,
-      toActiveEntry(defaultProvider, remoteEntry),
+      CreateFlowUtils.toActiveEntry(
+        /*defaultProvider=*/defaultProvider, createOptionSize,
+        lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
     )
   }
 
@@ -194,6 +201,7 @@
         .setRemoteEntry(
           newRemoteEntry("key2", "subkey-1")
         )
+        .setIsDefaultProvider(true)
         .build(),
       CreateCredentialProviderData
         .Builder("com.dashlane")
@@ -517,38 +525,4 @@
       "tribank.us"
     )
   }
-
-  private fun toCreateScreenState(
-    requestDisplayInfo: RequestDisplayInfo,
-    defaultProvider: EnabledProviderInfo?,
-    remoteEntry: RemoteInfo?,
-  ): CreateScreenState {
-    return if (
-      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
-    ){
-      CreateScreenState.EXTERNAL_ONLY_SELECTION
-    } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
-      if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
-        CreateScreenState.PASSKEY_INTRO
-      } else {
-        CreateScreenState.PROVIDER_SELECTION
-      }
-    } else {
-      CreateScreenState.CREATION_OPTION_SELECTION
-    }
-  }
-
-  private fun toActiveEntry(
-    defaultProvider: EnabledProviderInfo?,
-    remoteEntry: RemoteInfo?,
-  ): ActiveEntry? {
-    return if (
-      defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
-    ) {
-      ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-    } else if (
-      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
-      ActiveEntry(defaultProvider, remoteEntry)
-    } else null
-  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 357c55d..0d7e819 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -28,6 +28,9 @@
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.createflow.RemoteInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.getflow.ActionEntryInfo
 import com.android.credentialmanager.getflow.AuthenticationEntryInfo
 import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -36,6 +39,7 @@
 import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 import com.android.credentialmanager.jetpack.provider.ActionUi
 import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
 import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -243,7 +247,8 @@
             createCredentialRequestJetpack.password,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_password)!!
+            context.getDrawable(R.drawable.ic_password)!!,
+            requestInfo.isFirstUsage
           )
         }
         is CreatePublicKeyCredentialRequest -> {
@@ -261,7 +266,8 @@
             displayName,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_passkey)!!)
+            context.getDrawable(R.drawable.ic_passkey)!!,
+            requestInfo.isFirstUsage)
         }
         // TODO: correctly parsing for other sign-ins
         else -> {
@@ -270,11 +276,58 @@
             "Elisa Beckett",
             "other-sign-ins",
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_other_sign_in)!!)
+            context.getDrawable(R.drawable.ic_other_sign_in)!!,
+            requestInfo.isFirstUsage)
         }
       }
     }
 
+    fun toCreateScreenState(
+      createOptionSize: Int,
+      isOnPasskeyIntroStateAlready: Boolean,
+      requestDisplayInfo: RequestDisplayInfo,
+      defaultProvider: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): CreateScreenState {
+      return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+          .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+        CreateScreenState.PASSKEY_INTRO
+      } else if (
+        (defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize > 1) {
+        CreateScreenState.PROVIDER_SELECTION
+      } else if (
+        ((defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize == 1) || (
+                defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
+        CreateScreenState.CREATION_OPTION_SELECTION
+      } else if (createOptionSize == 0 && remoteEntry != null) {
+        CreateScreenState.EXTERNAL_ONLY_SELECTION
+      } else {
+          // TODO: properly handle error and gracefully finish itself
+          throw java.lang.IllegalStateException("Empty provider list.")
+      }
+    }
+
+   fun toActiveEntry(
+      defaultProvider: EnabledProviderInfo?,
+      createOptionSize: Int,
+      lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): ActiveEntry? {
+      return if (
+        defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+        ActiveEntry(defaultProvider, remoteEntry)
+      } else if (
+        defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+      ) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else if (createOptionSize == 1) {
+        ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
+          lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
+      } else null
+    }
+
     private fun toCreationOptionInfoList(
       providerId: String,
       creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 57e20be..5d22bfd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -140,12 +140,11 @@
 ) {
     ContainerCard() {
         Column() {
-            Icon(
-                painter = painterResource(R.drawable.ic_passkey),
+            Image(
+                painter = painterResource(R.drawable.ic_passkeys_onboarding),
                 contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
                 modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(top = 24.dp, bottom = 12.dp)
+                    .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
             )
             TextOnSurface(
                 text = stringResource(R.string.passkey_creation_intro_title),
@@ -159,11 +158,62 @@
                 thickness = 16.dp,
                 color = Color.Transparent
             )
-            TextSecondary(
-                text = stringResource(R.string.passkey_creation_intro_body),
-                style = MaterialTheme.typography.bodyLarge,
-                modifier = Modifier.padding(horizontal = 28.dp),
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_password),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
             )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_device),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
             Divider(
                 thickness = 32.dp,
                 color = Color.Transparent
@@ -698,7 +748,7 @@
                 },
                 contentDescription = null,
                 tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.padding(start = 18.dp).size(32.dp)
+                modifier = Modifier.padding(horizontal = 18.dp).size(32.dp)
             )
         },
         label = {
@@ -760,7 +810,7 @@
         onClick = onOptionSelected,
         icon = {
             Image(
-                modifier = Modifier.size(32.dp).padding(start = 16.dp),
+                modifier = Modifier.padding(horizontal = 16.dp).size(32.dp),
                 bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                 contentDescription = null
             )
@@ -770,13 +820,12 @@
                 TextOnSurfaceVariant(
                     text = providerInfo.displayName,
                     style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+                    modifier = Modifier.padding(top = 16.dp),
                 )
                 if (createOptionInfo.userProviderDisplayName != null) {
                     TextSecondary(
                         text = createOptionInfo.userProviderDisplayName,
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(start = 16.dp),
                     )
                 }
                 if (createOptionInfo.passwordCount != null &&
@@ -790,7 +839,7 @@
                             createOptionInfo.passkeyCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp),
                     )
                 } else if (createOptionInfo.passwordCount != null) {
                     TextSecondary(
@@ -800,7 +849,7 @@
                             createOptionInfo.passwordCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp),
                     )
                 } else if (createOptionInfo.passkeyCount != null) {
                     TextSecondary(
@@ -810,7 +859,7 @@
                             createOptionInfo.passkeyCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp),
                     )
                 } else if (createOptionInfo.totalCredentialCount != null) {
                     // TODO: Handle the case when there is total count
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 6f74998..3d23f5d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -27,6 +27,7 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CreateFlowUtils
 import com.android.credentialmanager.CredentialManagerRepo
 import com.android.credentialmanager.common.DialogResult
 import com.android.credentialmanager.common.ProviderActivityResult
@@ -60,24 +61,26 @@
 
   fun onConfirmIntro() {
     var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
     uiState.enabledProviders.forEach {
-      enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
-    uiState = if (createOptionSize > 1) {
-      uiState.copy(
-        currentScreenState = CreateScreenState.PROVIDER_SELECTION,
-        showActiveEntryOnly = true
-      )
-    } else if (createOptionSize == 1){
-      uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-        showActiveEntryOnly = false,
-        activeEntry = ActiveEntry(uiState.enabledProviders.first(),
-          uiState.enabledProviders.first().createOptions.first()
-        )
-      )
-    } else {
-      throw java.lang.IllegalStateException("Empty provider list.")
+      enabledProvider ->
+      if (enabledProvider.createOptions.isNotEmpty()) {
+        createOptionSize += enabledProvider.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+      }
+      if (enabledProvider.remoteEntry != null) {
+        remoteEntry = enabledProvider.remoteEntry!!
+      }
     }
+    uiState = uiState.copy(
+      currentScreenState = CreateFlowUtils.toCreateScreenState(
+        createOptionSize, true,
+        uiState.requestDisplayInfo, null, remoteEntry),
+      showActiveEntryOnly = createOptionSize > 1,
+      activeEntry = CreateFlowUtils.toActiveEntry(
+        null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+    )
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 9ac524a..21abe08 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -77,6 +77,7 @@
   val type: String,
   val appDomainName: String,
   val typeIcon: Drawable,
+  val isFirstUsage: Boolean,
 )
 
 /**
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8101a94..f59b0de 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,11 +17,13 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.getBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
@@ -66,4 +68,19 @@
 
         assertThat(clicked).isTrue()
     }
+
+    @Test
+    fun twoButtons_positionIsAligned() {
+        composeTestRule.setContent {
+            ActionButtons(
+                listOf(
+                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
+                )
+            )
+        }
+
+        assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
+            .isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
+    }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
new file mode 100644
index 0000000..9d0501f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FooterTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun footer_isEmpty() {
+        composeTestRule.setContent {
+            Footer(footerText = "")
+        }
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun footer_notEmpty_displayed() {
+        composeTestRule.setContent {
+            Footer(footerText = FOOTER_TEXT)
+        }
+
+        composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val FOOTER_TEXT = "Footer text"
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 61c7fb9..2951001 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -163,6 +163,11 @@
         mUnpairing = false;
     }
 
+    /** Clears any pending messages in the message queue. */
+    public void release() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
     private void initDrawableCache() {
         int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
         int cacheSize = maxMemory / 8;
@@ -1441,11 +1446,13 @@
         final boolean tmpJustDiscovered = mJustDiscovered;
         final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
         // Set main device from sub device
+        release();
         mDevice = mSubDevice.mDevice;
         mRssi = mSubDevice.mRssi;
         mJustDiscovered = mSubDevice.mJustDiscovered;
         mHearingAidInfo = mSubDevice.mHearingAidInfo;
         // Set sub device from backup
+        mSubDevice.release();
         mSubDevice.mDevice = tmpDevice;
         mSubDevice.mRssi = tmpRssi;
         mSubDevice.mJustDiscovered = tmpJustDiscovered;
@@ -1471,6 +1478,7 @@
      * Remove a device from the member device sets.
      */
     public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+        memberDevice.release();
         mMemberDevices.remove(memberDevice);
     }
 
@@ -1488,11 +1496,13 @@
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
         // Set main device from sub device
+        release();
         mDevice = newMainDevice.mDevice;
         mRssi = newMainDevice.mRssi;
         mJustDiscovered = newMainDevice.mJustDiscovered;
 
         // Set sub device from backup
+        newMainDevice.release();
         newMainDevice.mDevice = tmpDevice;
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index dd56bde..221836b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -223,8 +223,14 @@
 
     public synchronized void clearNonBondedDevices() {
         clearNonBondedSubDevices();
-        mCachedDevices.removeIf(cachedDevice
-            -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+        final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
+        mCachedDevices.stream()
+                .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+                .forEach(cachedDevice -> {
+                    cachedDevice.release();
+                    removedCachedDevice.add(cachedDevice);
+                });
+        mCachedDevices.removeAll(removedCachedDevice);
     }
 
     private void clearNonBondedSubDevices() {
@@ -245,6 +251,7 @@
             if (subDevice != null
                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                 // Sub device exists and it is not bonded
+                subDevice.release();
                 cachedDevice.setSubDevice(null);
             }
         }
@@ -294,6 +301,7 @@
                 }
                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                     cachedDevice.setJustDiscovered(false);
+                    cachedDevice.release();
                     mCachedDevices.remove(i);
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index f06aab3..6b7f733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -176,11 +176,11 @@
      * @param device is the device for which we want to know if supports synchronized presets
      * @return {@code true} if the device supports synchronized presets
      */
-    public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportSynchronizedPresets(device);
+        return mService.supportsSynchronizedPresets(device);
     }
 
     /**
@@ -189,11 +189,11 @@
      * @param device is the device for which we want to know if supports independent presets
      * @return {@code true} if the device supports independent presets
      */
-    public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportIndependentPresets(device);
+        return mService.supportsIndependentPresets(device);
     }
 
     /**
@@ -202,11 +202,11 @@
      * @param device is the device for which we want to know if supports dynamic presets
      * @return {@code true} if the device supports dynamic presets
      */
-    public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportDynamicPresets(device);
+        return mService.supportsDynamicPresets(device);
     }
 
     /**
@@ -215,11 +215,11 @@
      * @param device is the device for which we want to know if supports writable presets
      * @return {@code true} if the device supports writable presets
      */
-    public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+    public boolean supportsWritablePresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportWritablePresets(device);
+        return mService.supportsWritablePresets(device);
     }
 
     @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 65671a2..77c19a1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -998,10 +998,12 @@
 
         mCachedDevice.switchSubDeviceContent();
 
+        verify(mCachedDevice).release();
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
         assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+        verify(mSubCachedDevice).release();
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 503859b..9192086 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1144,7 +1144,7 @@
             Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
         }
 
-        DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
+        Settings.Config.enforceReadPermission(/*namespace=*/name.split("/")[0]);
 
         // Get the value.
         synchronized (mLock) {
@@ -1317,7 +1317,7 @@
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
         }
 
-        DeviceConfig.enforceReadPermission(
+        Settings.Config.enforceReadPermission(
                 prefix != null ? prefix.split("/")[0] : null);
 
         synchronized (mLock) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7db68b0..a8216e8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -914,6 +914,29 @@
         <service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE"/>
 
+        <!-- region Note Task -->
+        <activity
+            android:name=".notetask.shortcut.CreateNoteTaskShortcutActivity"
+            android:enabled="false"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay"
+            android:label="@string/note_task_button_label"
+            android:icon="@drawable/ic_note_task_button">
+
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".notetask.shortcut.LaunchNoteTaskActivity"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay" />
+        <!-- endregion -->
+
         <!-- started from ControlsRequestReceiver -->
         <activity
             android:name=".controls.management.ControlsRequestDialog"
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_button.xml
new file mode 100644
index 0000000..bb5e224
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2022 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="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#636C6F"
+        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+    <path
+        android:fillColor="#636C6F"
+        android:fillType="evenOdd"
+        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 530db0d..13c9a5e 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -35,7 +35,6 @@
         android:layout_height="@dimen/qs_media_session_height_expanded"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:translationZ="0dp"
         android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 49dd574..fd2e324 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -36,4 +36,8 @@
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
 
+    <!-- Whether the battery icon is allowed to display a shield when battery life is being
+         protected. -->
+    <bool name="flag_battery_shield_icon">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a4e9983..9ee918d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2773,6 +2773,10 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
+    <!-- TODO(b/259369672): Replace with final resource. -->
+    <!-- [CHAR LIMIT=30] Label used to open Note Task -->
+    <string name="note_task_button_label">Notetaking</string>
+
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..c985fd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
 import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@
         return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+            mPasswordEntry.requestFocus();
+        }
+        return super.onApplyWindowInsets(insets);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus) {
+            if (isVisibleToUser()) {
+                showKeyboard();
+            } else {
+                hideKeyboard();
+            }
+        }
+    }
+
+    /**
+     * Sends signal to the focused window to show the keyboard.
+     */
+    public void showKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.requestFocus();
+                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+            }
+        });
+    }
+
+    /**
+     * Sends signal to the focused window to hide the keyboard.
+     */
+    public void hideKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.clearFocus();
+                mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+            }
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 195e8f9..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -200,12 +199,9 @@
             return;
         }
 
-        mView.post(() -> {
-            if (mView.isShown()) {
-                mPasswordEntry.requestFocus();
-                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
-            }
-        });
+        if (mView.isShown()) {
+            mView.showKeyboard();
+        }
     }
 
     @Override
@@ -227,16 +223,12 @@
                 super.onPause();
             });
         }
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     @Override
     public void onStartingToHide() {
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8019b56..ae2e1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -83,8 +83,7 @@
     val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
 
     @JvmField
-    val NOTIFICATION_GROUP_CORNER =
-        unreleasedFlag(116, "notification_group_corner", teamfood = true)
+    val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
 
     // TODO(b/259217907)
     @JvmField
@@ -227,7 +226,9 @@
     val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
 
     // TODO(b/256623670): Tracking Bug
-    @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+    @JvmField
+    val BATTERY_SHIELD_ICON =
+        resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
 
     // TODO(b/260881289): Tracking Bug
     val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 827ac78..df8fb91 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -940,19 +940,9 @@
         if (mIsSeekBarEnabled) {
             return ConstraintSet.VISIBLE;
         }
-        // If disabled and "neighbours" are visible, set progress bar to INVISIBLE instead of GONE
-        // so layout weights still work.
-        return areAnyExpandedBottomActionsVisible() ? ConstraintSet.INVISIBLE : ConstraintSet.GONE;
-    }
-
-    private boolean areAnyExpandedBottomActionsVisible() {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        for (int id : MediaViewHolder.Companion.getExpandedBottomActionIds()) {
-            if (expandedSet.getVisibility(id) == ConstraintSet.VISIBLE) {
-                return true;
-            }
-        }
-        return false;
+        // Set progress bar to INVISIBLE to keep the positions of text and buttons similar to the
+        // original positions when seekbar is enabled.
+        return ConstraintSet.INVISIBLE;
     }
 
     private void setGenericButton(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 647beb9..b10abb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -48,52 +48,66 @@
     /** All commands for the sender device. */
     inner class SenderCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            val commandName = args[1]
+            if (args.size < 2) {
+                help(pw)
+                return
+            }
+
+            val senderArgs = processArgs(args)
+
             @StatusBarManager.MediaTransferSenderState
             val displayState: Int?
             try {
-                displayState = ChipStateSender.getSenderStateIdFromName(commandName)
+                displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
             } catch (ex: IllegalArgumentException) {
-                pw.println("Invalid command name $commandName")
+                pw.println("Invalid command name ${senderArgs.commandName}")
                 return
             }
 
             @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                     as StatusBarManager
-            val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
+            val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
                     .addFeature("feature")
-            val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
-            if (useAppIcon) {
+            if (senderArgs.useAppIcon) {
                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
+            var undoExecutor: Executor? = null
+            var undoRunnable: Runnable? = null
+            if (isSucceededState(displayState) && senderArgs.showUndo) {
+                undoExecutor = mainExecutor
+                undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+            }
+
             statusBarManager.updateMediaTapToTransferSenderDisplay(
                 displayState,
                 routeInfo.build(),
-                getUndoExecutor(displayState),
-                getUndoCallback(displayState)
+                undoExecutor,
+                undoRunnable,
             )
         }
 
-        private fun getUndoExecutor(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Executor? {
-            return if (isSucceededState(displayState)) {
-                mainExecutor
-            } else {
-                null
-            }
-        }
+        private fun processArgs(args: List<String>): SenderArgs {
+            val senderArgs = SenderArgs(
+                deviceName = args[0],
+                commandName = args[1],
+            )
 
-        private fun getUndoCallback(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Runnable? {
-            return if (isSucceededState(displayState)) {
-                Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
-            } else {
-                null
+            if (args.size == 2) {
+                return senderArgs
             }
+
+            // Process any optional arguments
+            args.subList(2, args.size).forEach {
+                when {
+                    it == "useAppIcon=false" -> senderArgs.useAppIcon = false
+                    it == "showUndo=false" -> senderArgs.showUndo = false
+                    it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
+                }
+            }
+
+            return senderArgs
         }
 
         private fun isSucceededState(
@@ -106,14 +120,31 @@
         }
 
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
-                    "<deviceName> <chipState> useAppIcon=[true|false] <id>")
+            pw.println(
+                "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
+                "<deviceName> <chipState> " +
+                "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
+            )
+            pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
         }
     }
 
+    private data class SenderArgs(
+        val deviceName: String,
+        val commandName: String,
+        var id: String = "id",
+        var useAppIcon: Boolean = true,
+        var showUndo: Boolean = true,
+    )
+
     /** All commands for the receiver device. */
     inner class ReceiverCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
+            if (args.isEmpty()) {
+                help(pw)
+                return
+            }
+
             val commandName = args[0]
             @StatusBarManager.MediaTransferReceiverState
             val displayState: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d6..b55bedd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@
         )
     }
 
+    /**
+     * Logs an invalid sender state transition error in trying to update to [desiredState].
+     *
+     * @param currentState the previous state of the chip.
+     * @param desiredState the new state of the chip.
+     */
+    fun logInvalidStateTransitionError(
+        currentState: String,
+        desiredState: String
+    ) {
+        buffer.log(
+                tag,
+                LogLevel.ERROR,
+                {
+                    str1 = currentState
+                    str2 = desiredState
+                },
+                { "Cannot display state=$str2 after state=$str1; invalid transition" }
+        )
+    }
+
     /** Logs that we couldn't find information for [packageName]. */
     fun logPackageNotFound(packageName: String) {
         buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index af7317c..1f27582 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -56,7 +56,12 @@
         R.string.media_move_closer_to_start_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@
         R.string.media_move_closer_to_end_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+                    nextState == TRANSFER_TO_RECEIVER_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer to the receiver device has failed. */
     TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer back to this device has failed. */
     TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that this device is far away from any receiver device. */
     FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@
             throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
                 "so its string should never be fetched")
         }
+
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState.transferStatus == TransferStatus.NOT_STARTED ||
+                    nextState.transferStatus == TransferStatus.IN_PROGRESS
+        }
     };
 
     /**
@@ -175,6 +227,8 @@
         return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
     }
 
+    abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
     companion object {
         /**
          * Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@
          */
         @StatusBarManager.MediaTransferSenderState
         fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+        /**
+         * Validates the transition from a chip state to another.
+         *
+         * @param currentState is the current state of the chip.
+         * @param desiredState is the desired state of the chip.
+         * @return true if the transition from [currentState] to [desiredState] is valid, and false
+         * otherwise.
+         */
+        fun isValidStateTransition(
+                currentState: ChipStateSender?,
+                desiredState: ChipStateSender,
+        ): Boolean {
+            // Far from receiver is the default state.
+            if (currentState == null) {
+                return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+            }
+
+            // No change in state is valid.
+            if (currentState == desiredState) {
+                return true
+            }
+
+            return currentState.isValidNextState(desiredState)
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index bb7bc6f..e34b0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -52,6 +52,8 @@
 ) : CoreStartable {
 
     private var displayedState: ChipStateSender? = null
+    // A map to store current chip state per id.
+    private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
 
     private val commandQueueCallbacks =
         object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@
             logger.logStateChangeError(displayState)
             return
         }
+
+        val currentState = stateMap[routeInfo.id]
+        if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+            // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+            logger.logInvalidStateTransitionError(
+                currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+                chipState.name
+            )
+            return
+        }
         uiEventLogger.logSenderStateChange(chipState)
 
+        stateMap.put(routeInfo.id, chipState)
         if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+            // No need to store the state since it is the default state
+            stateMap.remove(routeInfo.id)
             // Return early if we're not displaying a chip anyway
             val currentDisplayedState = displayedState ?: return
 
@@ -119,7 +134,7 @@
                     context,
                     logger,
                 )
-            )
+            ) { stateMap.remove(routeInfo.id) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3fd1aa7..e2f55f0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -145,7 +145,7 @@
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
                 + " willApplyConfigToNavbars=" + willApplyConfig
                 + " navBarCount=" + mNavigationBars.size());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 26d3902..f97385b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -977,7 +977,7 @@
         }
 
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
                 + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
         updateDisplaySize();
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index b964b76..6dd60d0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.UserManager
-import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -45,15 +47,22 @@
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    fun handleSystemKey(keyCode: Int) {
+    /**
+     * Shows a note task. How the task is shown will depend on when the method is invoked.
+     *
+     * If in multi-window mode, notes will open as a full screen experience. That is particularly
+     * important for Large screen devices. These devices may support a taskbar that let users to
+     * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour.
+     *
+     * If the keyguard is locked, notes will open as a full screen experience. A locked device has
+     * no contextual information which let us use the whole screen space available.
+     *
+     * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+     * That will let users open other apps in full screen, and take contextual notes.
+     */
+    fun showNoteTask(isInMultiWindowMode: Boolean = false) {
         if (!isEnabled) return
 
-        if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
-            showNoteTask()
-        }
-    }
-
-    private fun showNoteTask() {
         val bubbles = optionalBubbles.getOrNull() ?: return
         val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
@@ -62,11 +71,35 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        if (keyguardManager.isKeyguardLocked) {
+        if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
             context.startActivity(intent)
         } else {
             // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
             bubbles.showAppBubble(intent)
         }
     }
+
+    /**
+     * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
+     * component to [value].
+     *
+     * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
+     * Widget Picker to all users.
+     */
+    fun setNoteTaskShortcutEnabled(value: Boolean) {
+        val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+
+        val enabledState =
+            if (value) {
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+            } else {
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+            }
+
+        context.packageManager.setComponentEnabledSetting(
+            componentName,
+            enabledState,
+            PackageManager.DONT_KILL_APP,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 0a5b600..d14b7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.notetask
 
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
 import com.android.wm.shell.bubbles.Bubbles
-import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
 
@@ -27,15 +28,18 @@
 @Inject
 constructor(
     private val optionalBubbles: Optional<Bubbles>,
-    private val lazyNoteTaskController: Lazy<NoteTaskController>,
+    private val noteTaskController: NoteTaskController,
     private val commandQueue: CommandQueue,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    private val callbacks =
+    @VisibleForTesting
+    val callbacks =
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
-                lazyNoteTaskController.get().handleSystemKey(keyCode)
+                if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+                    noteTaskController.showNoteTask()
+                }
             }
         }
 
@@ -43,5 +47,6 @@
         if (isEnabled && optionalBubbles.isPresent) {
             commandQueue.addCallback(callbacks)
         }
+        noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 035396a..8bdf319 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -16,32 +16,47 @@
 
 package com.android.systemui.notetask
 
+import android.app.Activity
 import android.app.KeyguardManager
 import android.content.Context
 import android.os.UserManager
 import androidx.core.content.getSystemService
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import java.util.*
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
 
 /** Compose all dependencies required by Note Task feature. */
 @Module
-internal class NoteTaskModule {
+internal interface NoteTaskModule {
 
-    @[Provides NoteTaskEnabledKey]
-    fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
-        return featureFlags.isEnabled(Flags.NOTE_TASKS)
-    }
+    @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
+    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
 
-    @Provides
-    fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
-        return Optional.ofNullable(context.getSystemService())
-    }
+    @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
+    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
 
-    @Provides
-    fun provideOptionalUserManager(context: Context): Optional<UserManager> {
-        return Optional.ofNullable(context.getSystemService())
+    companion object {
+
+        @[Provides NoteTaskEnabledKey]
+        fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+            return featureFlags.isEnabled(Flags.NOTE_TASKS)
+        }
+
+        @Provides
+        fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
+
+        @Provides
+        fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
new file mode 100644
index 0000000..f6a623e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.notetask.shortcut
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.DrawableRes
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new
+ * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be
+ * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish.
+ *
+ * @see <a
+ * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
+ * a custom shortcut activity</a>
+ */
+internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val intent =
+            createShortcutIntent(
+                id = SHORTCUT_ID,
+                shortLabel = getString(R.string.note_task_button_label),
+                intent = LaunchNoteTaskActivity.newIntent(context = this),
+                iconResource = R.drawable.ic_note_task_button,
+            )
+        setResult(Activity.RESULT_OK, intent)
+
+        finish()
+    }
+
+    private fun createShortcutIntent(
+        id: String,
+        shortLabel: String,
+        intent: Intent,
+        @DrawableRes iconResource: Int,
+    ): Intent {
+        val shortcutInfo =
+            ShortcutInfoCompat.Builder(this, id)
+                .setIntent(intent)
+                .setShortLabel(shortLabel)
+                .setLongLived(true)
+                .setIcon(IconCompat.createWithResource(this, iconResource))
+                .build()
+
+        return ShortcutManagerCompat.createShortcutResultIntent(
+            this,
+            shortcutInfo,
+        )
+    }
+
+    private companion object {
+        private const val SHORTCUT_ID = "note-task-shortcut-id"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
new file mode 100644
index 0000000..47fe676
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.notetask.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskIntentResolver
+import javax.inject.Inject
+
+/** Activity responsible for launching the note experience, and finish. */
+internal class LaunchNoteTaskActivity
+@Inject
+constructor(
+    private val noteTaskController: NoteTaskController,
+) : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        noteTaskController.showNoteTask(isInMultiWindowMode)
+
+        finish()
+    }
+
+    companion object {
+
+        /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
+        fun newIntent(context: Context): Intent {
+            return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+                // Intent's action must be set in shortcuts, or an exception will be thrown.
+                // TODO(b/254606432): Use Intent.ACTION_NOTES instead.
+                action = NoteTaskIntentResolver.NOTES_ACTION
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 67bc769..5dbf0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -247,7 +247,7 @@
 
         Icon icon;
         ContentDescription contentDescription = null;
-        if (isParentalControlsEnabled) {
+        if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
             icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
         } else if (vpnName != null || vpnNameWorkProfile != null) {
             if (securityModel.isVpnBranded()) {
@@ -476,7 +476,7 @@
     @VisibleForTesting
     View createDialogView(Context quickSettingsContext) {
         if (mSecurityController.isParentalControlsEnabled()) {
-            return createParentalControlsDialogView();
+            return createParentalControlsDialogView(quickSettingsContext);
         }
         return createOrganizationDialogView(quickSettingsContext);
     }
@@ -579,8 +579,8 @@
         return dialogView;
     }
 
-    private View createParentalControlsDialogView() {
-        View dialogView = LayoutInflater.from(mContext)
+    private View createParentalControlsDialogView(Context quickSettingsContext) {
+        View dialogView = LayoutInflater.from(quickSettingsContext)
                 .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
 
         DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 350d8b0..9542a02 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -188,8 +188,6 @@
         int mWifiSignalIconId;
         @Nullable
         String mSsid;
-        boolean mActivityIn;
-        boolean mActivityOut;
         @Nullable
         String mWifiSignalContentDescription;
         boolean mIsTransient;
@@ -207,8 +205,6 @@
                     .append(",mConnected=").append(mConnected)
                     .append(",mWifiSignalIconId=").append(mWifiSignalIconId)
                     .append(",mSsid=").append(mSsid)
-                    .append(",mActivityIn=").append(mActivityIn)
-                    .append(",mActivityOut=").append(mActivityOut)
                     .append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
                     .append(",mIsTransient=").append(mIsTransient)
                     .append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -226,8 +222,6 @@
         CharSequence mDataContentDescription;
         int mMobileSignalIconId;
         int mQsTypeIcon;
-        boolean mActivityIn;
-        boolean mActivityOut;
         boolean mNoSim;
         boolean mRoaming;
         boolean mMultipleSubs;
@@ -243,8 +237,6 @@
                 .append(",mDataContentDescription=").append(mDataContentDescription)
                 .append(",mMobileSignalIconId=").append(mMobileSignalIconId)
                 .append(",mQsTypeIcon=").append(mQsTypeIcon)
-                .append(",mActivityIn=").append(mActivityIn)
-                .append(",mActivityOut=").append(mActivityOut)
                 .append(",mNoSim=").append(mNoSim)
                 .append(",mRoaming=").append(mRoaming)
                 .append(",mMultipleSubs=").append(mMultipleSubs)
@@ -275,8 +267,6 @@
             mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
             mWifiInfo.mEnabled = indicators.enabled;
             mWifiInfo.mSsid = indicators.description;
-            mWifiInfo.mActivityIn = indicators.activityIn;
-            mWifiInfo.mActivityOut = indicators.activityOut;
             mWifiInfo.mIsTransient = indicators.isTransient;
             mWifiInfo.mStatusLabel = indicators.statusLabel;
             refreshState(mWifiInfo);
@@ -297,8 +287,6 @@
                     ? indicators.typeContentDescriptionHtml : null;
             mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
             mCellularInfo.mQsTypeIcon = indicators.qsType;
-            mCellularInfo.mActivityIn = indicators.activityIn;
-            mCellularInfo.mActivityOut = indicators.activityOut;
             mCellularInfo.mRoaming = indicators.roaming;
             mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
             refreshState(mCellularInfo);
@@ -428,8 +416,6 @@
         state.state = Tile.STATE_ACTIVE;
         state.dualTarget = true;
         state.value = cb.mEnabled;
-        state.activityIn = cb.mEnabled && cb.mActivityIn;
-        state.activityOut = cb.mEnabled && cb.mActivityOut;
         final StringBuffer minimalContentDescription = new StringBuffer();
         final StringBuffer minimalStateDescription = new StringBuffer();
         final Resources r = mContext.getResources();
@@ -503,8 +489,6 @@
         boolean mobileDataEnabled = mDataController.isMobileDataSupported()
                 && mDataController.isMobileDataEnabled();
         state.value = mobileDataEnabled;
-        state.activityIn = mobileDataEnabled && cb.mActivityIn;
-        state.activityOut = mobileDataEnabled && cb.mActivityOut;
         state.expandedAccessibilityClassName = Switch.class.getName();
 
         if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 547b496..00d129a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -565,13 +565,25 @@
         statusBarWinController.registerCallback(mStatusBarWindowCallback);
         mScreenshotHelper = new ScreenshotHelper(context);
 
-        // Listen for tracing state changes
         commandQueue.addCallback(new CommandQueue.Callbacks() {
+
+            // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
                 mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
                         .commitUpdate(mContext.getDisplayId());
             }
+
+            @Override
+            public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+                if (mOverviewProxy != null) {
+                    try {
+                        mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+                    } catch (RemoteException e) {
+                        Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
+                    }
+                }
+            }
         });
         mCommandQueue = commandQueue;
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3b40aeb..507dec6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -465,7 +465,11 @@
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
 
-    /** Indicates drag starting height when swiping down or up on heads-up notifications */
+    /**
+     * Indicates drag starting height when swiping down or up on heads-up notifications.
+     * This usually serves as a threshold from when shade expansion should really start. Otherwise
+     * this value would be height of shade and it will be immediately expanded to some extent.
+     */
     private int mHeadsUpStartHeight;
     private HeadsUpTouchHelper mHeadsUpTouchHelper;
     private boolean mListenForHeadsUp;
@@ -3412,9 +3416,12 @@
                 && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
         boolean goingBetweenClosedShadeAndExpandedQs =
                 mQsExpandImmediate || collapsingShadeFromExpandedQs;
-        // we don't want to update QS expansion when HUN is visible because then the whole shade is
-        // initially hidden, even though it has non-zero height
-        if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
+        // in split shade we react when HUN is visible only if shade height is over HUN start
+        // height - which means user is swiping down. Otherwise shade QS will either not show at all
+        // with HUN movement or it will blink when touching HUN initially
+        boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+                || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+        if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
             float qsExpansionFraction;
             if (mSplitShadeEnabled) {
                 qsExpansionFraction = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1dd3a96..590a04a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -164,6 +164,8 @@
     private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
     private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+    private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+    private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -478,6 +480,16 @@
          * @see IStatusBar#showRearDisplayDialog
          */
         default void showRearDisplayDialog(int currentBaseState) {}
+
+        /**
+         * @see IStatusBar#goToFullscreenFromSplit
+         */
+        default void goToFullscreenFromSplit() {}
+
+        /**
+         * @see IStatusBar#enterStageSplitFromRunningApp
+         */
+        default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
     }
 
     public CommandQueue(Context context) {
@@ -1239,6 +1251,14 @@
     }
 
     @Override
+    public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
+                    leftOrTop).sendToTarget();
+        }
+    }
+
+    @Override
     public void requestAddTile(
             @NonNull ComponentName componentName,
             @NonNull CharSequence appName,
@@ -1299,6 +1319,11 @@
                 .sendToTarget();
     }
 
+    @Override
+    public void goToFullscreenFromSplit() {
+        mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1738,6 +1763,17 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
                     }
+                    break;
+                case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).goToFullscreenFromSplit();
+                    }
+                    break;
+                case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d7eddf5..56c34a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,6 +39,7 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -66,6 +67,8 @@
     // the next icon has translated out of the way, to avoid overlapping.
     private static final Interpolator ICON_ALPHA_INTERPOLATOR =
             new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
 
     private NotificationIconContainer mShelfIcons;
     private int[] mTmp = new int[2];
@@ -112,19 +115,24 @@
         setClipChildren(false);
         setClipToPadding(false);
         mShelfIcons.setIsStaticLayout(false);
-        requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
-        requestTopRoundness(1f, false, SourceType.DefaultValue);
+        requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false);
 
-        // Setting this to first in section to get the clipping to the top roundness correct. This
-        // value determines the way we are clipping to the top roundness of the overall shade
-        setFirstInSection(true);
+        if (!mUseRoundnessSourceTypes) {
+            // Setting this to first in section to get the clipping to the top roundness correct.
+            // This value determines the way we are clipping to the top roundness of the overall
+            // shade
+            setFirstInSection(true);
+        }
         updateResources();
     }
 
     public void bind(AmbientState ambientState,
-            NotificationStackScrollLayoutController hostLayoutController) {
+                     NotificationStackScrollLayoutController hostLayoutController) {
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
+        hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
+            child.requestRoundnessReset(SHELF_SCROLL);
+        });
     }
 
     private void updateResources() {
@@ -185,9 +193,11 @@
                 + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
     }
 
-    /** Update the state of the shelf. */
+    /**
+     * Update the state of the shelf.
+     */
     public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                            AmbientState ambientState) {
         ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
         ShelfState viewState = (ShelfState) getViewState();
         if (mShowNotificationShelf && lastView != null) {
@@ -246,7 +256,7 @@
 
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
-     * @param shortestWidth Shortest width to use for lockscreen shelf
+     * @param shortestWidth   Shortest width to use for lockscreen shelf
      */
     @VisibleForTesting
     public void updateActualWidth(float fractionToShade, float shortestWidth) {
@@ -281,9 +291,9 @@
 
     /**
      * @param localX Click x from left of screen
-     * @param slop Margin of error within which we count x for valid click
-     * @param left Left of shelf, from left of screen
-     * @param right Right of shelf, from left of screen
+     * @param slop   Margin of error within which we count x for valid click
+     * @param left   Left of shelf, from left of screen
+     * @param right  Right of shelf, from left of screen
      * @return Whether click x was in view
      */
     @VisibleForTesting
@@ -293,8 +303,8 @@
 
     /**
      * @param localY Click y from top of shelf
-     * @param slop Margin of error within which we count y for valid click
-     * @param top Top of shelf
+     * @param slop   Margin of error within which we count y for valid click
+     * @param top    Top of shelf
      * @param bottom Height of shelf
      * @return Whether click y was in view
      */
@@ -306,7 +316,7 @@
     /**
      * @param localX Click x
      * @param localY Click y
-     * @param slop Margin of error for valid click
+     * @param slop   Margin of error for valid click
      * @return Whether this click was on the visible (non-clipped) part of the shelf
      */
     @Override
@@ -478,13 +488,15 @@
         }
     }
 
-    private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart,
+    private void updateCornerRoundnessOnScroll(
+            ActivatableNotificationView anv,
+            float viewStart,
             float shelfStart) {
 
         final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard()
                 && !mAmbientState.isShadeExpanded()
                 && anv instanceof ExpandableNotificationRow
-                && ((ExpandableNotificationRow) anv).isHeadsUp();
+                && anv.isHeadsUp();
 
         final boolean isHunGoingToShade = mAmbientState.isShadeExpanded()
                 && anv == mAmbientState.getTrackedHeadsUpRow();
@@ -506,41 +518,40 @@
                 * mAmbientState.getExpansionFraction();
         final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
 
-        if (viewEnd >= cornerAnimationTop) {
-            // Round bottom corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
-                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestBottomRoundness(
-                    /* value = */ anv.isLastInSection() ? 1f : changeFraction,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
-
-        } else if (viewEnd < cornerAnimationTop) {
-            // Fast scroll skips frames and leaves corners with unfinished rounding.
-            // Reset top and bottom corners outside of animation bounds.
-            anv.requestBottomRoundness(
-                    /* value = */ anv.isLastInSection() ? 1f : 0f,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
+        final SourceType sourceType;
+        if (mUseRoundnessSourceTypes) {
+            sourceType = SHELF_SCROLL;
+        } else {
+            sourceType = LegacySourceType.OnScroll;
         }
 
-        if (viewStart >= cornerAnimationTop) {
+        final float topValue;
+        if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) {
+            topValue = 1f;
+        } else if (viewStart >= cornerAnimationTop) {
             // Round top corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
+            topValue = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestTopRoundness(
-                    /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
-
-        } else if (viewStart < cornerAnimationTop) {
+        } else {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
-            anv.requestTopRoundness(
-                    /* value = */ anv.isFirstInSection() ? 1f : 0f,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
+            topValue = 0f;
         }
+        anv.requestTopRoundness(topValue, sourceType, /* animate = */ false);
+
+        final float bottomValue;
+        if (!mUseRoundnessSourceTypes && anv.isLastInSection()) {
+            bottomValue = 1f;
+        } else if (viewEnd >= cornerAnimationTop) {
+            // Round bottom corners within animation bounds
+            bottomValue = MathUtils.saturate(
+                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+        } else {
+            // Fast scroll skips frames and leaves corners with unfinished rounding.
+            // Reset top and bottom corners outside of animation bounds.
+            bottomValue = 0f;
+        }
+        anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
     /**
@@ -626,10 +637,11 @@
 
     /**
      * Update the clipping of this view.
+     *
      * @return the amount that our own top should be clipped
      */
     private int updateNotificationClipHeight(ExpandableView view,
-            float notificationClipEnd, int childIndex) {
+                                             float notificationClipEnd, int childIndex) {
         float viewEnd = view.getTranslationY() + view.getActualHeight();
         boolean isPinned = (view.isPinned() || view.isHeadsUpAnimatingAway())
                 && !mAmbientState.isDozingAndNotPulsing(view);
@@ -657,7 +669,7 @@
 
     @Override
     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
-            int outlineTranslation) {
+                                       int outlineTranslation) {
         if (!mHasItemsInStableShelf) {
             shadowIntensity = 0.0f;
         }
@@ -665,18 +677,24 @@
     }
 
     /**
-     * @param i Index of the view in the host layout.
-     * @param view The current ExpandableView.
-     * @param scrollingFast Whether we are scrolling fast.
+     * @param i                 Index of the view in the host layout.
+     * @param view              The current ExpandableView.
+     * @param scrollingFast     Whether we are scrolling fast.
      * @param expandingAnimated Whether we are expanding a notification.
-     * @param isLastChild Whether this is the last view.
-     * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+     * @param isLastChild       Whether this is the last view.
+     * @param shelfClipStart    The point at which notifications start getting clipped by the shelf.
      * @return The amount how much this notification is in the shelf.
-     *         0f is not in shelf. 1f is completely in shelf.
+     * 0f is not in shelf. 1f is completely in shelf.
      */
     @VisibleForTesting
-    public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
-            boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
+    public float getAmountInShelf(
+            int i,
+            ExpandableView view,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild,
+            float shelfClipStart
+    ) {
 
         // Let's calculate how much the view is in the shelf
         float viewStart = view.getTranslationY();
@@ -755,8 +773,13 @@
         return start;
     }
 
-    private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
-            boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
+    private void updateIconPositioning(
+            ExpandableView view,
+            float iconTransitionAmount,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild
+    ) {
         StatusBarIconView icon = view.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
@@ -817,7 +840,7 @@
                 || row.showingPulsing()
                 || row.getTranslationZ() > mAmbientState.getBaseZHeight();
 
-        iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
+        iconState.iconAppearAmount = iconState.hidden ? 0f : transitionAmount;
 
         // Fade in icons at shelf start
         // This is important for conversation icons, which are badged and need x reset
@@ -847,7 +870,7 @@
     }
 
     private float getFullyClosedTranslation() {
-        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+        return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
     }
 
     @Override
@@ -904,7 +927,7 @@
 
     /**
      * @return whether the shelf has any icons in it when a potential animation has finished, i.e
-     *         if the current state would be applied right now
+     * if the current state would be applied right now
      */
     public boolean hasItemsInStableShelf() {
         return mHasItemsInStableShelf;
@@ -962,7 +985,7 @@
 
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
+                               int oldTop, int oldRight, int oldBottom) {
         updateRelativeOffset();
     }
 
@@ -981,12 +1004,11 @@
 
     /**
      * This method resets the OnScroll roundness of a view to 0f
-     *
+     * <p>
      * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
      */
-    public static void resetOnScrollRoundness(ExpandableView expandableView) {
-        expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
-        expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+    public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) {
+        expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
     }
 
     public class ShelfState extends ExpandableViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 3b1fa17..bb84c75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -18,6 +18,8 @@
 
 import android.view.View;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
@@ -42,14 +44,17 @@
     private AmbientState mAmbientState;
 
     @Inject
-    public NotificationShelfController(NotificationShelf notificationShelf,
+    public NotificationShelfController(
+            NotificationShelf notificationShelf,
             ActivatableNotificationViewController activatableNotificationViewController,
             KeyguardBypassController keyguardBypassController,
-            SysuiStatusBarStateController statusBarStateController) {
+            SysuiStatusBarStateController statusBarStateController,
+            FeatureFlags featureFlags) {
         mView = notificationShelf;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
+        mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -88,7 +93,7 @@
 
     public @View.Visibility int getVisibility() {
         return mView.getVisibility();
-    };
+    }
 
     public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
         mView.setCollapsedIcons(notificationIcons);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 37d83f8..336356e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -78,6 +78,7 @@
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -192,6 +193,7 @@
     private final Executor mBgExecutor;
     // Handler that all callbacks are made on.
     private final CallbackHandler mCallbackHandler;
+    private final StatusBarPipelineFlags mStatusBarPipelineFlags;
 
     private int mEmergencySource;
     private boolean mIsEmergency;
@@ -242,6 +244,7 @@
             TelephonyListenerManager telephonyListenerManager,
             @Nullable WifiManager wifiManager,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             WifiStatusTrackerFactory trackerFactory,
@@ -260,6 +263,7 @@
                 bgExecutor,
                 callbackHandler,
                 accessPointController,
+                statusBarPipelineFlags,
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
@@ -287,6 +291,7 @@
             Executor bgExecutor,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DataUsageController dataUsageController,
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
@@ -308,6 +313,7 @@
         mBgLooper = bgLooper;
         mBgExecutor = bgExecutor;
         mCallbackHandler = callbackHandler;
+        mStatusBarPipelineFlags = statusBarPipelineFlags;
         mDataSaverController = new DataSaverControllerImpl(context);
         mBroadcastDispatcher = broadcastDispatcher;
         mMobileFactory = mobileFactory;
@@ -1333,7 +1339,7 @@
             mWifiSignalController.notifyListeners();
         }
         String sims = args.getString("sims");
-        if (sims != null) {
+        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1356,7 +1362,7 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null) {
+        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
@@ -1441,7 +1447,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null) {
+        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index ed7f648..0eb0000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -74,8 +74,8 @@
     @JvmDefault
     fun requestTopRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.topRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -105,6 +105,30 @@
     }
 
     /**
+     * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the
+     * view is shown.
+     *
+     * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value a value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestTopRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestTopRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
      * Request the bottom roundness [value] for a specific [sourceType].
      *
      * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
@@ -119,8 +143,8 @@
     @JvmDefault
     fun requestBottomRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.bottomRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -149,9 +173,101 @@
         return false
     }
 
+    /**
+     * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if
+     * the view is shown.
+     *
+     * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestBottomRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestBottomRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType].
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @param animate true if it should animate to that value.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+        animate: Boolean,
+    ): Boolean {
+        val hasTopChanged =
+            requestTopRoundness(value = top, sourceType = sourceType, animate = animate)
+        val hasBottomChanged =
+            requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate)
+        return hasTopChanged || hasBottomChanged
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view
+     * is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestRoundness(
+            top = top,
+            bottom = bottom,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown,
+        )
+    }
+
+    /**
+     * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param sourceType the source from which the request for roundness comes.
+     */
+    @JvmDefault
+    fun requestRoundnessReset(sourceType: SourceType) {
+        requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType)
+    }
+
     /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
     @JvmDefault
-    fun applyRoundness() {
+    fun applyRoundnessAndInvalidate() {
         roundableState.targetView.invalidate()
     }
 
@@ -227,7 +343,7 @@
     /** Set the current top roundness */
     internal fun setTopRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
     }
@@ -235,11 +351,19 @@
     /** Set the current bottom roundness */
     internal fun setBottomRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
     }
 
+    fun debugString() = buildString {
+        append("TargetView: ${targetView.hashCode()} ")
+        append("Top: $topRoundness ")
+        append(topRoundnessMap.map { "${it.key} ${it.value}" })
+        append(" Bottom: $bottomRoundness ")
+        append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+    }
+
     companion object {
         private val DURATION: AnimationProperties =
             AnimationProperties()
@@ -252,7 +376,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.topRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.top_roundess_animator_tag,
@@ -267,7 +391,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.bottomRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.bottom_roundess_animator_tag,
@@ -277,7 +401,31 @@
     }
 }
 
-enum class SourceType {
+/**
+ * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a
+ * private property of a class.
+ */
+interface SourceType {
+    companion object {
+        /**
+         * This is the most convenient way to define a new [SourceType].
+         *
+         * For example:
+         *
+         * ```kotlin
+         *     private val SECTION = SourceType.from("Section")
+         * ```
+         */
+        @JvmStatic
+        fun from(name: String) =
+            object : SourceType {
+                override fun toString() = name
+            }
+    }
+}
+
+@Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()"))
+enum class LegacySourceType : SourceType {
     DefaultValue,
     OnDismissAnimation,
     OnScroll,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d29298a..fbe88df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -39,9 +39,13 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
  * to implement dimming/activating on Keyguard for the double-tap gesture
@@ -91,6 +95,7 @@
             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
             = new PathInterpolator(0, 0, 0.5f, 1);
+    private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>();
     private int mTintedRippleColor;
     private int mNormalRippleColor;
     private Gefingerpoken mTouchHandler;
@@ -134,6 +139,7 @@
     private boolean mDismissed;
     private boolean mRefocusOnDismiss;
     private AccessibilityManager mAccessibilityManager;
+    protected boolean mUseRoundnessSourceTypes;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -613,9 +619,9 @@
     protected void resetAllContentAlphas() {}
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
+        super.applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -775,6 +781,33 @@
         mAccessibilityManager = accessibilityManager;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) {
+            for (SourceType sourceType : mOnDetachResetRoundness) {
+                requestRoundnessReset(sourceType);
+            }
+            mOnDetachResetRoundness.clear();
+        }
+    }
+
+    /**
+     * SourceType which should be reset when this View is detached
+     * @param sourceType will be reset on View detached
+     */
+    public void addOnDetachResetRoundness(SourceType sourceType) {
+        mOnDetachResetRoundness.add(sourceType);
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d7d5ac9..c7c1634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -143,6 +144,9 @@
     private static final int MENU_VIEW_INDEX = 0;
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+    private static final SourceType PINNED = SourceType.from("Pinned");
 
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
@@ -150,6 +154,7 @@
     private boolean mNotificationTranslationFinished = false;
     private boolean mIsSnoozed;
     private boolean mIsFaded;
+    private boolean mAnimatePinnedRoundness = false;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -376,7 +381,7 @@
 
     private float mTopRoundnessDuringLaunchAnimation;
     private float mBottomRoundnessDuringLaunchAnimation;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private float mSmallRoundness;
 
     /**
      * Returns whether the given {@code statusBarNotification} is a system notification.
@@ -844,7 +849,9 @@
         }
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(false, null);
-        row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+        if (!mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(0.0f, LegacySourceType.DefaultValue, /* animate = */ false);
+        }
     }
 
     /**
@@ -860,7 +867,10 @@
             if (child.keepInParentForDismissAnimation()) {
                 mChildrenContainer.removeNotification(child);
                 child.setIsChildInGroup(false, null);
-                child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+                if (!mUseRoundnessSourceTypes) {
+                    LegacySourceType sourceType = LegacySourceType.DefaultValue;
+                    child.requestBottomRoundness(0f, sourceType, /* animate = */ false);
+                }
                 child.setKeepInParentForDismissAnimation(false);
                 logKeepInParentChildDetached(child);
                 childCountChanged = true;
@@ -915,6 +925,9 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateBackgroundClipping();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
+        }
     }
 
     @Override
@@ -1033,6 +1046,16 @@
         if (isAboveShelf() != wasAboveShelf) {
             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
         }
+        if (mUseRoundnessSourceTypes) {
+            if (pinned) {
+                // Should be animated if someone explicitly set it to 0 and the row is shown.
+                boolean animated = mAnimatePinnedRoundness && isShown();
+                requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
+            } else {
+                requestRoundnessReset(PINNED);
+                mAnimatePinnedRoundness = true;
+            }
+        }
     }
 
     @Override
@@ -1607,6 +1630,8 @@
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
+        float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
+        mSmallRoundness = radius / getMaxRadius();
         initDimens();
     }
 
@@ -1839,7 +1864,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -2271,7 +2296,7 @@
 
     @Override
     public float getTopRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mTopRoundnessDuringLaunchAnimation;
         }
 
@@ -2280,7 +2305,7 @@
 
     @Override
     public float getBottomRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mBottomRoundnessDuringLaunchAnimation;
         }
 
@@ -3436,17 +3461,24 @@
     }
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyChildrenRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     private void applyChildrenRoundness() {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.requestBottomRoundness(
-                    getBottomRoundness(),
-                    /* animate = */ false,
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                mChildrenContainer.requestRoundness(
+                        /* top = */ getTopRoundness(),
+                        /* bottom = */ getBottomRoundness(),
+                        FROM_PARENT);
+            } else {
+                mChildrenContainer.requestBottomRoundness(
+                        getBottomRoundness(),
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ false);
+            }
         }
     }
 
@@ -3605,6 +3637,7 @@
             } else {
                 pw.println("no viewState!!!");
             }
+            pw.println("Roundness: " + getRoundableState().debugString());
 
             if (mIsSummaryWithChildren) {
                 pw.println();
@@ -3649,14 +3682,38 @@
         return mTargetPoint;
     }
 
+    /** Update the minimum roundness based on current state */
+    private void updateBaseRoundness() {
+        if (isChildInGroup()) {
+            requestRoundnessReset(BASE_VALUE);
+        } else {
+            requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
+        }
+    }
+
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    @Override
+    public void useRoundnessSourceTypes(boolean enabled) {
+        super.useRoundnessSourceTypes(enabled);
         if (mChildrenContainer != null) {
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        }
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 8a400d5..d113860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -255,8 +255,8 @@
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
             }
         });
-        mView.enableNotificationGroupCorner(
-                mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
+        mView.useRoundnessSourceTypes(
+                mFeatureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
     }
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2324627..0213b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -219,9 +219,9 @@
     }
 
     @Override
-    public void applyRoundness() {
+    public void applyRoundnessAndInvalidate() {
         invalidateOutline();
-        super.applyRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     protected void setBackgroundTop(int backgroundTop) {
@@ -233,7 +233,7 @@
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -241,7 +241,7 @@
         int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
         if (previousHeight != actualHeight) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -250,7 +250,7 @@
         int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
         if (previousAmount != clipTopAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -259,14 +259,14 @@
         int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
         if (previousAmount != clipBottomAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -280,7 +280,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -340,7 +340,7 @@
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     public Path getCustomClipPath(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f13e48d..1f664cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -71,6 +71,8 @@
     private View mFeedbackIcon;
     private boolean mIsLowPriority;
     private boolean mTransformLowPriorityTitle;
+    private boolean mUseRoundnessSourceTypes;
+    private RoundnessChangedListener mRoundnessChangedListener;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -117,6 +119,20 @@
         return mRoundableState;
     }
 
+    @Override
+    public void applyRoundnessAndInvalidate() {
+        if (mUseRoundnessSourceTypes && mRoundnessChangedListener != null) {
+            // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
+            // clip our canvas. So we should invalidate our parent.
+            mRoundnessChangedListener.applyRoundnessAndInvalidate();
+        }
+        Roundable.super.applyRoundnessAndInvalidate();
+    }
+
+    public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
+        mRoundnessChangedListener = listener;
+    }
+
     protected void resolveHeaderViews() {
         mIcon = mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -343,4 +359,23 @@
             }
         }
     }
+
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     *
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    /**
+     * Interface that handle the Roundness changes
+     */
+    public interface RoundnessChangedListener {
+        /**
+         * This method will be called when this class call applyRoundnessAndInvalidate()
+         */
+        void applyRoundnessAndInvalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index d43ca823..4a8e2db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.NotificationGroupingUtil;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.Roundable;
@@ -83,6 +84,7 @@
             return mAnimationFilter;
         }
     }.setDuration(200);
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(NCC)");
 
     private final List<View> mDividers = new ArrayList<>();
     private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
@@ -131,7 +133,7 @@
     private int mUntruncatedChildCount;
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private boolean mUseRoundnessSourceTypes;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -313,9 +315,12 @@
         row.setContentTransformationAmount(0, false /* isLastChild */);
         row.setNotificationFaded(mContainingNotificationIsFaded);
 
-        // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
-        // Here we should reset the `OnScroll` roundness only on top-level rows.
-        NotificationShelf.resetOnScrollRoundness(row);
+        if (!mUseRoundnessSourceTypes) {
+            // This is a workaround, the NotificationShelf should be the owner of `OnScroll`
+            // roundness.
+            // Here we should reset the `OnScroll` roundness only on top-level rows.
+            NotificationShelf.resetLegacyOnScrollRoundness(row);
+        }
 
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
@@ -323,6 +328,10 @@
             viewState.cancelAnimations(row);
             row.cancelAppearDrawing();
         }
+
+        if (mUseRoundnessSourceTypes) {
+            applyRoundnessAndInvalidate();
+        }
     }
 
     private void ensureRemovedFromTransientContainer(View v) {
@@ -356,6 +365,11 @@
         if (!row.isRemoved()) {
             mGroupingUtil.restoreChildNotification(row);
         }
+
+        if (mUseRoundnessSourceTypes) {
+            row.requestRoundnessReset(FROM_PARENT);
+            applyRoundnessAndInvalidate();
+        }
     }
 
     /**
@@ -382,6 +396,10 @@
                             getContext(),
                             mNotificationHeader,
                             mContainingNotification);
+            mNotificationHeaderWrapper.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+            if (mUseRoundnessSourceTypes) {
+                mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+            }
             addView(mNotificationHeader, 0);
             invalidate();
         } else {
@@ -419,6 +437,12 @@
                                 getContext(),
                                 mNotificationHeaderLowPriority,
                                 mContainingNotification);
+                mNotificationHeaderWrapperLowPriority.useRoundnessSourceTypes(
+                        mUseRoundnessSourceTypes
+                );
+                if (mUseRoundnessSourceTypes) {
+                    mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+                }
                 addView(mNotificationHeaderLowPriority, 0);
                 invalidate();
             } else {
@@ -841,7 +865,7 @@
 
             isCanvasChanged = true;
             canvas.save();
-            if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+            if (mUseRoundnessSourceTypes && translation != 0f) {
                 clipPath.offset(translation, 0f);
                 canvas.clipPath(clipPath);
                 clipPath.offset(-translation, 0f);
@@ -1392,24 +1416,28 @@
     }
 
     @Override
-    public void applyRoundness() {
-        Roundable.super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         boolean last = true;
         for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mAttachedChildren.get(i);
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
-            child.requestTopRoundness(
-                    /* value = */ 0f,
-                    /* animate = */ isShown(),
-                    SourceType.DefaultValue);
-            child.requestBottomRoundness(
-                    /* value = */ last ? getBottomRoundness() : 0f,
-                    /* animate = */ isShown(),
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        FROM_PARENT);
+            } else {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ isShown());
+            }
             last = false;
         }
+        Roundable.super.applyRoundnessAndInvalidate();
     }
 
     public void setHeaderVisibleAmount(float headerVisibleAmount) {
@@ -1467,10 +1495,17 @@
     }
 
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
+     *
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 6810055..fde8c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -25,6 +25,9 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -45,6 +48,7 @@
 public class NotificationRoundnessManager implements Dumpable {
 
     private static final String TAG = "NotificationRoundnessManager";
+    private static final SourceType DISMISS_ANIMATION = SourceType.from("DismissAnimation");
 
     private final ExpandableView[] mFirstInSectionViews;
     private final ExpandableView[] mLastInSectionViews;
@@ -63,12 +67,14 @@
     private ExpandableView mSwipedView = null;
     private Roundable mViewBeforeSwipedView = null;
     private Roundable mViewAfterSwipedView = null;
+    private boolean mUseRoundnessSourceTypes;
 
     @Inject
     NotificationRoundnessManager(
             NotificationSectionsFeatureManager sectionsFeatureManager,
             NotificationRoundnessLogger notifLogger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -76,6 +82,7 @@
         mTmpLastInSectionViews = new ExpandableView[numberOfSections];
         mNotifLogger = notifLogger;
         mDumpManager = dumpManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
 
         mDumpManager.registerDumpable(TAG, this);
     }
@@ -94,6 +101,7 @@
     }
 
     public void updateView(ExpandableView view, boolean animate) {
+        if (mUseRoundnessSourceTypes) return;
         boolean changed = updateViewWithoutCallback(view, animate);
         if (changed) {
             mRoundingChangedCallback.run();
@@ -110,6 +118,7 @@
     boolean updateViewWithoutCallback(
             ExpandableView view,
             boolean animate) {
+        if (mUseRoundnessSourceTypes) return false;
         if (view == null
                 || view == mViewBeforeSwipedView
                 || view == mViewAfterSwipedView) {
@@ -118,13 +127,13 @@
 
         final boolean isTopChanged = view.requestTopRoundness(
                 getRoundnessDefaultValue(view, true /* top */),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isBottomChanged = view.requestBottomRoundness(
                 getRoundnessDefaultValue(view, /* top = */ false),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isFirstInSection = isFirstInSection(view);
         final boolean isLastInSection = isLastInSection(view);
@@ -139,6 +148,7 @@
     }
 
     private boolean isFirstInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = 0; i < mFirstInSectionViews.length; i++) {
             if (view == mFirstInSectionViews[i]) {
                 return true;
@@ -148,6 +158,7 @@
     }
 
     private boolean isLastInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
             if (view == mLastInSectionViews[i]) {
                 return true;
@@ -160,9 +171,6 @@
             Roundable viewBefore,
             ExpandableView viewSwiped,
             Roundable viewAfter) {
-        final boolean animate = true;
-        final SourceType source = SourceType.OnDismissAnimation;
-
         // This method requires you to change the roundness of the current View targets and reset
         // the roundness of the old View targets (if any) to 0f.
         // To avoid conflicts, it generates a set of old Views and removes the current Views
@@ -172,31 +180,34 @@
         if (mSwipedView != null) oldViews.add(mSwipedView);
         if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
 
+        final SourceType source;
+        if (mUseRoundnessSourceTypes) {
+            source = DISMISS_ANIMATION;
+        } else {
+            source = LegacySourceType.OnDismissAnimation;
+        }
+
         mViewBeforeSwipedView = viewBefore;
         if (viewBefore != null) {
             oldViews.remove(viewBefore);
-            viewBefore.requestTopRoundness(0f, animate, source);
-            viewBefore.requestBottomRoundness(1f, animate, source);
+            viewBefore.requestRoundness(/* top = */ 0f, /* bottom = */ 1f, source);
         }
 
         mSwipedView = viewSwiped;
         if (viewSwiped != null) {
             oldViews.remove(viewSwiped);
-            viewSwiped.requestTopRoundness(1f, animate, source);
-            viewSwiped.requestBottomRoundness(1f, animate, source);
+            viewSwiped.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, source);
         }
 
         mViewAfterSwipedView = viewAfter;
         if (viewAfter != null) {
             oldViews.remove(viewAfter);
-            viewAfter.requestTopRoundness(1f, animate, source);
-            viewAfter.requestBottomRoundness(0f, animate, source);
+            viewAfter.requestRoundness(/* top = */ 1f, /* bottom = */ 0f, source);
         }
 
         // After setting the current Views, reset the views that are still present in the set.
         for (Roundable oldView : oldViews) {
-            oldView.requestTopRoundness(0f, animate, source);
-            oldView.requestBottomRoundness(0f, animate, source);
+            oldView.requestRoundnessReset(source);
         }
     }
 
@@ -204,7 +215,23 @@
         mIsClearAllInProgress = isClearingAll;
     }
 
+    /**
+     * Check if "Clear all" notifications is in progress.
+     */
+    public boolean isClearAllInProgress() {
+        return mIsClearAllInProgress;
+    }
+
+    /**
+     * Check if we can request the `Pulsing` roundness for notification.
+     */
+    public boolean shouldRoundNotificationPulsing() {
+        return mRoundForPulsingViews;
+    }
+
     private float getRoundnessDefaultValue(Roundable view, boolean top) {
+        if (mUseRoundnessSourceTypes) return 0f;
+
         if (view == null) {
             return 0f;
         }
@@ -250,6 +277,7 @@
     }
 
     public void setExpanded(float expandedHeight, float appearFraction) {
+        if (mUseRoundnessSourceTypes) return;
         mExpanded = expandedHeight != 0.0f;
         mAppearFraction = appearFraction;
         if (mTrackedHeadsUp != null) {
@@ -258,6 +286,7 @@
     }
 
     public void updateRoundedChildren(NotificationSection[] sections) {
+        if (mUseRoundnessSourceTypes) return;
         boolean anyChanged = false;
         for (int i = 0; i < sections.length; i++) {
             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
@@ -280,6 +309,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (ExpandableView oldView : oldViews) {
             if (oldView != null) {
@@ -313,6 +343,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (NotificationSection section : sections) {
             ExpandableView newView =
@@ -339,6 +370,15 @@
         mAnimatedChildren = animatedChildren;
     }
 
+    /**
+     * Check if the view should be animated
+     * @param view target view
+     * @return true, if is in the AnimatedChildren set
+     */
+    public boolean isAnimatedChild(ExpandableView view) {
+        return mAnimatedChildren.contains(view);
+    }
+
     public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
         mRoundingChangedCallback = roundingChangedCallback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index a1b77ac..070b439 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,8 +19,11 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -44,12 +47,16 @@
     private val keyguardMediaController: KeyguardMediaController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val mediaContainerController: MediaContainerController,
+    private val notificationRoundnessManager: NotificationRoundnessManager,
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
     @PeopleHeader private val peopleHeaderController: SectionHeaderController,
     @AlertingHeader private val alertingHeaderController: SectionHeaderController,
-    @SilentHeader private val silentHeaderController: SectionHeaderController
+    @SilentHeader private val silentHeaderController: SectionHeaderController,
+    featureFlags: FeatureFlags
 ) : SectionProvider {
 
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
+
     private val configurationListener = object : ConfigurationController.ConfigurationListener {
         override fun onLocaleListChanged() {
             reinflateViews()
@@ -177,11 +184,49 @@
                         size = sections.size,
                         operation = SectionBounds::addNotif
                 )
+
+        // Build a set of the old first/last Views of the sections
+        val oldFirstChildren = sections.mapNotNull { it.firstVisibleChild }.toSet().toMutableSet()
+        val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()
+
         // Update each section with the associated boundary, tracking if there was a change
         val changed = sections.fold(false) { changed, section ->
             val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
-            bounds.updateSection(section) || changed
+            val isSectionChanged = bounds.updateSection(section)
+            isSectionChanged || changed
         }
+
+        if (useRoundnessSourceTypes) {
+            val newFirstChildren = sections.mapNotNull { it.firstVisibleChild }
+            val newLastChildren = sections.mapNotNull { it.lastVisibleChild }
+
+            // Update the roundness of Views that weren't already in the first/last position
+            newFirstChildren.forEach { firstChild ->
+                val wasFirstChild = oldFirstChildren.remove(firstChild)
+                if (!wasFirstChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(firstChild)
+                    val animated = firstChild.isShown && notAnimatedChild
+                    firstChild.requestTopRoundness(1f, SECTION, animated)
+                }
+            }
+            newLastChildren.forEach { lastChild ->
+                val wasLastChild = oldLastChildren.remove(lastChild)
+                if (!wasLastChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(lastChild)
+                    val animated = lastChild.isShown && notAnimatedChild
+                    lastChild.requestBottomRoundness(1f, SECTION, animated)
+                }
+            }
+
+            // The Views left in the set are no longer in the first/last position
+            oldFirstChildren.forEach { noMoreFirstChild ->
+                noMoreFirstChild.requestTopRoundness(0f, SECTION)
+            }
+            oldLastChildren.forEach { noMoreLastChild ->
+                noMoreLastChild.requestBottomRoundness(0f, SECTION)
+            }
+        }
+
         if (DEBUG) {
             logSections(sections)
         }
@@ -215,5 +260,6 @@
     companion object {
         private const val TAG = "NotifSectionsManager"
         private const val DEBUG = false
+        private val SECTION = SourceType.from("Section")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7c3e52c..21e2bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -191,10 +191,13 @@
 
     private final boolean mDebugLines;
     private Paint mDebugPaint;
-    /** Used to track the Y positions that were already used to draw debug text labels. */
+    /**
+     * Used to track the Y positions that were already used to draw debug text labels.
+     */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
     private final boolean mSimplifiedAppearFraction;
+    private final boolean mUseRoundnessSourceTypes;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -355,15 +358,14 @@
     private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
-    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
-            = () -> {
-                updateViewShadows();
-                return true;
-            };
+    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
+        updateViewShadows();
+        return true;
+    };
     private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
-                updateBackground();
-                return true;
-            };
+        updateBackground();
+        return true;
+    };
     private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -419,7 +421,8 @@
      */
     private int mMaxDisplayedNotifications = -1;
     private float mKeyguardBottomPadding = -1;
-    @VisibleForTesting int mStatusBarHeight;
+    @VisibleForTesting
+    int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
     private boolean mIsClipped;
@@ -568,6 +571,8 @@
 
     @Nullable
     private OnClickListener mManageButtonClickListener;
+    @Nullable
+    private OnNotificationRemovedListener mOnNotificationRemovedListener;
 
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
@@ -576,6 +581,7 @@
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -741,7 +747,7 @@
     }
 
     private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
-            boolean expected, boolean actual) {
+                                                 boolean expected, boolean actual) {
         if (mLogger == null) return;
         mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
     }
@@ -868,13 +874,13 @@
 
     /**
      * Draws round rects for each background section.
-     *
+     * <p>
      * We want to draw a round rect for each background section as defined by {@link #mSections}.
      * However, if two sections are directly adjacent with no gap between them (e.g. on the
      * lockscreen where the shelf can appear directly below the high priority section, or while
      * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
      * section), we don't want to round the adjacent corners.
-     *
+     * <p>
      * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
      * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
      * This method tracks the top of each rect we need to draw, then iterates through the visible
@@ -883,7 +889,7 @@
      * the current section.  When we're done iterating we will always have one rect left to draw.
      */
     private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
-            int animationYOffset) {
+                                     int animationYOffset) {
         int backgroundRectTop = top;
         int lastSectionBottom =
                 mSections[0].getCurrentBounds().bottom + animationYOffset;
@@ -974,7 +980,7 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void initView(Context context, NotificationSwipeHelper swipeHelper,
-            NotificationStackSizeCalculator notificationStackSizeCalculator) {
+                  NotificationStackSizeCalculator notificationStackSizeCalculator) {
         mScroller = new OverScroller(getContext());
         mSwipeHelper = swipeHelper;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -1304,18 +1310,19 @@
     /**
      * @return Whether we should skip stack height updates.
      * True when
-     *      1) Unlock hint is running
-     *      2) Swiping up on lockscreen or flinging down after swipe up
+     * 1) Unlock hint is running
+     * 2) Swiping up on lockscreen or flinging down after swipe up
      */
     private boolean shouldSkipHeightUpdate() {
         return mAmbientState.isOnKeyguard()
                 && (mAmbientState.isUnlockHintRunning()
-                        || mAmbientState.isSwipingUp()
-                        || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+                || mAmbientState.isSwipingUp()
+                || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
     }
 
     /**
      * Apply expansion fraction to the y position and height of the notifications panel.
+     *
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
@@ -1708,7 +1715,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     ExpandableView getChildAtPosition(float touchX, float touchY,
-            boolean requireMinHeight, boolean ignoreDecors) {
+                                      boolean requireMinHeight, boolean ignoreDecors) {
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
         for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1868,9 +1875,9 @@
     public void dismissViewAnimated(
             View child, Consumer<Boolean> endRunnable, int delay, long duration) {
         if (child instanceof SectionHeaderView) {
-             ((StackScrollerDecorView) child).setContentVisible(
-                     false /* visible */, true /* animate */, endRunnable);
-             return;
+            ((StackScrollerDecorView) child).setContentVisible(
+                    false /* visible */, true /* animate */, endRunnable);
+            return;
         }
         mSwipeHelper.dismissChild(
                 child,
@@ -2032,10 +2039,11 @@
     /**
      * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
      * would cause us to exceed the provided maximum overscroll, springs back instead.
-     *
+     * <p>
      * This method performs the determination of whether we're exceeding the overscroll and clamps
      * the scroll amount if so.  The actual scrolling/overscrolling happens in
      * {@link #onCustomOverScrolled(int, boolean)}
+     *
      * @param deltaY         The (signed) number of pixels to scroll.
      * @param scrollY        The current scroll position (absolute scrolling only).
      * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
@@ -2098,7 +2106,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
     }
 
@@ -2114,7 +2122,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators, boolean isRubberbanded) {
+                                    boolean cancelAnimators, boolean isRubberbanded) {
         if (cancelAnimators) {
             mStateAnimator.cancelOverScrollAnimators(onTop);
         }
@@ -2123,7 +2131,7 @@
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
-            boolean isRubberbanded) {
+                                             boolean isRubberbanded) {
         amount = Math.max(0, amount);
         if (animate) {
             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -2179,7 +2187,7 @@
      * Scrolls to the given position, overscrolling if needed.  If called during a fling and the
      * position exceeds the provided maximum overscroll, springs back instead.
      *
-     * @param scrollY The target scroll position.
+     * @param scrollY  The target scroll position.
      * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
      *                 the overscroll limit.
      */
@@ -2288,8 +2296,8 @@
                 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
                     List<ExpandableNotificationRow> notificationChildren =
                             row.getAttachedChildren();
-                    for (int childIndex = 0; childIndex < notificationChildren.size();
-                            childIndex++) {
+                    int childrenSize = notificationChildren.size();
+                    for (int childIndex = 0; childIndex < childrenSize; childIndex++) {
                         ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
                         if (rowChild.getTranslationY() + rowTranslation >= translationY) {
                             return rowChild;
@@ -2365,10 +2373,9 @@
     /**
      * Calculate the gap height between two different views
      *
-     * @param previous the previousView
-     * @param current the currentView
+     * @param previous     the previousView
+     * @param current      the currentView
      * @param visibleIndex the visible index in the list
-     *
      * @return the gap height needed before the current view
      */
     public float calculateGapHeight(
@@ -2376,7 +2383,7 @@
             ExpandableView current,
             int visibleIndex
     ) {
-       return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
+        return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
                 previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
     }
 
@@ -2642,15 +2649,15 @@
         return mScrolledToTopOnFirstDown
                 && !mExpandedInThisMotion
                 && (initialVelocity > mMinimumVelocity
-                        || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
+                || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
     }
 
     /**
      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
      * account.
      *
-     * @param qsHeight               the top padding imposed by the quick settings panel
-     * @param animate                whether to animate the change
+     * @param qsHeight the top padding imposed by the quick settings panel
+     * @param animate  whether to animate the change
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void updateTopPadding(float qsHeight, boolean animate) {
@@ -2724,21 +2731,34 @@
     }
 
 
-
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setChildTransferInProgress(boolean childTransferInProgress) {
         Assert.isMainThread();
         mChildTransferInProgress = childTransferInProgress;
     }
 
+    /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+        mOnNotificationRemovedListener = listener;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         // we only call our internal methods if this is actually a removal and not just a
         // notification which becomes a child notification
+        ExpandableView expandableView = (ExpandableView) child;
         if (!mChildTransferInProgress) {
-            onViewRemovedInternal((ExpandableView) child, this);
+            onViewRemovedInternal(expandableView, this);
+        }
+        if (mOnNotificationRemovedListener != null) {
+            mOnNotificationRemovedListener.onNotificationRemoved(
+                    expandableView,
+                    mChildTransferInProgress);
         }
     }
 
@@ -2998,8 +3018,10 @@
             mAnimateNextSectionBoundsChange = false;
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
-        // TODO: Refactor SectionManager and put the RoundnessManager there.
-        mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        if (!mUseRoundnessSourceTypes) {
+            // TODO: Refactor SectionManager and put the RoundnessManager there.
+            mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        }
         mAnimateBottomOnLayout = false;
         invalidate();
     }
@@ -3206,7 +3228,7 @@
                     // Only animate if we still have pinned heads up, otherwise we just have the
                     // regular collapse animation of the lock screen
                     || (mKeyguardBypassEnabled && onKeyguard()
-                            && mInHeadsUpPinnedMode);
+                    && mInHeadsUpPinnedMode);
             if (performDisappearAnimation && !isHeadsUp) {
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3351,7 +3373,7 @@
             AnimationEvent animEvent = duration == null
                     ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
                     : new AnimationEvent(
-                            child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+                    child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
             mAnimationEvents.add(animEvent);
         }
         mChildrenChangingPositions.clear();
@@ -3442,7 +3464,9 @@
 
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
-        return new StackScrollAlgorithm(context, this);
+        StackScrollAlgorithm stackScrollAlgorithm = new StackScrollAlgorithm(context, this);
+        stackScrollAlgorithm.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        return stackScrollAlgorithm;
     }
 
     /**
@@ -3772,7 +3796,7 @@
     }
 
     private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
-            int statusBarState, boolean touchIsClick) {
+                                    int statusBarState, boolean touchIsClick) {
         if (mLogger == null) {
             return;
         }
@@ -3957,7 +3981,9 @@
         mOnEmptySpaceClickListener = listener;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
@@ -4728,7 +4754,9 @@
         return touchY > mTopPadding + mStackTranslation;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -5353,7 +5381,9 @@
         return canChildBeCleared(row) && matchesSelection(row, selection);
     }
 
-    /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+    /**
+     * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
+     */
     public void setManageButtonClickListener(@Nullable OnClickListener listener) {
         mManageButtonClickListener = listener;
         if (mFooterView != null) {
@@ -5418,6 +5448,7 @@
     /**
      * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
      * notification positions accordingly.
+     *
      * @param height the new wake up height
      * @return the overflow how much the height is further than he lowest notification
      */
@@ -5649,7 +5680,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
                 && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
                 && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5710,7 +5741,7 @@
         mLaunchingNotification = launching;
         mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null
                 && (mLaunchAnimationParams.getStartRoundedTopClipping() > 0
-                        || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
+                || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
         if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) {
             mLaunchedNotificationClipPath.reset();
         }
@@ -5748,7 +5779,7 @@
                 mLaunchAnimationParams.getProgress(0,
                         NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
         int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
-                mLaunchAnimationParams.getTop(), expandProgress),
+                        mLaunchAnimationParams.getTop(), expandProgress),
                 mRoundedRectClippingTop);
         float topRadius = mLaunchAnimationParams.getTopCornerRadius();
         float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
@@ -5872,25 +5903,25 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public interface OnOverscrollTopChangedListener {
 
-    /**
-     * Notifies a listener that the overscroll has changed.
-     *
-     * @param amount         the amount of overscroll, in pixels
-     * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
-     *                       unrubberbanded motion to directly expand overscroll view (e.g
-     *                       expand
-     *                       QS)
-     */
-    void onOverscrollTopChanged(float amount, boolean isRubberbanded);
+        /**
+         * Notifies a listener that the overscroll has changed.
+         *
+         * @param amount         the amount of overscroll, in pixels
+         * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
+         *                       unrubberbanded motion to directly expand overscroll view (e.g
+         *                       expand
+         *                       QS)
+         */
+        void onOverscrollTopChanged(float amount, boolean isRubberbanded);
 
-    /**
-     * Notify a listener that the scroller wants to escape from the scrolling motion and
-     * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
-     *
-     * @param velocity The velocity that the Scroller had when over flinging
-     * @param open     Should the fling open or close the overscroll view.
-     */
-    void flingTopOverscroll(float velocity, boolean open);
+        /**
+         * Notify a listener that the scroller wants to escape from the scrolling motion and
+         * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
+         *
+         * @param velocity The velocity that the Scroller had when over flinging
+         * @param open     Should the fling open or close the overscroll view.
+         */
+        void flingTopOverscroll(float velocity, boolean open);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6252,7 +6283,9 @@
         }
     };
 
-    public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
+    public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+        return mHeadsUpCallback;
+    }
 
     void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
         boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
@@ -6357,15 +6390,25 @@
         return mLastSentExpandedHeight;
     }
 
-    /** Enum for selecting some or all notification rows (does not included non-notif views). */
+    /**
+     * Enum for selecting some or all notification rows (does not included non-notif views).
+     */
     @Retention(SOURCE)
     @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
-    @interface SelectedRows {}
-    /** All rows representing notifs. */
+    @interface SelectedRows {
+    }
+
+    /**
+     * All rows representing notifs.
+     */
     public static final int ROWS_ALL = 0;
-    /** Only rows where entry.isHighPriority() is true. */
+    /**
+     * Only rows where entry.isHighPriority() is true.
+     */
     public static final int ROWS_HIGH_PRIORITY = 1;
-    /** Only rows where entry.isHighPriority() is false. */
+    /**
+     * Only rows where entry.isHighPriority() is false.
+     */
     public static final int ROWS_GENTLE = 2;
 
     interface ClearAllListener {
@@ -6380,4 +6423,16 @@
         void onAnimationEnd(
                 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
     }
+
+    /**
+     *
+     */
+    public interface OnNotificationRemovedListener {
+        /**
+         *
+         * @param child
+         * @param isTransferInProgress
+         */
+        void onNotificationRemoved(ExpandableView child, boolean isTransferInProgress);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2d6d0a9c..4bcc0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -182,10 +182,12 @@
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
     private NotificationSwipeHelper mSwipeHelper;
-    @Nullable private Boolean mHistoryEnabled;
+    @Nullable
+    private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private final FeatureFlags mFeatureFlags;
+    private final boolean mUseRoundnessSourceTypes;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
 
     private View mLongPressedView;
@@ -391,7 +393,7 @@
                     if (item != null) {
                         Point origin = provider.getRevealAnimationOrigin();
                         mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
-                    } else  {
+                    } else {
                         Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
                                 + "menu item in menuItemtoExposeOnSnap. Skipping.");
                     }
@@ -420,7 +422,7 @@
 
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
-                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                                     NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
@@ -544,7 +546,7 @@
 
                 @Override
                 public boolean updateSwipeProgress(View animView, boolean dismissable,
-                        float swipeProgress) {
+                                                   float swipeProgress) {
                     // Returning true prevents alpha fading.
                     return !mFadeNotificationsOnDismiss;
                 }
@@ -584,16 +586,22 @@
 
                 @Override
                 public void onHeadsUpPinned(NotificationEntry entry) {
-                    mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        mNotificationRoundnessManager.updateView(
+                                entry.getRow(),
+                                /* animate = */ false);
+                    }
                 }
 
                 @Override
                 public void onHeadsUpUnPinned(NotificationEntry entry) {
-                    ExpandableNotificationRow row = entry.getRow();
-                    // update the roundedness posted, because we might be animating away the
-                    // headsup soon, so no need to set the roundedness to 0 and then back to 1.
-                    row.post(() -> mNotificationRoundnessManager.updateView(row,
-                            true /* animate */));
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        // update the roundedness posted, because we might be animating away the
+                        // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+                        row.post(() -> mNotificationRoundnessManager.updateView(row,
+                                true /* animate */));
+                    }
                 }
 
                 @Override
@@ -603,8 +611,10 @@
                     mView.setNumHeadsUp(numEntries);
                     mView.setTopHeadsUpEntry(topEntry);
                     generateHeadsUpAnimation(entry, isHeadsUp);
-                    ExpandableNotificationRow row = entry.getRow();
-                    mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    }
                 }
             };
 
@@ -697,6 +707,7 @@
         mSeenNotificationsProvider = seenNotificationsProvider;
         mShadeController = shadeController;
         mFeatureFlags = featureFlags;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mNotificationTargetsHelper = notificationTargetsHelper;
         updateResources();
     }
@@ -763,8 +774,10 @@
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
         mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
-        mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
-        mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        if (!mUseRoundnessSourceTypes) {
+            mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+            mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        }
 
         mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
 
@@ -997,9 +1010,11 @@
                 Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
                 return false;
             }
-            mHistoryEnabled = historyEnabled =
-                    Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
-                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+            mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
+                    mView.getContext().getContentResolver(),
+                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                    0,
+                    UserHandle.USER_CURRENT) == 1;
         }
         return historyEnabled;
     }
@@ -1029,7 +1044,7 @@
     }
 
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
     }
 
@@ -1207,7 +1222,7 @@
 
     /**
      * Update whether we should show the empty shade view ("no notifications" in the shade).
-     *
+     * <p>
      * When in split mode, notifications are always visible regardless of the state of the
      * QuickSettings panel. That being the case, empty view is always shown if the other conditions
      * are true.
@@ -1233,7 +1248,7 @@
 
     /**
      * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
-     *         and false otherwise.
+     * and false otherwise.
      */
     private boolean isInTransitionToKeyguard() {
         final int currentState = mStatusBarStateController.getState();
@@ -1265,7 +1280,9 @@
         mView.setExpandedHeight(expandedHeight);
     }
 
-    /** Sets the QS header. Used to check if a touch is within its bounds. */
+    /**
+     * Sets the QS header. Used to check if a touch is within its bounds.
+     */
     public void setQsHeader(ViewGroup view) {
         mView.setQsHeader(view);
     }
@@ -1328,7 +1345,7 @@
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
-                    boolean remoteInputActive) {
+                                             boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 updateFooter();
@@ -1459,7 +1476,7 @@
     }
 
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
-            @SelectedRows int selectedRows) {
+                                @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
@@ -1502,8 +1519,8 @@
 
     /**
      * @return the inset during the full shade transition, that needs to be added to the position
-     *         of the quick settings edge. This is relevant for media, that is transitioning
-     *         from the keyguard host to the quick settings one.
+     * of the quick settings edge. This is relevant for media, that is transitioning
+     * from the keyguard host to the quick settings one.
      */
     public int getFullShadeTransitionInset() {
         MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
@@ -1517,10 +1534,10 @@
     /**
      * @param fraction The fraction of lockscreen to shade transition.
      *                 0f for all other states.
-     *
-     * Once the lockscreen to shade transition completes and the shade is 100% open,
-     * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
-     * until the next lockscreen-to-shade transition.
+     *                 <p>
+     *                 Once the lockscreen to shade transition completes and the shade is 100% open,
+     *                 LockscreenShadeTransitionController resets amount and fraction to 0, where
+     *                 they remain until the next lockscreen-to-shade transition.
      */
     public void setTransitionToFullShadeAmount(float fraction) {
         mView.setFractionToShade(fraction);
@@ -1533,7 +1550,9 @@
         mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
     }
 
-    /** */
+    /**
+     *
+     */
     public void setWillExpand(boolean willExpand) {
         mView.setWillExpand(willExpand);
     }
@@ -1549,7 +1568,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
     }
 
@@ -1569,6 +1588,15 @@
     }
 
     /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(
+            NotificationStackScrollLayout.OnNotificationRemovedListener listener) {
+        mView.setOnNotificationRemovedListener(listener);
+    }
+
+    /**
      * Enum for UiEvent logged from this class
      */
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@@ -1578,10 +1606,13 @@
         @UiEvent(doc = "User dismissed all silent notifications from notification panel.")
         DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
         private final int mId;
+
         NotificationPanelEvent(int id) {
             mId = id;
         }
-        @Override public int getId() {
+
+        @Override
+        public int getId() {
             return mId;
         }
 
@@ -1722,8 +1753,12 @@
         @Override
         public void bindRow(ExpandableNotificationRow row) {
             row.setHeadsUpAnimatingAwayListener(animatingAway -> {
-                mNotificationRoundnessManager.updateView(row, false);
-                mHeadsUpAppearanceController.updateHeader(row.getEntry());
+                if (!mUseRoundnessSourceTypes) {
+                    mNotificationRoundnessManager.updateView(row, false);
+                }
+                NotificationEntry entry = row.getEntry();
+                mHeadsUpAppearanceController.updateHeader(entry);
+                mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry);
             });
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index ee57411..aaf9300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -20,6 +20,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -34,9 +35,11 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
@@ -49,6 +52,7 @@
     @VisibleForTesting
     protected static final long COVER_MENU_DELAY = 4000;
     private static final String TAG = "NotificationSwipeHelper";
+    private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
     private final Runnable mFalsingCheck;
     private View mTranslatingParentView;
     private View mMenuExposedView;
@@ -64,13 +68,21 @@
     private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
     private boolean mIsExpanded;
     private boolean mPulsing;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
 
     NotificationSwipeHelper(
-            Resources resources, ViewConfiguration viewConfiguration,
-            FalsingManager falsingManager, FeatureFlags featureFlags,
-            int swipeDirection, NotificationCallback callback,
-            NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+            Resources resources,
+            ViewConfiguration viewConfiguration,
+            FalsingManager falsingManager,
+            FeatureFlags featureFlags,
+            int swipeDirection,
+            NotificationCallback callback,
+            NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+            NotificationRoundnessManager notificationRoundnessManager) {
         super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mMenuListener = menuListener;
         mCallback = callback;
         mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -304,6 +316,33 @@
         handleMenuCoveredOrDismissed();
     }
 
+    @Override
+    protected void prepareDismissAnimation(View view, Animator anim) {
+        super.prepareDismissAnimation(view, anim);
+
+        if (mUseRoundnessSourceTypes
+                && view instanceof ExpandableNotificationRow
+                && mNotificationRoundnessManager.isClearAllInProgress()) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+            });
+        }
+    }
+
     @VisibleForTesting
     protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
         super.dismissChild(view, velocity, useAccelerateInterpolator);
@@ -521,14 +560,17 @@
         private int mSwipeDirection;
         private NotificationCallback mNotificationCallback;
         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+        private NotificationRoundnessManager mNotificationRoundnessManager;
 
         @Inject
         Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
-                FalsingManager falsingManager, FeatureFlags featureFlags) {
+                FalsingManager falsingManager, FeatureFlags featureFlags,
+                NotificationRoundnessManager notificationRoundnessManager) {
             mResources = resources;
             mViewConfiguration = viewConfiguration;
             mFalsingManager = falsingManager;
             mFeatureFlags = featureFlags;
+            mNotificationRoundnessManager = notificationRoundnessManager;
         }
 
         Builder setSwipeDirection(int swipeDirection) {
@@ -549,7 +591,8 @@
 
         NotificationSwipeHelper build() {
             return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
-                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+                    mNotificationRoundnessManager);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
index 991a14b..548d1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -20,8 +20,7 @@
 constructor(
     featureFlags: FeatureFlags,
 ) {
-    private val isNotificationGroupCornerEnabled =
-        featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
 
     /**
      * This method looks for views that can be rounded (and implement [Roundable]) during a
@@ -48,7 +47,7 @@
         if (notificationParent != null && childrenContainer != null) {
             // We are inside a notification group
 
-            if (!isNotificationGroupCornerEnabled) {
+            if (!useRoundnessSourceTypes) {
                 return RoundableTargets(null, null, null)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d8c6878..aff7b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -31,6 +31,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +52,7 @@
 
     private static final String TAG = "StackScrollAlgorithm";
     private static final Boolean DEBUG = false;
+    private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
 
     private final ViewGroup mHostView;
     private float mPaddingBetweenElements;
@@ -70,6 +72,7 @@
     private float mQuickQsOffsetHeight;
     private float mSmallCornerRadius;
     private float mLargeCornerRadius;
+    private boolean mUseRoundnessSourceTypes;
 
     public StackScrollAlgorithm(
             Context context,
@@ -129,7 +132,7 @@
     }
 
     private void updateAlphaState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                  AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
 
@@ -229,7 +232,7 @@
     }
 
     private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                               AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = algorithmState.visibleChildren.get(i);
@@ -241,7 +244,7 @@
     }
 
     private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
-            int speedBumpIndex) {
+                                      int speedBumpIndex) {
         int childCount = algorithmState.visibleChildren.size();
         int belowSpeedBump = speedBumpIndex;
         for (int i = 0; i < childCount; i++) {
@@ -268,7 +271,7 @@
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
         float clipStart = 0;
@@ -314,7 +317,7 @@
      * Updates the dimmed, activated and hiding sensitive states of the children.
      */
     private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
-            StackScrollAlgorithmState algorithmState) {
+                                                    StackScrollAlgorithmState algorithmState) {
         boolean dimmed = ambientState.isDimmed();
         boolean hideSensitive = ambientState.isHideSensitive();
         View activatedChild = ambientState.getActivatedChild();
@@ -408,7 +411,7 @@
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
-            ExpandableView v) {
+                                   ExpandableView v) {
         ExpandableViewState viewState = v.getViewState();
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
@@ -434,7 +437,7 @@
      * @param ambientState   The current ambient state
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                           AmbientState ambientState) {
         if (!ambientState.isOnKeyguard()
                 || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
             algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -448,7 +451,7 @@
     }
 
     private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
-            int i) {
+                             int i) {
         expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         if (currentYPosition <= 0) {
             expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -496,9 +499,13 @@
     }
 
     @VisibleForTesting
-    void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
-            boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
-
+    void maybeUpdateHeadsUpIsVisible(
+            ExpandableViewState viewState,
+            boolean isShadeExpanded,
+            boolean mustStayOnScreen,
+            boolean topVisible,
+            float viewEnd,
+            float hunMax) {
         if (isShadeExpanded && mustStayOnScreen && topVisible) {
             viewState.headsUpIsVisible = viewEnd < hunMax;
         }
@@ -676,7 +683,7 @@
     }
 
     private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
@@ -693,7 +700,7 @@
     }
 
     private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
 
         // Move the tracked heads up into position during the appear animation, by interpolating
@@ -777,7 +784,7 @@
      */
     @VisibleForTesting
     void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
-            ExpandableViewState viewState) {
+                       ExpandableViewState viewState) {
 
         final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
                 viewState.getYTranslation());
@@ -792,7 +799,7 @@
     // Pin HUN to bottom of expanded QS
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-            ExpandableViewState childState) {
+                                          ExpandableViewState childState) {
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
         final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
@@ -807,14 +814,19 @@
         // Animate pinned HUN bottom corners to and from original roundness.
         final float originalCornerRadius =
                 row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
-        final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+        final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
                 ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
-        row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
+        if (mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
+            row.addOnDetachResetRoundness(STACK_SCROLL_ALGO);
+        } else {
+            row.requestBottomRoundness(bottomValue, LegacySourceType.OnScroll);
+        }
     }
 
     @VisibleForTesting
     float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
-            float viewMaxHeight, float originalCornerRadius) {
+                                             float viewMaxHeight, float originalCornerRadius) {
 
         // Compute y where corner roundness should be in its original unpinned state.
         // We use view max height because the pinned collapsed HUN expands to max height
@@ -844,7 +856,7 @@
      * @param ambientState   The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                       AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
 
@@ -876,9 +888,9 @@
      *                      previous HUNs whose Z positions are greater than 0.
      */
     protected float updateChildZValue(int i, float childrenOnTop,
-            StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState,
-            boolean isTopHun) {
+                                      StackScrollAlgorithmState algorithmState,
+                                      AmbientState ambientState,
+                                      boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
@@ -950,6 +962,14 @@
         this.mIsExpanded = isExpanded;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
     public static class StackScrollAlgorithmState {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 484441a..c217ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -19,11 +19,14 @@
 import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
 
 import android.graphics.Rect;
+import android.util.MathUtils;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -32,8 +35,10 @@
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.statusbar.policy.Clock;
@@ -51,6 +56,7 @@
 
 /**
  * Controls the appearance of heads up notifications in the icon area and the header itself.
+ * It also controls the roundness of the heads up notifications and the pulsing notifications.
  */
 @StatusBarFragmentScope
 public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
@@ -59,12 +65,17 @@
         NotificationWakeUpCoordinator.WakeUpListener {
     public static final int CONTENT_FADE_DURATION = 110;
     public static final int CONTENT_FADE_DELAY = 100;
+
+    private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
+    private static final SourceType PULSING = SourceType.from("Pulsing");
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final DarkIconDispatcher mDarkIconDispatcher;
     private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
     private final Consumer<ExpandableNotificationRow>
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
@@ -105,11 +116,15 @@
             CommandQueue commandQueue,
             NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
+            NotificationRoundnessManager notificationRoundnessManager,
+            FeatureFlags featureFlags,
             HeadsUpStatusBarView headsUpStatusBarView,
             Clock clockView,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
         mNotificationIconAreaController = notificationIconAreaController;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mHeadsUpManager = headsUpManager;
 
         // We may be mid-HUN-expansion when this controller is re-created (for example, if the user
@@ -179,6 +194,7 @@
     public void onHeadsUpPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     private void updateTopEntry() {
@@ -316,6 +332,7 @@
     public void onHeadsUpUnPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     public void setAppearFraction(float expandedHeight, float appearFraction) {
@@ -346,7 +363,9 @@
         ExpandableNotificationRow previousTracked = mTrackedChild;
         mTrackedChild = trackedChild;
         if (previousTracked != null) {
-            updateHeader(previousTracked.getEntry());
+            NotificationEntry entry = previousTracked.getEntry();
+            updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         }
     }
 
@@ -357,6 +376,7 @@
     private void updateHeadsUpHeaders() {
         mHeadsUpManager.getAllEntries().forEach(entry -> {
             updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         });
     }
 
@@ -370,6 +390,31 @@
         row.setHeaderVisibleAmount(headerVisibleAmount);
     }
 
+    /**
+     * Update the HeadsUp and the Pulsing roundness based on current state
+     * @param entry target notification
+     */
+    public void updateHeadsUpAndPulsingRoundness(NotificationEntry entry) {
+        if (mUseRoundnessSourceTypes) {
+            ExpandableNotificationRow row = entry.getRow();
+            boolean isTrackedChild = row == mTrackedChild;
+            if (row.isPinned() || row.isHeadsUpAnimatingAway() || isTrackedChild) {
+                float roundness = MathUtils.saturate(1f - mAppearFraction);
+                row.requestRoundness(roundness, roundness, HEADS_UP);
+            } else {
+                row.requestRoundnessReset(HEADS_UP);
+            }
+            if (mNotificationRoundnessManager.shouldRoundNotificationPulsing()) {
+                if (row.showingPulsing()) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PULSING);
+                } else {
+                    row.requestRoundnessReset(PULSING);
+                }
+            }
+        }
+    }
+
+
     @Override
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         mView.onDarkChanged(areas, darkIntensity, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index fb67f1a..e7afc50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
@@ -40,6 +42,8 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -52,8 +56,7 @@
     @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
-    @Binds
-    abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+    @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
 
     @Binds
     abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
@@ -63,15 +66,18 @@
         impl: MobileConnectionsRepositoryImpl
     ): MobileConnectionsRepository
 
-    @Binds
-    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+    @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
 
-    @Binds
-    abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+    @Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
 
     @Binds
     abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
 
+    @Binds
+    @IntoMap
+    @ClassKey(MobileUiAdapter::class)
+    abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+
     @Module
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index da87f73..5479b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,6 +20,7 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.DataState
 
 /** Internal enum representation of the telephony data connection states */
@@ -28,6 +29,7 @@
     Connecting(DATA_CONNECTING),
     Disconnected(DATA_DISCONNECTED),
     Disconnecting(DATA_DISCONNECTING),
+    Unknown(DATA_UNKNOWN),
 }
 
 fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -36,5 +38,6 @@
         DATA_CONNECTING -> DataConnectionState.Connecting
         DATA_DISCONNECTED -> DataConnectionState.Disconnected
         DATA_DISCONNECTING -> DataConnectionState.Disconnecting
-        else -> throw IllegalArgumentException("unknown data state received")
+        DATA_UNKNOWN -> DataConnectionState.Unknown
+        else -> throw IllegalArgumentException("unknown data state received $this")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index c7e0ce1..d9487bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
@@ -29,9 +30,10 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * This class is intended to provide a context to collect on the
@@ -50,9 +52,9 @@
     interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
-    @Application scope: CoroutineScope,
+    @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
-) {
+) : CoreStartable {
     private val mobileSubIds: Flow<List<Int>> =
         interactor.filteredSubscriptions.mapLatest { infos ->
             infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
@@ -66,18 +68,19 @@
      * NOTE: this should go away as the view presenter learns more about this data pipeline
      */
     private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .onEach {
-                // Only notify the icon controller if we want to *render* the new icons.
-                // Note that this flow may still run if
-                // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
-                // get the logging data without rendering.
-                if (statusBarPipelineFlags.useNewMobileIcons()) {
-                    // Notify the icon controller here so that it knows to add icons
-                    iconController.setNewMobileIconSubIds(it)
-                }
+        mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    override fun start() {
+        // Only notify the icon controller if we want to *render* the new icons.
+        // Note that this flow may still run if
+        // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+        // get the logging data without rendering.
+        if (statusBarPipelineFlags.useNewMobileIcons()) {
+            scope.launch {
+                mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+        }
+    }
 
     /**
      * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index b816364..5223760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import javax.inject.Inject
@@ -73,7 +74,9 @@
                         // Note that this flow may still run if
                         // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
                         // want to get the logging data without rendering.
-                        if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+                        if (
+                            wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
+                        ) {
                             iconController.setNewWifiIcon()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 345f8cb..f5b5950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -92,8 +93,10 @@
 
                 launch {
                     viewModel.wifiIcon.collect { wifiIcon ->
-                        view.isVisible = wifiIcon != null
-                        wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
+                        view.isVisible = wifiIcon is WifiIcon.Visible
+                        if (wifiIcon is WifiIcon.Visible) {
+                            IconViewBinder.bind(wifiIcon.icon, iconView)
+                        }
                     }
                 }
 
@@ -135,7 +138,7 @@
 
         return object : Binding {
             override fun getShouldIconBeVisible(): Boolean {
-                return viewModel.wifiIcon.value != null
+                return viewModel.wifiIcon.value is WifiIcon.Visible
             }
 
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
new file mode 100644
index 0000000..e491d2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.wifi.ui.model
+
+import android.annotation.DrawableRes
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Represents the various states of the wifi icon. */
+sealed interface WifiIcon : Diffable<WifiIcon> {
+    /** Represents a wifi icon that should be hidden (not visible). */
+    object Hidden : WifiIcon {
+        override fun toString() = "hidden"
+    }
+
+    /**
+     * Represents a visible wifi icon that uses [res] as its image and [contentDescription] as its
+     * description.
+     */
+    class Visible(
+        @DrawableRes res: Int,
+        val contentDescription: ContentDescription.Loaded,
+    ) : WifiIcon {
+        val icon = Icon.Resource(res, contentDescription)
+
+        override fun toString() = contentDescription.description.toString()
+    }
+
+    override fun logDiffs(prevVal: WifiIcon, row: TableRowLogger) {
+        if (prevVal.toString() != toString()) {
+            row.logChange(COL_ICON, toString())
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ICON, toString())
+    }
+}
+
+private const val COL_ICON = "wifiIcon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 95ab251..a29c9b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -28,7 +28,7 @@
  */
 class HomeWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 86535d6..1e190fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index 7cbdf5d..e35a8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -33,8 +33,8 @@
     statusBarPipelineFlags: StatusBarPipelineFlags,
     debugTint: Int,
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    val wifiIcon: StateFlow<Icon.Resource?>,
+    /** The wifi icon that should be displayed. */
+    val wifiIcon: StateFlow<WifiIcon>,
 
     /** True if the activity in view should be visible. */
     val isActivityInViewVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index fd54c5f..18e62b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 0782bbb..ec7ba65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,16 +17,18 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.content.Context
-import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -71,50 +73,39 @@
     connectivityConstants: ConnectivityConstants,
     private val context: Context,
     logger: ConnectivityPipelineLogger,
+    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     interactor: WifiInteractor,
     @Application private val scope: CoroutineScope,
     statusBarPipelineFlags: StatusBarPipelineFlags,
     wifiConstants: WifiConstants,
 ) {
-    /**
-     * Returns the drawable resource ID to use for the wifi icon based on the given network.
-     * Null if we can't compute the icon.
-     */
-    @DrawableRes
-    private fun WifiNetworkModel.iconResId(): Int? {
+    /** Returns the icon to use based on the given network. */
+    private fun WifiNetworkModel.icon(): WifiIcon {
         return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
-            is WifiNetworkModel.Active ->
-                when {
-                    this.level == null -> null
-                    this.isValidated -> WIFI_FULL_ICONS[this.level]
-                    else -> WIFI_NO_INTERNET_ICONS[this.level]
-                }
-        }
-    }
-
-    /**
-     * Returns the content description for the wifi icon based on the given network.
-     * Null if we can't compute the content description.
-     */
-    private fun WifiNetworkModel.contentDescription(): ContentDescription? {
-        return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive ->
+            is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
+            is WifiNetworkModel.Inactive -> WifiIcon.Visible(
+                res = WIFI_NO_NETWORK,
                 ContentDescription.Loaded(
                     "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
                 )
+            )
             is WifiNetworkModel.Active ->
                 when (this.level) {
-                    null -> null
+                    null -> WifiIcon.Hidden
                     else -> {
                         val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
                         when {
-                            this.isValidated -> ContentDescription.Loaded(levelDesc)
+                            this.isValidated ->
+                                WifiIcon.Visible(
+                                    WIFI_FULL_ICONS[this.level],
+                                    ContentDescription.Loaded(levelDesc)
+                                )
                             else ->
-                                ContentDescription.Loaded(
-                                    "$levelDesc,${context.getString(NO_INTERNET)}"
+                                WifiIcon.Visible(
+                                    WIFI_NO_INTERNET_ICONS[this.level],
+                                    ContentDescription.Loaded(
+                                        "$levelDesc,${context.getString(NO_INTERNET)}"
+                                    )
                                 )
                         }
                     }
@@ -122,8 +113,8 @@
         }
     }
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    private val wifiIcon: StateFlow<Icon.Resource?> =
+    /** The wifi icon that should be displayed. */
+    private val wifiIcon: StateFlow<WifiIcon> =
         combine(
             interactor.isEnabled,
             interactor.isDefault,
@@ -131,22 +122,29 @@
             interactor.wifiNetwork,
         ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
             if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
-                return@combine null
+                return@combine WifiIcon.Hidden
             }
 
-            val iconResId = wifiNetwork.iconResId() ?: return@combine null
-            val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+            val icon = wifiNetwork.icon()
 
             return@combine when {
                 isDefault -> icon
                 wifiConstants.alwaysShowIconIfEnabled -> icon
                 !connectivityConstants.hasDataCapabilities -> icon
                 wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
-                else -> null
+                else -> WifiIcon.Hidden
             }
         }
-                .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
-                .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+            .logDiffsForTable(
+                wifiTableLogBuffer,
+                columnPrefix = "",
+                initialValue = WifiIcon.Hidden,
+            )
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = WifiIcon.Hidden
+            )
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
     private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a9d05d1..ea40208 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -105,8 +105,9 @@
      *
      * This method handles inflating and attaching the view, then delegates to [updateView] to
      * display the correct information in the view.
+     * @param onViewTimeout a runnable that runs after the view timeout.
      */
-    fun displayView(newInfo: T) {
+    fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
         val currentDisplayInfo = displayInfo
 
         // Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@
             cancelViewTimeout?.run()
         }
         cancelViewTimeout = mainExecutor.executeDelayed(
-            { removeView(id, REMOVAL_REASON_TIMEOUT) },
+            {
+                removeView(id, REMOVAL_REASON_TIMEOUT)
+                onViewTimeout?.run()
+            },
             timeout.toLong()
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 0f3eddf..bff6132d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -50,13 +50,6 @@
     }
 
     /**
-     * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
-     */
-    public void enforceReadPermission(String namespace) {
-        DeviceConfig.enforceReadPermission(namespace);
-    }
-
-    /**
      * Wrapped version of {@link DeviceConfig#getBoolean}.
      */
     public boolean getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5..8ef98f0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@
                 splitScreen.onFinishedWakingUp();
             }
         });
+        mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+            @Override
+            public void goToFullscreenFromSplit() {
+                splitScreen.goToFullscreenFromSplit();
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index ffd95f4..d20be56 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -43,6 +44,8 @@
     @Mock
     private lateinit var keyguardPasswordView: KeyguardPasswordView
     @Mock
+    private lateinit var passwordEntry: EditText
+    @Mock
     lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock
     lateinit var securityMode: KeyguardSecurityModel.SecurityMode
@@ -81,6 +84,9 @@
         ).thenReturn(mKeyguardMessageArea)
         Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
             .thenReturn(mKeyguardMessageAreaController)
+        Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+        Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
+        ).thenReturn(passwordEntry)
         keyguardPasswordViewController = KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
@@ -103,7 +109,10 @@
         Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
         Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
         keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).requestFocus()
+            verify(keyguardPasswordView).showKeyboard()
+        }
     }
 
     @Test
@@ -115,6 +124,15 @@
     }
 
     @Test
+    fun testHideKeyboardWhenOnPause() {
+        keyguardPasswordViewController.onPause()
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).clearFocus()
+            verify(keyguardPasswordView).hideKeyboard()
+        }
+    }
+
+    @Test
     fun startAppearAnimation() {
         keyguardPasswordViewController.startAppearAnimation()
         verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 761773b..fdef344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -702,7 +702,7 @@
     }
 
     @Test
-    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToGone() {
+    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -711,7 +711,7 @@
 
         player.bindPlayer(state, PACKAGE)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
@@ -741,7 +741,7 @@
     }
 
     @Test
-    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToGone() {
+    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -752,7 +752,7 @@
 
         getEnabledChangeListener().onEnabledChanged(enabled = false)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4437394..311740e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -265,6 +265,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -278,13 +280,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -297,6 +301,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -313,6 +318,7 @@
     @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -325,8 +331,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
         assertThat(undoCallbackCalled).isTrue()
         assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@
 
     @Test
     fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+        displayThisDeviceTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -348,13 +357,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -367,6 +378,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -383,6 +395,7 @@
     @Test
     fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -395,8 +408,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(
                 MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
             )
@@ -407,6 +421,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
@@ -421,7 +437,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -429,6 +446,12 @@
     @Test
     fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        reset(vibratorHelper)
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
             routeInfo,
             null
@@ -442,7 +465,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -517,6 +541,166 @@
     }
 
     @Test
+    fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -575,6 +759,7 @@
 
     @Test
     fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -598,6 +783,7 @@
 
     @Test
     fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -621,6 +807,7 @@
 
     @Test
     fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -660,6 +847,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -717,6 +905,26 @@
     private fun ChipStateSender.getExpectedStateText(): String? {
         return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
     }
+
+    // display receiver triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+    private fun displayReceiverTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
+
+    // display this device triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+    private fun displayThisDeviceTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
 }
 
 private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 9758842..4a9c750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,21 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
-import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import org.junit.Before
 import org.junit.Test
@@ -48,6 +53,7 @@
     private val notesIntent = Intent(NOTES_ACTION)
 
     @Mock lateinit var context: Context
+    @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
     @Mock lateinit var bubbles: Bubbles
     @Mock lateinit var optionalBubbles: Optional<Bubbles>
@@ -60,6 +66,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.packageManager).thenReturn(packageManager)
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
         whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
@@ -78,89 +85,125 @@
         )
     }
 
+    // region showNoteTask
     @Test
-    fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+    fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(bubbles).showAppBubble(notesIntent)
         verify(context, never()).startActivity(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+    fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        verify(context, never()).startActivity(notesIntent)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+
+        verify(context).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+    fun showNoteTask_bubblesIsNull_shouldDoNothing() {
         whenever(optionalBubbles.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_userManagerIsNull_shouldDoNothing() {
         whenever(optionalUserManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+    fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_flagDisabled_shouldDoNothing() {
-        createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+    fun showNoteTask_flagDisabled_shouldDoNothing() {
+        createNoteTaskController(isEnabled = false).showNoteTask()
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userIsLocked_shouldDoNothing() {
+    fun showNoteTask_userIsLocked_shouldDoNothing() {
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
+    // endregion
+
+    // region setNoteTaskShortcutEnabled
+    @Test
+    fun setNoteTaskShortcutEnabled_setTrue() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+
+    @Test
+    fun setNoteTaskShortcutEnabled_setFalse() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 334089c..538131a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -16,10 +16,10 @@
 package com.android.systemui.notetask
 
 import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -45,6 +45,7 @@
     @Mock lateinit var commandQueue: CommandQueue
     @Mock lateinit var bubbles: Bubbles
     @Mock lateinit var optionalBubbles: Optional<Bubbles>
+    @Mock lateinit var noteTaskController: NoteTaskController
 
     @Before
     fun setUp() {
@@ -57,12 +58,13 @@
     private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
         return NoteTaskInitializer(
             optionalBubbles = optionalBubbles,
-            lazyNoteTaskController = mock(),
+            noteTaskController = noteTaskController,
             commandQueue = commandQueue,
             isEnabled = isEnabled,
         )
     }
 
+    // region initializer
     @Test
     fun initialize_shouldAddCallbacks() {
         createNoteTaskInitializer().initialize()
@@ -85,4 +87,35 @@
 
         verify(commandQueue, never()).addCallback(any())
     }
+
+    @Test
+    fun initialize_flagEnabled_shouldEnableShortcut() {
+        createNoteTaskInitializer().initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(true)
+    }
+
+    @Test
+    fun initialize_flagDisabled_shouldDisableShortcut() {
+        createNoteTaskInitializer(isEnabled = false).initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+    }
+    // endregion
+
+    // region handleSystemKey
+    @Test
+    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+        verify(noteTaskController).showNoteTask()
+    }
+
+    @Test
+    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+        verify(noteTaskController, never()).showNoteTask()
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 5e9c1aa..906c20b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -703,28 +703,32 @@
     public void testParentalControls() {
         // Make sure the security footer is visible, so that the images are updated.
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
-
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
 
+        // We use the default icon when there is no admin icon.
+        when(mSecurityController.getIcon(any())).thenReturn(null);
+        mFooter.refreshState();
+        TestableLooper.get(this).processAllMessages();
+        assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
+                mFooterText.getText());
+        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+
         Drawable testDrawable = new VectorDrawable();
         when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
         assertNotNull(mSecurityController.getIcon(null));
 
         mFooter.refreshState();
-
         TestableLooper.get(this).processAllMessages();
 
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
                 mFooterText.getText());
         assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-
         assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
 
         // Ensure the primary icon is back to default after parental controls are gone
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
         mFooter.refreshState();
         TestableLooper.get(this).processAllMessages();
-
         assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index faf4592..5431eba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -72,6 +72,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -245,6 +246,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ca75a40..9441d49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -49,6 +49,7 @@
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -150,6 +151,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 84c242c..4c1f0a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -78,6 +79,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
@@ -115,6 +117,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
@@ -150,6 +153,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
@@ -188,6 +192,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
@@ -274,6 +279,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
new file mode 100644
index 0000000..89faa239
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RoundableTest : SysuiTestCase() {
+    val targetView: View = mock()
+    val roundable = FakeRoundable(targetView)
+
+    @Test
+    fun defaultConfig_shouldNotHaveRoundedCorner() {
+        // the expected default value for the roundness is top = 0f, bottom = 0f
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
+        roundable.applyRoundnessAndInvalidate()
+
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestTopRoundness_update_and_invalidate_targetView() {
+        roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestBottomRoundness_update_and_invalidate_targetView() {
+        roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestRoundness_update_and_invalidate_targetView() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun requestRoundnessReset_update_and_invalidate_targetView() {
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundnessReset(sourceType = SOURCE1)
+
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.1f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
+        // SOURCE1 has 0.3f - SOURCE2 has 0.2f
+        assertEquals(0.3f, roundable.roundableState.topRoundness)
+        assertEquals(0.3f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.2f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    class FakeRoundable(
+        targetView: View,
+        radius: Float = MAX_RADIUS,
+    ) : Roundable {
+        override val roundableState =
+            RoundableState(
+                targetView = targetView,
+                roundable = this,
+                maxRadius = radius,
+            )
+    }
+
+    companion object {
+        private const val MAX_RADIUS = 10f
+        private val SOURCE1 = SourceType.from("Source1")
+        private val SOURCE2 = SourceType.from("Source2")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 088d165..59d4720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -213,8 +213,7 @@
             SourceType sourceType
     ) throws Exception {
         ExpandableNotificationRow row = createRow();
-        row.requestTopRoundness(topRoundness, false, sourceType);
-        row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+        row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
         assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
         assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
         return row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 438b528..fd1944e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,7 +25,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
@@ -158,7 +158,7 @@
         ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnScroll);
+                /* sourceType = */ LegacySourceType.OnScroll);
 
         mChildrenContainer.addNotification(row, 0);
 
@@ -171,11 +171,11 @@
         ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.DefaultValue);
+                /* sourceType = */ LegacySourceType.DefaultValue);
         ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnDismissAnimation);
+                /* sourceType = */ LegacySourceType.OnDismissAnimation);
 
         mChildrenContainer.addNotification(row1, 0);
         mChildrenContainer.addNotification(row2, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 8c8b644..bd0a556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
@@ -73,7 +74,8 @@
         mRoundnessManager = new NotificationRoundnessManager(
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
                 mLogger,
-                mock(DumpManager.class));
+                mock(DumpManager.class),
+                mock(FeatureFlags.class));
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ecc0224..30da08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -59,10 +60,12 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
     @Mock private MediaContainerController mMediaContainerController;
+    @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private SectionHeaderController mIncomingHeaderController;
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
+    @Mock private FeatureFlags mFeatureFlag;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -89,10 +92,12 @@
                         mKeyguardMediaController,
                         mSectionsFeatureManager,
                         mMediaContainerController,
+                        mNotificationRoundnessManager,
                         mIncomingHeaderController,
                         mPeopleHeaderController,
                         mAlertingHeaderController,
-                        mSilentHeaderController
+                        mSilentHeaderController,
+                        mFeatureFlag
                 );
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index bda2336..9d759c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -9,7 +9,7 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.SourceType
+import com.android.systemui.statusbar.notification.LegacySourceType
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
@@ -314,9 +314,9 @@
         val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnScroll)
+                /* sourceType = */ LegacySourceType.OnScroll)
 
-        NotificationShelf.resetOnScrollRoundness(row)
+        NotificationShelf.resetLegacyOnScrollRoundness(row)
 
         assertEquals(0f, row.topRoundness)
         assertEquals(0f, row.bottomRoundness)
@@ -327,14 +327,14 @@
         val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.DefaultValue)
+                /* sourceType = */ LegacySourceType.DefaultValue)
         val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnDismissAnimation)
+                /* sourceType = */ LegacySourceType.OnDismissAnimation)
 
-        NotificationShelf.resetOnScrollRoundness(row1)
-        NotificationShelf.resetOnScrollRoundness(row2)
+        NotificationShelf.resetLegacyOnScrollRoundness(row1)
+        NotificationShelf.resetLegacyOnScrollRoundness(row2)
 
         assertEquals(1f, row1.topRoundness)
         assertEquals(1f, row1.bottomRoundness)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 4ea1c71..680a323 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -74,6 +74,7 @@
     private NotificationSwipeHelper mSwipeHelper;
     private NotificationSwipeHelper.NotificationCallback mCallback;
     private NotificationMenuRowPlugin.OnMenuEventListener mListener;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
     private View mView;
     private MotionEvent mEvent;
     private NotificationMenuRowPlugin mMenuRow;
@@ -92,10 +93,17 @@
     public void setUp() throws Exception {
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
         mFeatureFlags = mock(FeatureFlags.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
-                mContext.getResources(), ViewConfiguration.get(mContext),
-                new FalsingManagerFake(), mFeatureFlags, SwipeHelper.X, mCallback, mListener));
+                mContext.getResources(),
+                ViewConfiguration.get(mContext),
+                new FalsingManagerFake(),
+                mFeatureFlags,
+                SwipeHelper.X,
+                mCallback,
+                mListener,
+                mNotificationRoundnessManager));
         mView = mock(View.class);
         mEvent = mock(MotionEvent.class);
         mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index a2e9230..81a3f12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -35,7 +35,7 @@
     ) =
         NotificationTargetsHelper(
             FakeFeatureFlags().apply {
-                set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+                set(Flags.USE_ROUNDNESS_SOURCETYPES, notificationGroupCorner)
             }
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 038af8f..6155e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.doAnswer
@@ -295,6 +296,7 @@
     }
 
     @Test
+    @Ignore("b/261408895")
     fun equivalentConfigObject_listenerNotNotified() {
         val config = mContext.resources.configuration
         val listener = createAndAddListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 103b7b42..9727b6c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -32,6 +32,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +73,8 @@
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
     private CommandQueue mCommandQueue;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
+    private FeatureFlags mFeatureFlag;
 
     @Before
     public void setUp() throws Exception {
@@ -89,6 +93,8 @@
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
         mCommandQueue = mock(CommandQueue.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
+        mFeatureFlag = mock(FeatureFlags.class);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
@@ -100,6 +106,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.of(mOperatorNameView));
@@ -182,6 +190,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.empty());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 5ce51bb..aa7ab0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -31,6 +31,7 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
@@ -221,6 +222,21 @@
         }
 
     @Test
+    fun testFlowForSubId_dataConnectionState_unknown() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+
+            job.cancel()
+        }
+
+    @Test
     fun testFlowForSubId_dataActivity() =
         runBlocking(IMMEDIATE) {
             var latest: MobileSubscriptionModel? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c16e129..3d9fd96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -64,6 +65,7 @@
     private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock
     private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock
     private lateinit var connectivityConstants: ConnectivityConstants
     @Mock
@@ -103,6 +105,7 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             scope,
             statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 3001b81..12b93819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -67,6 +69,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -123,6 +126,7 @@
                     connectivityConstants,
                     context,
                     logger,
+                    tableLogBuffer,
                     interactor,
                     scope,
                     statusBarPipelineFlags,
@@ -137,15 +141,21 @@
             yield()
 
             // THEN we get the expected icon
-            assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
-            val expectedContentDescription =
-                if (testCase.expected == null) {
-                    null
-                } else {
-                    testCase.expected.contentDescription.invoke(context)
+            val actualIcon = iconFlow.value
+            when (testCase.expected) {
+                null -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java)
                 }
-            assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
-                .isEqualTo(expectedContentDescription)
+                else -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java)
+                    val actualIconVisible = actualIcon as WifiIcon.Visible
+                    assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource)
+                    val expectedContentDescription =
+                        testCase.expected.contentDescription.invoke(context)
+                    assertThat(actualIconVisible.contentDescription.loadContentDescription(context))
+                        .isEqualTo(expectedContentDescription)
+                }
+            }
 
             job.cancel()
         }
@@ -174,7 +184,7 @@
         val isDefault: Boolean = false,
         val network: WifiNetworkModel,
 
-        /** The expected output. Null if we expect the output to be null. */
+        /** The expected output. Null if we expect the output to be hidden. */
         val expected: Expected?
     ) {
         override fun toString(): String {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 6a6b2a8..7502020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -59,6 +60,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -103,21 +105,21 @@
 
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
-        var latestHome: Icon? = null
+        var latestHome: WifiIcon? = null
         val jobHome = underTest
             .home
             .wifiIcon
             .onEach { latestHome = it }
             .launchIn(this)
 
-        var latestKeyguard: Icon? = null
+        var latestKeyguard: WifiIcon? = null
         val jobKeyguard = underTest
             .keyguard
             .wifiIcon
             .onEach { latestKeyguard = it }
             .launchIn(this)
 
-        var latestQs: Icon? = null
+        var latestQs: WifiIcon? = null
         val jobQs = underTest
             .qs
             .wifiIcon
@@ -133,7 +135,7 @@
         )
         yield()
 
-        assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+        assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
         assertThat(latestHome).isEqualTo(latestKeyguard)
         assertThat(latestKeyguard).isEqualTo(latestQs)
 
@@ -541,6 +543,7 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             scope,
             statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 0000000..3767fbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+    private static File sFilesDir = null;
+    private static String sLatestTestClassName = null;
+
+    @Before public void grabFilesDir() {
+        if (sFilesDir == null) {
+            sFilesDir = mContext.getFilesDir();
+        }
+        sLatestTestClassName = getClass().getName();
+    }
+
+    @AfterClass
+    public static void dumpHeap() throws IOException {
+        if (sFilesDir == null) {
+            Log.e("MEMORY", "Somehow no test cases??");
+            return;
+        }
+        mockitoTearDown();
+        Log.w("MEMORY", "about to dump heap");
+        File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+        Debug.dumpHprofData(path.getPath());
+        Log.w("MEMORY", "did it!  Location: " + path);
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 21e16a1..8a10bf06 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -81,11 +81,6 @@
         properties.get(namespace).put(name, value);
     }
 
-    @Override
-    public void enforceReadPermission(String namespace) {
-        // no-op
-    }
-
     private Properties propsForNamespaceAndName(String namespace, String name) {
         if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
             return new Properties.Builder(namespace)
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index abd08de..ccff624 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -2,3 +2,4 @@
 per-file wifi.proto = file:/wifi/OWNERS
 per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
 per-file system_messages.proto = file:/core/res/OWNERS
+per-file altitude.proto = file:/location/OWNERS
diff --git a/proto/src/altitude.proto b/proto/src/altitude.proto
new file mode 100644
index 0000000..1010f67
--- /dev/null
+++ b/proto/src/altitude.proto
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.location.altitude;
+
+option java_package = "com.android.internal.location.altitude";
+option java_multiple_files = true;
+
+// Defines parameters for a spherically projected geoid map and corresponding
+// tile management.
+message MapParamsProto {
+  // Defines the resolution of the map in terms of an S2 level.
+  optional int32 map_s2_level = 1;
+  // Defines the resolution of the tiles in cache in terms of an S2 level.
+  optional int32 cache_tile_s2_level = 2;
+  // Defines the resolution of the tiles on disk in terms of an S2 level.
+  optional int32 disk_tile_s2_level = 3;
+  // Defines the `a` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_a_meters = 4;
+  // Defines the `b` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_b_meters = 5;
+  // Defines the root mean square error in meters of the geoid height.
+  optional double model_rmse_meters = 6;
+}
+
+// A single tile associating values in the unit interval [0, 1] to map cells.
+message S2TileProto {
+  // The S2 token associated with the common parent of all map cells in this
+  // tile.
+  optional string tile_key = 1;
+
+  // Encoded data that merge into a value in the unit interval [0, 1] for each
+  // map cell in this tile.
+  optional bytes byte_buffer = 2;
+  optional bytes byte_jpeg = 3;
+  optional bytes byte_png = 4;
+}
diff --git a/services/api/current.txt b/services/api/current.txt
index 834ed2f..18b1df3 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -59,6 +59,7 @@
     method public void close();
     method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
     method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
+    method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
   }
 
   public static interface PackageManagerLocal.UnfilteredSnapshot extends java.lang.AutoCloseable {
@@ -98,6 +99,7 @@
   public interface PackageState {
     method @Nullable public com.android.server.pm.pkg.AndroidPackage getAndroidPackage();
     method public int getAppId();
+    method public int getHiddenApiEnforcementPolicy();
     method @NonNull public String getPackageName();
     method @Nullable public String getPrimaryCpuAbi();
     method @Nullable public String getSeInfo();
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index f1ba5ff..5e68d52 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,8 +16,7 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.virtual.VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,10 +25,10 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
-import android.companion.AssociationRequest;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.VirtualDeviceParams.ActivityPolicy;
+import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -127,10 +126,10 @@
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
             new ArraySet<>();
-    @Nullable
-    private final @AssociationRequest.DeviceProfile String mDeviceProfile;
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
     @Nullable private final List<String> mDisplayCategories;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -156,7 +155,7 @@
      *   launching.
      * @param secureWindowCallback Callback that is called when a secure window shows on the
      *   virtual display.
-     * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
+     * @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
      */
     public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
@@ -169,8 +168,8 @@
             @NonNull PipBlockedCallback pipBlockedCallback,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
             @NonNull SecureWindowCallback secureWindowCallback,
-            @AssociationRequest.DeviceProfile String deviceProfile,
-            @NonNull List<String> displayCategories) {
+            @NonNull List<String> displayCategories,
+            @RecentsPolicy int defaultRecentsPolicy) {
         super();
         mAllowedUsers = allowedUsers;
         mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -181,10 +180,10 @@
         mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
-        mDeviceProfile = deviceProfile;
         mPipBlockedCallback = pipBlockedCallback;
         mSecureWindowCallback = secureWindowCallback;
         mDisplayCategories = displayCategories;
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     /**
@@ -318,18 +317,8 @@
     }
 
     @Override
-    public boolean canShowTasksInRecents() {
-        if (mDeviceProfile == null) {
-            return true;
-        }
-        // TODO(b/234075973) : Remove this once proper API is ready.
-        switch (mDeviceProfile) {
-            case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
-                return false;
-            case DEVICE_PROFILE_APP_STREAMING:
-            default:
-                return true;
-        }
+    public boolean canShowTasksInHostDeviceRecents() {
+        return (mDefaultRecentsPolicy & RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS) != 0;
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ef93810..5819861 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -679,8 +679,8 @@
                             this::onEnteringPipBlocked,
                             this::onActivityBlocked,
                             this::onSecureWindowShown,
-                            mAssociationInfo.getDeviceProfile(),
-                            displayCategories);
+                            displayCategories,
+                            mParams.getDefaultRecentsPolicy());
             gwpc.registerRunningAppsChangedListener(/* listener= */ this);
             return gwpc;
         }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bd90d85..32afcca 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1041,7 +1041,7 @@
             String callingFeatureId, IPhoneStateListener callback,
             int[] events, boolean notifyNow) {
         Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
-        listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+        listen(renounceFineLocationAccess, renounceCoarseLocationAccess, callingPackage,
                 callingFeatureId, callback, eventList, notifyNow, subId);
     }
 
@@ -1606,7 +1606,7 @@
                             if (DBG) {
                                 log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
                                         + " subId=" + subId + " phoneId=" + phoneId
-                                        + " state=" + state);
+                                        + " state=" + stateToSend);
                             }
                             r.callback.onServiceStateChanged(stateToSend);
                         } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7566bab..efe14f4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18324,7 +18324,7 @@
 
     public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
-        BroadcastLoopers.waitForIdle(pw);
+        BroadcastLoopers.waitForBarrier(pw);
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForBarrier(pw);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 1eebd01..f5d1c10 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,12 +153,27 @@
             "bcast_extra_running_urgent_process_queues";
     private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
 
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
     public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
     private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
             "bcast_max_consecutive_urgent_dispatches";
     private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
 
     /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
+    public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES =
+            "bcast_max_consecutive_normal_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10;
+
+    /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
      * to dispatch to a "running" process queue before we retire them back to
      * being "runnable" to give other processes a chance to run.
@@ -341,6 +356,9 @@
             MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
                     KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
                     DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
+            MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
@@ -396,6 +414,10 @@
                     TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
             pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
             pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+            pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
+            pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index b828720..a5535cb 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
@@ -30,6 +31,7 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
 
 /**
  * Collection of {@link Looper} that are known to be used for broadcast dispatch
@@ -73,19 +75,44 @@
      * still in the future are ignored for the purposes of the idle test.
      */
     public static void waitForIdle(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            final MessageQueue queue = looper.getQueue();
+            queue.addIdleHandler(() -> {
+                latch.countDown();
+                return false;
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to handle currently waiting messages.
+     * Note that {@link Message#when} still in the future are ignored for the purposes
+     * of the idle test.
+     */
+    public static void waitForBarrier(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            (new Handler(looper)).post(() -> {
+                latch.countDown();
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to meet a certain condition.
+     */
+    private static void waitForCondition(@Nullable PrintWriter pw,
+            @NonNull BiConsumer<Looper, CountDownLatch> condition) {
         final CountDownLatch latch;
         synchronized (sLoopers) {
             final int N = sLoopers.size();
             latch = new CountDownLatch(N);
             for (int i = 0; i < N; i++) {
-                final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+                final Looper looper = sLoopers.valueAt(i);
+                final MessageQueue queue = looper.getQueue();
                 if (queue.isIdle()) {
                     latch.countDown();
                 } else {
-                    queue.addIdleHandler(() -> {
-                        latch.countDown();
-                        return false;
-                    });
+                    condition.accept(looper, latch);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 66d7fc9..672392d 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -153,6 +153,12 @@
     private int mActiveCountConsecutiveUrgent;
 
     /**
+     * Number of consecutive normal broadcasts that have been dispatched
+     * since the last offload dispatch.
+     */
+    private int mActiveCountConsecutiveNormal;
+
+    /**
      * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
@@ -551,48 +557,63 @@
      * Will thrown an exception if there are no pending broadcasts; relies on
      * {@link #isEmpty()} being false.
      */
-    SomeArgs removeNextBroadcast() {
+    private @Nullable SomeArgs removeNextBroadcast() {
         final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
         if (queue == mPendingUrgent) {
             mActiveCountConsecutiveUrgent++;
-        } else {
+        } else if (queue == mPending) {
             mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal++;
+        } else if (queue == mPendingOffload) {
+            mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal = 0;
         }
-        return queue.removeFirst();
+        return !isQueueEmpty(queue) ? queue.removeFirst() : null;
     }
 
     @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
-        ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
-        ArrayDeque<SomeArgs> nextNormal = null;
-        if (!mPending.isEmpty()) {
-            nextNormal = mPending;
-        } else if (!mPendingOffload.isEmpty()) {
-            nextNormal = mPendingOffload;
+        final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
+                mPending, mPendingOffload,
+                mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
+        final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
+                mPendingUrgent, nextNormal,
+                mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
+        return nextBroadcastQueue;
+    }
+
+    private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
+            @Nullable ArrayDeque<SomeArgs> highPriorityQueue,
+            @Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
+            int consecutiveHighPriorityCount,
+            int maxHighPriorityDispatchLimit) {
+        // nothing high priority pending, no further decisionmaking
+        if (isQueueEmpty(highPriorityQueue)) {
+            return lowPriorityQueue;
         }
-        // nothing urgent pending, no further decisionmaking
-        if (nextUrgent == null) {
-            return nextNormal;
-        }
-        // nothing but urgent pending, also no further decisionmaking
-        if (nextNormal == null) {
-            return nextUrgent;
+        // nothing but high priority pending, also no further decisionmaking
+        if (isQueueEmpty(lowPriorityQueue)) {
+            return highPriorityQueue;
         }
 
-        // Starvation mitigation: although we prioritize urgent broadcasts by default,
-        // we allow non-urgent deliveries to make steady progress even if urgent
-        // broadcasts are arriving faster than they can be dispatched.
+        // Starvation mitigation: although we prioritize high priority queues by default,
+        // we allow low priority queues to make steady progress even if broadcasts in
+        // high priority queue are arriving faster than they can be dispatched.
         //
-        // We do not try to defer to the next non-urgent broadcast if that broadcast
+        // We do not try to defer to the next broadcast in low priority queues if that broadcast
         // is ordered and still blocked on delivery to other recipients.
-        final SomeArgs nextNormalArgs = nextNormal.peekFirst();
-        final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
-        final int nextNormalIndex = nextNormalArgs.argi1;
-        final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
-        final boolean canTakeNormal =
-                mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
-                        && rNormal.enqueueTime <= rUrgent.enqueueTime
-                        && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
-        return canTakeNormal ? nextNormal : nextUrgent;
+        final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
+        final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
+        final int nextLPRecordIndex = nextLPArgs.argi1;
+        final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
+        final boolean isLPQueueEligible =
+                consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit
+                        && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+                        && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+        return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
+    }
+
+    private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
+        return (queue == null || queue.isEmpty());
     }
 
     /**
@@ -600,13 +621,13 @@
      */
     @Nullable SomeArgs peekNextBroadcast() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? queue.peekFirst() : null;
+        return !isQueueEmpty(queue) ? queue.peekFirst() : null;
     }
 
     @VisibleForTesting
     @Nullable BroadcastRecord peekNextBroadcastRecord() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+        return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5d5dbbb..e9340e9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -220,10 +220,19 @@
 
     private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
+        enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
+                deliveryState, reason);
+    }
+
+    private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index,
+            @DeliveryState int deliveryState, @NonNull String reason) {
         final SomeArgs args = SomeArgs.obtain();
         args.arg1 = queue;
         args.argi1 = deliveryState;
         args.arg2 = reason;
+        args.arg3 = r;
+        args.argi2 = index;
         mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
     }
 
@@ -271,8 +280,10 @@
                     final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
                     final int deliveryState = args.argi1;
                     final String reason = (String) args.arg2;
+                    final BroadcastRecord r = (BroadcastRecord) args.arg3;
+                    final int index = args.argi2;
                     args.recycle();
-                    finishReceiverLocked(queue, deliveryState, reason);
+                    finishReceiverLocked(queue, deliveryState, reason, r, index);
                 }
                 return true;
             }
@@ -729,10 +740,8 @@
     private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
-        final Object receiver = r.receivers.get(index);
 
         if (r.terminalCount == 0) {
             r.dispatchTime = SystemClock.uptimeMillis();
@@ -740,26 +749,40 @@
             r.dispatchClockTime = System.currentTimeMillis();
         }
 
-        // If someone already finished this broadcast, finish immediately
+        if (maybeSkipReceiver(queue, r, index)) {
+            return;
+        }
+        dispatchReceivers(queue, r, index);
+    }
+
+    /**
+     * Examine a receiver and possibly skip it.  The method returns true if the receiver is
+     * skipped (and therefore no more work is required).
+     */
+    private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
         final int oldDeliveryState = getDeliveryState(r, index);
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
+
+        // If someone already finished this broadcast, finish immediately
         if (isDeliveryStateTerminal(oldDeliveryState)) {
             enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
-            return;
+            return true;
         }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
-            return;
+            return true;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
-            return;
+            return true;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
-            return;
+            return true;
         }
 
         // Ignore registered receivers from a previous PID
@@ -767,12 +790,29 @@
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
                     "BroadcastFilter for mismatched PID");
-            return;
+            return true;
         }
+        // The receiver was not handled in this method.
+        return false;
+    }
+
+    /**
+     * Return true if this receiver should be assumed to have been delivered.
+     */
+    private boolean isAssumedDelivered(BroadcastRecord r, int index) {
+        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+    }
+
+    /**
+     * A receiver is about to be dispatched.  Start ANR timers, if necessary.
+     */
+    private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
 
         // Skip ANR tracking early during boot, when requested, or when we
         // immediately assume delivery success
-        final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+        final boolean assumeDelivered = isAssumedDelivered(r, index);
         if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
@@ -805,6 +845,7 @@
         setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
                 "scheduleReceiverWarmLocked");
 
+        final Intent receiverIntent = r.getReceiverIntent(receiver);
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
             try {
@@ -920,6 +961,19 @@
         return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
     }
 
+    /**
+     * Return true if there are more broadcasts in the queue and the queue is runnable.
+     */
+    private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
+        // If we've made reasonable progress, periodically retire ourselves to
+        // avoid starvation of other processes and stack overflow when a
+        // broadcast is immediately finished without waiting
+        final boolean shouldRetire =
+                (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+
+        return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+    }
+
     private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
         if (!queue.isActive()) {
@@ -927,10 +981,21 @@
             return false;
         }
 
-        final int cookie = traceBegin("finishReceiver");
-        final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
+        return finishReceiverLocked(queue, deliveryState, reason, r, index);
+    }
+
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState, @NonNull String reason,
+            BroadcastRecord r, int index) {
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
+
+        final int cookie = traceBegin("finishReceiver");
+        final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
         setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
@@ -945,18 +1010,11 @@
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
         }
 
-        // If we've made reasonable progress, periodically retire ourselves to
-        // avoid starvation of other processes and stack overflow when a
-        // broadcast is immediately finished without waiting
-        final boolean shouldRetire =
-                (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
-
-        final boolean res;
-        if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
+        final boolean res = shouldContinueScheduling(queue);
+        if (res) {
             // We're on a roll; move onto the next broadcast for this process
             queue.makeActiveNextPending();
             scheduleReceiverWarmLocked(queue);
-            res = true;
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
@@ -970,7 +1028,6 @@
             // Tell other OS components that app is not actively running, giving
             // a chance to update OOM adjustment
             notifyStoppedRunning(queue);
-            res = false;
         }
         traceEnd(cookie);
         return res;
@@ -1442,10 +1499,10 @@
 
     private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
         mService.notifyBroadcastFinishedLocked(r);
-        mHistory.addBroadcastToHistoryLocked(r);
-
         r.finishTime = SystemClock.uptimeMillis();
         r.nextReceiver = r.receivers.size();
+        mHistory.addBroadcastToHistoryLocked(r);
+
         BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
 
         if (r.intent.getComponent() == null && r.intent.getPackage() == null
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a7e95fb..aefa2f5 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -949,9 +949,8 @@
     int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
-        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
-            throw new IllegalArgumentException("Can't stop system user " + userId);
-        }
+        Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);
+
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
             return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 8d1da71..587fb04 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -218,7 +218,7 @@
     }
 
     @Override
-    public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
+    public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             if (packageModes == null) {
@@ -490,15 +490,16 @@
     }
 
     @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+    public SparseBooleanArray evalForegroundUidOps(int uid,
+            @Nullable SparseBooleanArray foregroundOps) {
         synchronized (mLock) {
             return evalForegroundOps(mUidModes.get(uid), foregroundOps);
         }
     }
 
     @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+    public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -537,8 +538,8 @@
     }
 
     @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
+    public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter) {
         boolean needSep = false;
         if (mOpModeWatchers.size() > 0) {
             boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48..ef3e368 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@
      * @param packageName package name.
      * @param userId user id associated with the package.
      */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+    boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
 
     /**
      * Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@
      * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
      * @return  foregroundOps.
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+    SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
 
     /**
      * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@
      * @param userId user id associated with the package.
      * @return foregroundOps.
      */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
 
     /**
      * Dump op mode and package mode listeners and their details.
@@ -205,5 +205,6 @@
      * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
      * @param printWriter writer to dump to.
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+    boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter);
 }
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 5ebe811..1d1a9e7 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -22,7 +22,7 @@
  * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
  * change.
  */
-abstract class OnOpModeChangedListener {
+public abstract class OnOpModeChangedListener {
 
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 519429f..aa8ee3d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -86,6 +86,7 @@
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
+import android.media.AudioHalVersionInfo;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
@@ -257,6 +258,9 @@
     /** Debug communication route */
     protected static final boolean DEBUG_COMM_RTE = false;
 
+    /** Debug log sound fx (touchsounds...) in dumpsys */
+    protected static final boolean DEBUG_LOG_SOUND_FX = false;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -378,6 +382,7 @@
     private static final int MSG_ROTATION_UPDATE = 48;
     private static final int MSG_FOLD_UPDATE = 49;
     private static final int MSG_RESET_SPATIALIZER = 50;
+    private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1016,7 +1021,7 @@
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
 
-        mSfxHelper = new SoundEffectsHelper(mContext);
+        mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
         final boolean headTrackingDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
@@ -1500,6 +1505,18 @@
     }
 
     //-----------------------------------------------------------------
+    // Communicate to PlayackActivityMonitor whether to log or not
+    // the sound FX activity (useful for removing touch sounds in the activity logs)
+    void ignorePlayerLogs(@NonNull PlayerBase playerToIgnore) {
+        if (DEBUG_LOG_SOUND_FX) {
+            return;
+        }
+        sendMsg(mAudioHandler, MSG_NO_LOG_FOR_PLAYER_I, SENDMSG_REPLACE,
+                /*arg1, piid of the player*/ playerToIgnore.getPlayerIId(),
+                /*arg2 ignored*/ 0, /*obj ignored*/ null, /*delay*/ 0);
+    }
+
+    //-----------------------------------------------------------------
     // monitoring requests for volume range initialization
     @Override // AudioSystemAdapter.OnVolRangeInitRequestListener
     public void onVolumeRangeInitRequestFromNative() {
@@ -6622,9 +6639,13 @@
                     return AudioSystem.STREAM_RING;
                 } else if (wasStreamActiveRecently(
                         AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
-                    return AudioSystem.STREAM_NOTIFICATION;
+                        if (DEBUG_VOL) {
+                            Log.v(
+                                    TAG,
+                                    "getActiveStreamType: Forcing STREAM_NOTIFICATION stream"
+                                            + " active");
+                        }
+                        return AudioSystem.STREAM_NOTIFICATION;
                 } else {
                     if (DEBUG_VOL) {
                         Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK("
@@ -8721,6 +8742,10 @@
                     // fold parameter format: "device_folded=x" where x is one of on, off
                     mAudioSystem.setParameters((String) msg.obj);
                     break;
+
+                case MSG_NO_LOG_FOR_PLAYER_I:
+                    mPlaybackMonitor.ignorePlayerIId(msg.arg1);
+                    break;
             }
         }
     }
@@ -11299,15 +11324,16 @@
         return mMediaFocusControl.sendFocusLoss(focusLoser);
     }
 
-    private static final String[] HAL_VERSIONS =
-            new String[] {"7.1", "7.0", "6.0", "5.0", "4.0", "2.0"};
-
-    /** @see AudioManager#getHalVersion */
-    public @Nullable String getHalVersion() {
-        for (String version : HAL_VERSIONS) {
+    /**
+     * @see AudioManager#getHalVersion
+     */
+    public @Nullable AudioHalVersionInfo getHalVersion() {
+        for (AudioHalVersionInfo version : AudioHalVersionInfo.VERSIONS) {
             try {
+                // TODO: check AIDL service.
+                String versionStr = version.getMajorVersion() + "." + version.getMinorVersion();
                 HwBinder.getService(
-                        String.format("android.hardware.audio@%s::IDevicesFactory", version),
+                        String.format("android.hardware.audio@%s::IDevicesFactory", versionStr),
                         "default");
                 return version;
             } catch (NoSuchElementException e) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 89dfc17..f35931ca 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -192,6 +192,18 @@
     }
 
     //=================================================================
+    // Player to ignore (only handling single player, designed for ignoring
+    // in the logs one specific player such as the touch sounds player)
+    @GuardedBy("mPlayerLock")
+    private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
+
+    /*package*/ void ignorePlayerIId(int doNotLogPiid) {
+        synchronized (mPlayerLock) {
+            mDoNotLogPiidList.add(doNotLogPiid);
+        }
+    }
+
+    //=================================================================
     // Track players and their states
     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -315,14 +327,18 @@
             Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
                     piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
         }
-
-        final boolean change;
+        boolean change;
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (apc == null) {
                 return;
             }
 
+            final boolean doNotLog = mDoNotLogPiidList.contains(piid);
+            if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+                // do not log nor dispatch events for "ignored" players other than the release
+                return;
+            }
             sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
@@ -339,7 +355,8 @@
                     }
                 }
             }
-            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+                    && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
                 // FIXME SoundPool not ready for state reporting
                 return;
             }
@@ -351,9 +368,15 @@
                 Log.e(TAG, "Error handling event " + event);
                 change = false;
             }
-            if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                mDuckingManager.checkDuck(apc);
-                mFadingManager.checkFade(apc);
+            if (change) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    mDuckingManager.checkDuck(apc);
+                    mFadingManager.checkFade(apc);
+                }
+                if (doNotLog) {
+                    // do not dispatch events for "ignored" players
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -436,6 +459,10 @@
                 mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
 
+                if (change && mDoNotLogPiidList.contains(piid)) {
+                    // do not dispatch a change for a "do not log" player
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -581,6 +608,9 @@
             for (Integer piidInt : piidIntList) {
                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
                 if (apc != null) {
+                    if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
+                        pw.print("(not logged)");
+                    }
                     apc.dump(pw);
                 }
             }
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 79b54eb..8c4efba 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -25,6 +25,7 @@
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
 import android.media.SoundPool;
 import android.os.Environment;
 import android.os.Handler;
@@ -47,6 +48,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * A helper class for managing sound effects loading / unloading
@@ -109,11 +111,14 @@
     private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
     private SoundPool mSoundPool;
     private SoundPoolLoader mSoundPoolLoader;
+    /** callback to provide handle to the player of the sound effects */
+    private final Consumer<PlayerBase> mPlayerAvailableCb;
 
-    SoundEffectsHelper(Context context) {
+    SoundEffectsHelper(Context context, Consumer<PlayerBase> playerAvailableCb) {
         mContext = context;
         mSfxAttenuationDb = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_soundEffectVolumeDb);
+        mPlayerAvailableCb = playerAvailableCb;
         startWorker();
     }
 
@@ -189,6 +194,7 @@
                         .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                         .build())
                 .build();
+        mPlayerAvailableCb.accept(mSoundPool);
         loadSoundAssets();
 
         mSoundPoolLoader = new SoundPoolLoader();
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index ddb9243..fe1d1a6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -245,6 +245,12 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at.
+     * @see android.view.DisplayInfo#renderFrameRate for more details.
+     */
+    public float renderFrameRate;
+
+    /**
      * The default mode of the display.
      */
     public int defaultModeId;
@@ -439,6 +445,7 @@
                 || width != other.width
                 || height != other.height
                 || modeId != other.modeId
+                || renderFrameRate != other.renderFrameRate
                 || defaultModeId != other.defaultModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -483,6 +490,7 @@
         width = other.width;
         height = other.height;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = other.supportedModes;
         colorMode = other.colorMode;
@@ -523,6 +531,7 @@
         sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
         sb.append(width).append(" x ").append(height);
         sb.append(", modeId ").append(modeId);
+        sb.append(", renderFrameRate ").append(renderFrameRate);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
         sb.append(", colorMode ").append(colorMode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0d1aca8..ae84e96 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,6 +36,7 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 
 import android.Manifest;
@@ -882,20 +883,27 @@
 
     private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
             frameRateOverrides, DisplayInfo info, int callingUid) {
-        float frameRateHz = 0;
+        float frameRateHz = info.renderFrameRate;
         for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
             if (frameRateOverride.uid == callingUid) {
                 frameRateHz = frameRateOverride.frameRateHz;
                 break;
             }
         }
+
         if (frameRateHz == 0) {
             return info;
         }
 
+        // For non-apps users we always return the physical refresh rate from display mode
+        boolean displayModeReturnsPhysicalRefreshRate =
+                callingUid < FIRST_APPLICATION_UID
+                        || CompatChanges.isChangeEnabled(
+                                DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
+
         // Override the refresh rate only if it is a divisor of the current
         // refresh rate. This calculation needs to be in sync with the native code
-        // in RefreshRateConfigs::getFrameRateDivisor
+        // in RefreshRateSelector::getFrameRateDivisor
         Display.Mode currentMode = info.getMode();
         float numPeriods = currentMode.getRefreshRate() / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
@@ -919,8 +927,7 @@
                 }
                 overriddenInfo.refreshRateOverride = mode.getRefreshRate();
 
-                if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                        callingUid)) {
+                if (!displayModeReturnsPhysicalRefreshRate) {
                     overriddenInfo.modeId = mode.getModeId();
                 }
                 return overriddenInfo;
@@ -928,8 +935,7 @@
         }
 
         overriddenInfo.refreshRateOverride = frameRateHz;
-        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                callingUid)) {
+        if (!displayModeReturnsPhysicalRefreshRate) {
             overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
                     info.supportedModes.length + 1);
             overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index dc5c80f2..ee53b60 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -226,6 +226,8 @@
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
         // The active display mode in SurfaceFlinger
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
+        // The active display vsync period in SurfaceFlinger
+        private float mActiveRenderFrameRate;
 
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
@@ -267,7 +269,7 @@
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             boolean changed = updateDisplayModesLocked(
                     dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode,
-                    dynamicInfo.activeDisplayModeId, modeSpecs);
+                    dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs);
             changed |= updateStaticInfo(staticInfo);
             changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
                     dynamicInfo.activeColorMode);
@@ -283,7 +285,8 @@
 
         public boolean updateDisplayModesLocked(
                 SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId,
-                int activeSfDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
+                int activeSfDisplayModeId, float renderFrameRate,
+                SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
             mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId);
             SurfaceControl.DisplayMode preferredSfDisplayMode =
@@ -379,6 +382,16 @@
                 sendTraversalRequestLocked();
             }
 
+            boolean renderFrameRateChanged = false;
+
+            if (mActiveRenderFrameRate > 0 &&  mActiveRenderFrameRate != renderFrameRate) {
+                Slog.d(TAG, "The render frame rate was changed from SurfaceFlinger or the display"
+                        + " device to " + renderFrameRate);
+                mActiveRenderFrameRate = renderFrameRate;
+                renderFrameRateChanged = true;
+                sendTraversalRequestLocked();
+            }
+
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
             if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) {
@@ -398,7 +411,7 @@
             boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
             // If the records haven't changed then we're done here.
             if (!recordsChanged) {
-                return activeModeChanged || preferredModeChanged;
+                return activeModeChanged || preferredModeChanged || renderFrameRateChanged;
             }
 
             mSupportedModes.clear();
@@ -410,16 +423,19 @@
             if (mDefaultModeId == INVALID_MODE_ID) {
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (findSfDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
                 Slog.w(TAG, "Default display mode no longer available, using currently"
                         + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             }
 
             // Determine whether the display mode specs' base mode is still there.
@@ -620,6 +636,7 @@
                 mInfo.width = mActiveSfDisplayMode.width;
                 mInfo.height = mActiveSfDisplayMode.height;
                 mInfo.modeId = mActiveModeId;
+                mInfo.renderFrameRate = mActiveRenderFrameRate;
                 mInfo.defaultModeId = getPreferredModeId();
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
@@ -995,8 +1012,8 @@
             updateDeviceInfoLocked();
         }
 
-        public void onActiveDisplayModeChangedLocked(int sfModeId) {
-            if (updateActiveModeLocked(sfModeId)) {
+        public void onActiveDisplayModeChangedLocked(int sfModeId, float renderFrameRate) {
+            if (updateActiveModeLocked(sfModeId, renderFrameRate)) {
                 updateDeviceInfoLocked();
             }
         }
@@ -1008,8 +1025,9 @@
             }
         }
 
-        public boolean updateActiveModeLocked(int activeSfModeId) {
-            if (mActiveSfDisplayMode.id == activeSfModeId) {
+        public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
+            if (mActiveSfDisplayMode.id == activeSfModeId
+                    && mActiveRenderFrameRate == renderFrameRate) {
                 return false;
             }
             mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
@@ -1018,6 +1036,7 @@
                 Slog.w(TAG, "In unknown mode after setting allowed modes"
                         + ", activeModeId=" + activeSfModeId);
             }
+            mActiveRenderFrameRate = renderFrameRate;
             return true;
         }
 
@@ -1114,6 +1133,7 @@
                 pw.println("  " + sfDisplayMode);
             }
             pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
+            pw.println("mActiveRenderFrameRate=" + mActiveRenderFrameRate);
             pw.println("mSupportedModes=");
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
@@ -1288,7 +1308,8 @@
 
     public interface DisplayEventListener {
         void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
-        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
+        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod);
         void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
                 DisplayEventReceiver.FrameRateOverride[] overrides);
 
@@ -1309,8 +1330,9 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
+            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
         }
 
         @Override
@@ -1333,12 +1355,14 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
             if (DEBUG) {
                 Slog.d(TAG, "onModeChanged("
                         + "timestampNanos=" + timestampNanos
                         + ", physicalDisplayId=" + physicalDisplayId
-                        + ", modeId=" + modeId + ")");
+                        + ", modeId=" + modeId
+                        + ", renderPeriod=" + renderPeriod + ")");
             }
             synchronized (getSyncRoot()) {
                 LocalDisplayDevice device = mDevices.get(physicalDisplayId);
@@ -1349,7 +1373,8 @@
                     }
                     return;
                 }
-                device.onActiveDisplayModeChangedLocked(modeId);
+                float renderFrameRate = 1e9f / renderPeriod;
+                device.onActiveDisplayModeChangedLocked(modeId, renderFrameRate);
             }
         }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8dd169bf..c7b27de 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -377,6 +377,7 @@
             mBaseDisplayInfo.logicalHeight = maskedHeight;
             mBaseDisplayInfo.rotation = Surface.ROTATION_0;
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
+            mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0e11b53..3e67f0a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -340,6 +340,7 @@
                 mInfo.width = mode.getPhysicalWidth();
                 mInfo.height = mode.getPhysicalHeight();
                 mInfo.modeId = mode.getModeId();
+                mInfo.renderFrameRate = mode.getRefreshRate();
                 mInfo.defaultModeId = mModes[0].getModeId();
                 mInfo.supportedModes = mModes;
                 mInfo.densityDpi = rawMode.mDensityDpi;
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index f30a84f..e7601bc 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -619,7 +619,7 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness;
+        private float mBrightness = Float.NaN;
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -700,7 +700,11 @@
                         break;
                     case TAG_BRIGHTNESS_VALUE:
                         String brightness = parser.nextText();
-                        mBrightness = Float.parseFloat(brightness);
+                        try {
+                            mBrightness = Float.parseFloat(brightness);
+                        } catch (NumberFormatException e) {
+                            mBrightness = Float.NaN;
+                        }
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -727,7 +731,9 @@
             serializer.endTag(null, TAG_COLOR_MODE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            serializer.text(Float.toString(mBrightness));
+            if (!Float.isNaN(mBrightness)) {
+                serializer.text(Float.toString(mBrightness));
+            }
             serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d24630d..a118b2f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -448,6 +448,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.densityDpi = mDensityDpi;
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index c759d98..e832701 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -646,6 +646,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 4855be6..ccb2633 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 
 /**
@@ -33,6 +35,10 @@
         super(source);
     }
 
+    ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+        super(source, callback);
+    }
+
     @Override
     boolean start() {
         mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE;
@@ -47,10 +53,19 @@
             return false;
         }
         switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_FEATURE_ABORT:
+                int originalOpcode = cmd.getParams()[0] & 0xFF;
+                if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) {
+                    mState = STATE_ARC_TERMINATED;
+                    audioSystem().processArcTermination();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+                    return true;
+                }
+                return false;
             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
                 mState = STATE_ARC_TERMINATED;
                 audioSystem().processArcTermination();
-                finish();
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
                 return true;
         }
         return false;
@@ -79,7 +94,7 @@
                         audioSystem().setArcStatus(false);
                     }
                     HdmiLogger.debug("Terminate ARC was not successfully sent.");
-                    finish();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
                 }
             });
     }
@@ -88,6 +103,6 @@
         // Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
         audioSystem().setArcStatus(false);
         HdmiLogger.debug("handleTerminateArcTimeout");
-        finish();
+        finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index ccaa9255d..a026c4b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -458,8 +458,16 @@
             HdmiLogger.debug("ARC is not established between TV and AVR device");
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
-            removeAction(ArcTerminationActionFromAvr.class);
-            addAndStartAction(new ArcTerminationActionFromAvr(this));
+            if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
+                    && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
+                IHdmiControlCallback callback =
+                        getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
+            } else {
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this));
+            }
             return Constants.HANDLED;
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 43cd71a..2f15e57 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -878,16 +878,30 @@
             Slog.w(TAG, "Device type doesn't support ARC.");
             return;
         }
+        boolean isArcEnabled = false;
         if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
-            if (audioSystem.isArcEnabled()) {
-                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
-            }
+            isArcEnabled = audioSystem.isArcEnabled();
             if (isSystemAudioActivated()) {
                 audioSystem.terminateSystemAudioMode();
             }
+            if (isArcEnabled) {
+                if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) {
+                    audioSystem.removeAction(ArcTerminationActionFromAvr.class);
+                }
+                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem,
+                        new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                mAddressAllocated = false;
+                                initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+                            }
+                        }));
+            }
         }
-        mAddressAllocated = false;
-        initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+        if (!isArcEnabled) {
+            mAddressAllocated = false;
+            initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 85d6197..1bb14aa 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -145,7 +145,7 @@
     @Override
     public void onInputDeviceChanged(int deviceId) {
         final InputDevice inputDevice = getInputDevice(deviceId);
-        if (inputDevice == null) {
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
         synchronized (mDataStore) {
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 4bbd40d..5f8572b 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -59,6 +59,9 @@
 
     static final int CONCURRENT_SNOOZE_LIMIT = 500;
 
+    // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+    static final int MAX_STRING_LENGTH = 1000;
+
     protected static final String XML_TAG_NAME = "snoozed-notifications";
 
     private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -200,7 +203,7 @@
         scheduleRepost(key, duration);
         Long activateAt = System.currentTimeMillis() + duration;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.put(key, activateAt);
+            mPersistedSnoozedNotifications.put(getTrimmedString(key), activateAt);
         }
     }
 
@@ -210,7 +213,10 @@
     protected void snooze(NotificationRecord record, String contextId) {
         if (contextId != null) {
             synchronized (mLock) {
-                mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
+                mPersistedSnoozedNotificationsWithContext.put(
+                        getTrimmedString(record.getKey()),
+                        getTrimmedString(contextId)
+                );
             }
         }
         snooze(record);
@@ -225,6 +231,13 @@
         }
     }
 
+    private String getTrimmedString(String key) {
+        if (key != null && key.length() > MAX_STRING_LENGTH) {
+            return key.substring(0, MAX_STRING_LENGTH);
+        }
+        return key;
+    }
+
     protected boolean cancel(int userId, String pkg, String tag, int id) {
         synchronized (mLock) {
             final Set<Map.Entry<String, NotificationRecord>> records =
@@ -293,10 +306,12 @@
     }
 
     protected void repost(String key, int userId, boolean muteOnReturn) {
+        final String trimmedKey = getTrimmedString(key);
+
         NotificationRecord record;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.remove(key);
-            mPersistedSnoozedNotificationsWithContext.remove(key);
+            mPersistedSnoozedNotifications.remove(trimmedKey);
+            mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
             record = mSnoozedNotifications.remove(key);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 21c2f2c..fad61b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -167,14 +167,16 @@
         PackageState getPackageState(@NonNull String packageName);
 
         /**
-         * Iterates on all states. This should only be used when either the target package name
-         * is not known or the large majority of the states are expected to be used.
-         *
+         * Returns a map of all {@link PackageState PackageStates} on the device.
+         * <p>
          * This will cause app visibility filtering to be invoked on each state on the device,
-         * which can be expensive.
+         * which can be expensive. Prefer {@link #getPackageState(String)} if possible.
          *
-         * @param consumer Block to accept each state as it becomes available post-filtering.
+         * @return Mapping of package name to {@link PackageState}.
          */
+        @NonNull
+        Map<String, PackageState> getPackageStates();
+
         void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77334e5..a72ae56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -142,6 +142,11 @@
     public static final Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
             pkgSetting -> pkgSetting.getPkg() == null;
 
+    // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+    // A proper fix should be implemented in master instead.
+    public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+            ThreadLocal.withInitial(() -> false);
+
     /**
      * Components of apps targeting Android T and above will stop receiving intents from
      * external callers that do not match its declared intent filters.
@@ -1093,6 +1098,8 @@
             PlatformCompat compat, ComponentResolverApi resolver,
             List<ResolveInfo> resolveInfos, boolean isReceiver,
             Intent intent, String resolvedType, int filterCallingUid) {
+        if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
         final Printer logPrinter = DEBUG_INTENT_MATCHING
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6dcbb52..b18179e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1380,7 +1380,11 @@
         return mSecondaryCpuAbi;
     }
 
-
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    @Override
+    public int getHiddenApiEnforcementPolicy() {
+        return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
+    }
 
     // Code below generated by codegen v1.0.23.
     //
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4ff0d59..024b63e 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -22,6 +22,7 @@
 import android.annotation.UserIdInt;
 import android.os.Binder;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 
 import com.android.server.pm.Computer;
 import com.android.server.pm.PackageManagerLocal;
@@ -30,7 +31,6 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -143,7 +143,7 @@
         private final int mUserId;
 
         @Nullable
-        private ArrayList<PackageState> mFilteredPackageStates;
+        private Map<String, PackageState> mFilteredPackageStates;
 
         @Nullable
         private final UnfilteredSnapshotImpl mParentSnapshot;
@@ -179,25 +179,36 @@
             return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
         }
 
+        @NonNull
         @Override
-        public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+        public Map<String, PackageState> getPackageStates() {
             checkClosed();
 
             if (mFilteredPackageStates == null) {
                 var packageStates = mSnapshot.getPackageStates();
-                var filteredPackageStates = new ArrayList<PackageState>();
+                var filteredPackageStates = new ArrayMap<String, PackageState>();
                 for (int index = 0, size = packageStates.size(); index < size; index++) {
                     var packageState = packageStates.valueAt(index);
                     if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
-                        filteredPackageStates.add(packageState);
+                        filteredPackageStates.put(packageStates.keyAt(index), packageState);
                     }
                 }
-                mFilteredPackageStates = filteredPackageStates;
+                mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
             }
 
-            for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
-                var packageState = mFilteredPackageStates.get(index);
-                consumer.accept(packageState);
+            return mFilteredPackageStates;
+        }
+
+        @Override
+        public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+            checkClosed();
+
+            var packageStates = mSnapshot.getPackageStates();
+            for (int index = 0, size = packageStates.size(); index < size; index++) {
+                var packageState = packageStates.valueAt(index);
+                if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
+                    consumer.accept(packageState);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index c76b129..82b5fa2 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -234,10 +234,12 @@
                 || !pkg.getLibraryNames().isEmpty();
     }
 
-    public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
+    public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
             @NonNull PackageStateInternal pkgSetting) {
         boolean isAllowedToUseHiddenApis;
-        if (pkg.isSignedWithPlatformKey()) {
+        if (pkg == null) {
+            isAllowedToUseHiddenApis = false;
+        } else if (pkg.isSignedWithPlatformKey()) {
             isAllowedToUseHiddenApis = true;
         } else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
             isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e8d0640..67b7647 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -112,6 +112,19 @@
     int getAppId();
 
     /**
+     * Retrieves effective hidden API policy for this app. The state can be dependent on
+     * {@link #getAndroidPackage()} availability and whether the app is a system app.
+     *
+     * Note that during process start, this policy may be mutated by device specific process
+     * configuration, so this value isn't truly final.
+     *
+     * @return The (mostly) final {@link ApplicationInfo.HiddenApiEnforcementPolicy} that should be
+     * applied to this package.
+     */
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    int getHiddenApiEnforcementPolicy();
+
+    /**
      * @see PackageInfo#packageName
      * @see AndroidPackage#getPackageName()
      */
@@ -139,6 +152,18 @@
     String getSeInfo();
 
     /**
+     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+     */
+    @NonNull
+    PackageUserState getStateForUser(@NonNull UserHandle user);
+
+    /**
+     * @see R.styleable#AndroidManifestUsesLibrary
+     */
+    @NonNull
+    List<SharedLibrary> getUsesLibraries();
+
+    /**
      * @see AndroidPackage#isPrivileged()
      */
     boolean isPrivileged();
@@ -154,18 +179,6 @@
      */
     boolean isUpdatedSystemApp();
 
-    /**
-     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
-     */
-    @NonNull
-    PackageUserState getStateForUser(@NonNull UserHandle user);
-
-    /**
-     * @see R.styleable#AndroidManifestUsesLibrary
-     */
-    @NonNull
-    List<SharedLibrary> getUsesLibraries();
-
     // Methods below this comment are not yet exposed as API
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index e552a34..43d019a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningInfo;
@@ -118,6 +119,8 @@
     private final int mCategoryOverride;
     @Nullable
     private final String mCpuAbiOverride;
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    private final int mHiddenApiEnforcementPolicy;
     private final long mLastModifiedTime;
     private final long mLastUpdateTime;
     private final long mLongVersionCode;
@@ -170,6 +173,7 @@
         mAppId = pkgState.getAppId();
         mCategoryOverride = pkgState.getCategoryOverride();
         mCpuAbiOverride = pkgState.getCpuAbiOverride();
+        mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
         mLastModifiedTime = pkgState.getLastModifiedTime();
         mLastUpdateTime = pkgState.getLastUpdateTime();
         mLongVersionCode = pkgState.getVersionCode();
@@ -545,7 +549,7 @@
         }
 
         @DataClass.Generated(
-                time = 1665778832625L,
+                time = 1666719622708L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
                 inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTime\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -609,6 +613,11 @@
     }
 
     @DataClass.Generated.Member
+    public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+        return mHiddenApiEnforcementPolicy;
+    }
+
+    @DataClass.Generated.Member
     public long getLastModifiedTime() {
         return mLastModifiedTime;
     }
@@ -705,10 +714,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665778832668L,
+            time = 1666719622749L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index d2e0502..91bb677 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -378,13 +378,14 @@
                 try {
                     conditionSatisfied = mStateConditions.get(state).getAsBoolean();
                 } catch (IllegalStateException e) {
-                    // Failed to compute the current state based on current available data. Return
+                    // Failed to compute the current state based on current available data. Continue
                     // with the expectation that notifyDeviceStateChangedIfNeeded() will be called
-                    // when a callback with the missing data is triggered.
+                    // when a callback with the missing data is triggered. May trigger another state
+                    // change if another state is satisfied currently.
                     if (DEBUG) {
                         Slog.d(TAG, "Unable to check current state", e);
                     }
-                    return;
+                    continue;
                 }
 
                 if (conditionSatisfied) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9d58a73..2f0f88a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2920,6 +2920,27 @@
                     return key_consumed;
                 }
                 break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+                    if (statusbar != null) {
+                        statusbar.goToFullscreenFromSplit();
+                    }
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(true /* leftOrTop */);
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(false /* leftOrTop */);
+                    return key_consumed;
+                }
+                break;
             case KeyEvent.KEYCODE_SLASH:
                 if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
@@ -3574,6 +3595,13 @@
         }
     }
 
+    private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.enterStageSplitFromRunningApp(leftOrTop);
+        }
+    }
+
     void launchHomeFromHotKey(int displayId) {
         launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
     }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0c5e451..af4fa85 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -12229,6 +12229,7 @@
     @GuardedBy("this")
     private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
         final int infoSize = deltaInfo.getSpecificInfoLength();
+
         if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
                 == AccessNetworkConstants.AccessNetworkType.UNKNOWN
                 && deltaInfo.getSpecificInfoFrequencyRange(0)
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 806ed64..2c7aea9 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -15,45 +15,79 @@
  */
 package com.android.server.power.stats;
 
+import android.annotation.Nullable;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
 import android.util.Log;
+import android.util.LongArrayQueue;
 import android.util.SparseArray;
 
 import com.android.internal.os.PowerProfile;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
 
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
 
+    private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
             CellSignalStrength.getNumSignalStrengthLevels();
 
     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+    private static final int IGNORE = -1;
 
-    private final UsageBasedPowerEstimator mActivePowerEstimator;
+    private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
-            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
-    private final UsageBasedPowerEstimator mScanPowerEstimator;
+            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
+    private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
+
+    @Nullable
+    private final UsageBasedPowerEstimator mSleepPowerEstimator;
+    @Nullable
+    private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+    private final PowerProfile mPowerProfile;
 
     private static class PowerAndDuration {
-        public long durationMs;
+        public long remainingDurationMs;
         public double remainingPowerMah;
         public long totalAppDurationMs;
         public double totalAppPowerMah;
-        public long signalDurationMs;
-        public long noCoverageDurationMs;
     }
 
     public MobileRadioPowerCalculator(PowerProfile profile) {
-        // Power consumption when radio is active
+        mPowerProfile = profile;
+
+        final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+                Double.NaN);
+        if (Double.isNaN(sleepDrainRateMa)) {
+            mSleepPowerEstimator = null;
+        } else {
+            mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+        }
+
+        final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+                Double.NaN);
+        if (Double.isNaN(idleDrainRateMa)) {
+            mIdlePowerEstimator = null;
+        } else {
+            mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+        }
+
+        // Instantiate legacy power estimators
         double powerRadioActiveMa =
-                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
-        if (powerRadioActiveMa == -1) {
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+        if (Double.isNaN(powerRadioActiveMa)) {
             double sum = 0;
             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
@@ -61,11 +95,10 @@
             }
             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
         }
-
         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
 
-        // Power consumption when radio is on, but idle
-        if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+        if (!Double.isNaN(
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
@@ -95,6 +128,23 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
+        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+        final int powerModel = getPowerModel(totalConsumptionUC, query);
+
+        final double totalActivePowerMah;
+        final ArrayList<UidBatteryConsumer.Builder> apps;
+        final LongArrayQueue appDurationsMs;
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Measured energy is available, don't bother calculating power.
+            totalActivePowerMah = Double.NaN;
+            apps = null;
+            appDurationsMs = null;
+        } else {
+            totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
+            apps = new ArrayList();
+            appDurationsMs = new LongArrayQueue();
+        }
+
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
@@ -110,132 +160,352 @@
                 }
             }
 
-            calculateApp(app, uid, total, query, keys);
+            // Sum and populate each app's active radio duration.
+            final long radioActiveDurationMs = calculateDuration(uid,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            if (!app.isVirtualUid()) {
+                total.totalAppDurationMs += radioActiveDurationMs;
+            }
+            app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                    radioActiveDurationMs);
+
+            if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+                // Measured energy is available, populate the consumed power now.
+                final long appConsumptionUC = uid.getMobileRadioMeasuredBatteryConsumptionUC();
+                if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
+                    final double appConsumptionMah = uCtoMah(appConsumptionUC);
+                    if (!app.isVirtualUid()) {
+                        total.totalAppPowerMah += appConsumptionMah;
+                    }
+                    app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                            appConsumptionMah, powerModel);
+
+                    if (query.isProcessStateDataNeeded() && keys != null) {
+                        for (BatteryConsumer.Key key : keys) {
+                            final int processState = key.processState;
+                            if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                                // Already populated with the total across all process states
+                                continue;
+                            }
+                            final long consumptionInStateUc =
+                                    uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+                            final double powerInStateMah = uCtoMah(consumptionInStateUc);
+                            app.setConsumedPower(key, powerInStateMah, powerModel);
+                        }
+                    }
+                }
+            } else {
+                // Cache the app and its active duration for later calculations.
+                apps.add(app);
+                appDurationsMs.addLast(radioActiveDurationMs);
+            }
         }
 
-        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(totalConsumptionUC, query);
-        calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, totalConsumptionUC);
+        long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        if (totalActiveDurationMs < total.totalAppDurationMs) {
+            totalActiveDurationMs = total.totalAppDurationMs;
+        }
+
+        if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Need to smear the calculated total active power across the apps based on app
+            // active durations.
+            final int appSize = apps.size();
+            for (int i = 0; i < appSize; i++) {
+                final UidBatteryConsumer.Builder app = apps.get(i);
+                final long activeDurationMs = appDurationsMs.get(i);
+
+                // Proportionally attribute radio power consumption based on active duration.
+                final double appConsumptionMah;
+                if (totalActiveDurationMs == 0.0) {
+                    appConsumptionMah = 0.0;
+                } else {
+                    appConsumptionMah =
+                            (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
+                }
+
+                if (!app.isVirtualUid()) {
+                    total.totalAppPowerMah += appConsumptionMah;
+                }
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                        appConsumptionMah, powerModel);
+
+                if (query.isProcessStateDataNeeded() && keys != null) {
+                    final BatteryStats.Uid uid = app.getBatteryStatsUid();
+                    for (BatteryConsumer.Key key : keys) {
+                        final int processState = key.processState;
+                        if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                            // Already populated with the total across all process states
+                            continue;
+                        }
+
+                        final long durationInStateMs =
+                                uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+                        // Proportionally attribute per process state radio power consumption
+                        // based on time state duration.
+                        final double powerInStateMah;
+                        if (activeDurationMs == 0.0) {
+                            powerInStateMah = 0.0;
+                        } else {
+                            powerInStateMah =
+                                    (appConsumptionMah * durationInStateMs) / activeDurationMs;
+                        }
+                        app.setConsumedPower(key, powerInStateMah, powerModel);
+                    }
+                }
+            }
+        }
+
+        total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
+
+        // Calculate remaining power consumption.
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
+            if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
+        } else {
+            // Smear unattributed active time and add it to the remaining power consumption.
+            total.remainingPowerMah +=
+                    (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+
+            // Calculate the inactive modem power consumption.
+            final BatteryStats.ControllerActivityCounter modemActivity =
+                    batteryStats.getModemControllerActivity();
+            if (modemActivity != null && (mSleepPowerEstimator != null
+                    || mIdlePowerEstimator != null)) {
+                final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
+                final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
+            } else {
+                // Modem activity counters unavailable. Use legacy calculations for inactive usage.
+                final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+                        BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+                for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+                    long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+                            BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                    total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+                }
+            }
+
+        }
 
         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.remainingDurationMs + total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
 
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.totalAppPowerMah, powerModel);
         }
     }
 
-    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            PowerAndDuration total,
-            BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
-        final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
-        final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC, query);
-        final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
-
-        if (!app.isVirtualUid()) {
-            total.totalAppDurationMs += radioActiveDurationMs;
-            total.totalAppPowerMah += powerMah;
-        }
-
-        app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        radioActiveDurationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
-                        powerModel);
-
-        if (query.isProcessStateDataNeeded() && keys != null) {
-            for (BatteryConsumer.Key key: keys) {
-                final int processState = key.processState;
-                if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
-                    // Already populated with the total across all process states
-                    continue;
-                }
-
-                final long durationInStateMs =
-                        u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
-                final long consumptionInStateUc =
-                        u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
-                final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
-                        consumptionInStateUc);
-                app.setConsumedPower(key, powerInStateMah, powerModel);
-            }
-        }
-    }
-
     private long calculateDuration(BatteryStats.Uid u, int statsType) {
         return u.getMobileRadioActiveTime(statsType) / 1000;
     }
 
-    private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
-            long radioActiveDurationMs, long measuredChargeUC) {
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            return uCtoMah(measuredChargeUC);
+    private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
+        final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
+        final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+        double consumptionMah = 0.0;
+
+        if (DEBUG) {
+            Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+                    + elapsedRealtimeMs + " ms");
         }
 
-        if (radioActiveDurationMs > 0) {
-            return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+        boolean hasConstants = false;
+
+        for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+            final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+                    ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+            for (int freq = 0; freq < freqCount; freq++) {
+                for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
+                    final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
+                            elapsedRealtimeMs);
+                    if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                        continue;
+                    }
+                    final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
+                            txDurationMs);
+                    if (Double.isNaN(txConsumptionMah)) {
+                        continue;
+                    }
+                    hasConstants = true;
+                    consumptionMah += txConsumptionMah;
+                }
+
+                final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
+                        elapsedRealtimeMs);
+                if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                    continue;
+                }
+                final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
+                if (Double.isNaN(rxConsumptionMah)) {
+                    continue;
+                }
+                hasConstants = true;
+                consumptionMah += rxConsumptionMah;
+            }
         }
-        return 0;
+
+        if (!hasConstants) {
+            final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Failed to calculate radio power consumption. Reattempted with legacy "
+                                + "method. Radio active duration : "
+                                + radioActiveDurationMs + " ms");
+            }
+            if (radioActiveDurationMs > 0) {
+                consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+            } else {
+                consumptionMah = 0.0;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+                    + " mAH.");
+        }
+
+        return consumptionMah;
     }
 
-    private void calculateRemaining(PowerAndDuration total,
-            @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
-            long rawRealtimeUs, long totalConsumptionUC) {
-        long signalTimeMs = 0;
-        double powerMah = 0;
+    private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
+            @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+            int txLevel) {
+        long key = PowerProfile.SUBSYSTEM_MODEM;
 
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            powerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
-            if (powerMah < 0) powerMah = 0;
+        // Attach Modem drain type to the key if specified.
+        if (drainType != IGNORE) {
+            key |= drainType;
         }
 
-        for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
-            long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
-                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
-            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-                final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
-                if (DEBUG && p != 0) {
-                    Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
-                            + BatteryStats.formatCharge(p));
-                }
-                powerMah += p;
-            }
-            signalTimeMs += strengthTimeMs;
-            if (i == 0) {
-                total.noCoverageDurationMs = strengthTimeMs;
-            }
+        // Attach RadioAccessTechnology to the key if specified.
+        switch (rat) {
+            case IGNORE:
+                // do nothing
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
+                break;
+            default:
+                Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
         }
 
-        final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
-
-        if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-            final double p = calcScanTimePowerMah(scanningTimeMs);
-            if (DEBUG && p != 0) {
-                Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
-                        + " power=" + BatteryStats.formatCharge(p));
-            }
-            powerMah += p;
-
-            if (remainingActiveTimeMs > 0) {
-                powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
-            }
+        // Attach NR Frequency Range to the key if specified.
+        switch (freqRange) {
+            case IGNORE:
+                // do nothing
+                break;
+            case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+                break;
+            case ServiceState.FREQUENCY_RANGE_LOW:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MID:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
+                break;
+            case ServiceState.FREQUENCY_RANGE_HIGH:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MMWAVE:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+                break;
+            default:
+                Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
         }
-        total.durationMs = radioActiveTimeMs;
-        total.remainingPowerMah = powerMah;
-        total.signalDurationMs = signalTimeMs;
+
+        // Attach transmission level to the key if specified.
+        switch (txLevel) {
+            case IGNORE:
+                // do nothing
+                break;
+            case 0:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
+                break;
+            case 1:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
+                break;
+            case 2:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
+                break;
+            case 3:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
+                break;
+            case 4:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
+                break;
+            default:
+                Log.w(TAG, "Unexpected transmission level : " + txLevel);
+        }
+        return key;
+    }
+
+    /**
+     * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
+        final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
+                freqRange, IGNORE);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) rxKey));
+        }
+        return consumptionMah;
+    }
+
+    /**
+     * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
+        final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
+                freqRange, txLevel);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) txKey));
+        }
+        return consumptionMah;
     }
 
     /**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 434cd78..392fda9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -205,4 +205,16 @@
      * @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
      */
     void showRearDisplayDialog(int currentBaseState);
+
+    /**
+     * Called when requested to go to fullscreen from the active split app.
+     */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 006d888..8d71d9c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -726,6 +726,24 @@
                 } catch (RemoteException ex) { }
             }
         }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            if (mBar != null) {
+                try {
+                    mBar.goToFullscreenFromSplit();
+                } catch (RemoteException ex) { }
+            }
+        }
+
+        @Override
+        public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+            if (mBar != null) {
+                try {
+                    mBar.enterStageSplitFromRunningApp(leftOrTop);
+                } catch (RemoteException ex) { }
+            }
+        }
     };
 
     private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e1ab291..13111fb 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -740,7 +740,7 @@
         // visible such as after the top task is finished.
         for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
             final TransitionInfo prevInfo = mTransitionInfoList.get(i);
-            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
+            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.isVisibleRequested()) {
                 scheduleCheckActivityToBeDrawn(prevInfo.mLastLaunchedActivity, 0 /* delay */);
             }
         }
@@ -867,7 +867,7 @@
             return;
         }
         if (DEBUG_METRICS) {
-            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.isVisibleRequested()
                     + " state=" + r.getState() + " finishing=" + r.finishing);
         }
         if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
@@ -876,7 +876,7 @@
             // the tracking of launch event.
             return;
         }
-        if (!r.mVisibleRequested || r.finishing) {
+        if (!r.isVisibleRequested() || r.finishing) {
             // Check if the tracker can be cancelled because the last launched activity may be
             // no longer visible.
             scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
@@ -909,7 +909,7 @@
             // activities in this task may be finished, invisible or drawn, so the transition event
             // should be cancelled.
             if (t != null && t.forAllActivities(
-                    a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) {
+                    a -> a.isVisibleRequested() && !a.isReportedDrawn() && !a.finishing)) {
                 return;
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e099aac..aec06f0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -800,7 +800,7 @@
     // it will sometimes be true a little earlier: when the activity record has
     // been shown, but is still waiting for its app transition to execute
     // before making its windows shown.
-    boolean mVisibleRequested;
+    private boolean mVisibleRequested;
 
     // Last visibility state we reported to the app token.
     boolean reportedVisible;
@@ -3622,7 +3622,7 @@
         // implied that the current finishing activity should be added into stopping list rather
         // than destroy immediately.
         final boolean isNextNotYetVisible = next != null
-                && (!next.nowVisible || !next.mVisibleRequested);
+                && (!next.nowVisible || !next.isVisibleRequested());
 
         // Clear last paused activity to ensure top activity can be resumed during sleeping.
         if (isNextNotYetVisible && mDisplayContent.isSleeping()
@@ -4440,7 +4440,7 @@
     void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
-            return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+            return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
 
@@ -5105,7 +5105,8 @@
      * This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
      * outside of this class should use {@link #setVisibility}.
      */
-    private void setVisibleRequested(boolean visible) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    void setVisibleRequested(boolean visible) {
         if (visible == mVisibleRequested) {
             return;
         }
@@ -5433,25 +5434,20 @@
      */
     private void postApplyAnimation(boolean visible, boolean fromTransition) {
         final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
-        final boolean delayed = isAnimating(PARENTS | CHILDREN,
+        final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
                         | ANIMATION_TYPE_RECENTS);
-        if (!delayed) {
+        if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
             // which we might have done because we were in closing/opening apps list.
-            if (!usingShellTransitions) {
-                onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
-                if (visible) {
-                    // The token was made immediately visible, there will be no entrance animation.
-                    // We need to inform the client the enter animation was finished.
-                    mEnteringAnimation = true;
-                    mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
-                            token);
-                }
-            } else {
-                // update wallpaper target
-                setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
+            onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
+            if (visible) {
+                // The token was made immediately visible, there will be no entrance animation.
+                // We need to inform the client the enter animation was finished.
+                mEnteringAnimation = true;
+                mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+                        token);
             }
         }
 
@@ -5460,8 +5456,8 @@
         // updated.
         // If we're becoming invisible, update the client visibility if we are not running an
         // animation. Otherwise, we'll update client visibility in onAnimationFinished.
-        if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
-                || usingShellTransitions) {
+        if (visible || usingShellTransitions
+                || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
             setClientVisible(visible);
         }
 
@@ -6549,7 +6545,7 @@
         if (associatedTask == null) {
             removeStartingWindow();
         } else if (associatedTask.getActivity(
-                r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+                r -> r.isVisibleRequested() && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
@@ -7442,8 +7438,10 @@
             } else if (!show && mLastSurfaceShowing) {
                 getSyncTransaction().hide(mSurfaceControl);
             }
-            if (show) {
-                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction());
+            // Input sink surface is not a part of animation, so just apply in a steady state
+            // (non-sync) with pending transaction.
+            if (show && mSyncState == SYNC_STATE_NONE) {
+                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
             }
         }
         if (mThumbnail != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 30c7b23..0859d40 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -92,7 +92,7 @@
 
     public boolean isActivityVisible() {
         synchronized (mService.mGlobalLock) {
-            return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING);
+            return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 56aae2d6..05ec3b5 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -561,7 +561,7 @@
         if (rootTask == null) return false;
         final RemoteTransition remote = options.getRemoteTransition();
         final ActivityRecord r = rootTask.topRunningActivity();
-        if (r == null || r.mVisibleRequested || !r.attachedToProcess() || remote == null
+        if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
                 || !r.mActivityComponent.equals(intent.getComponent())
                 // Recents keeps invisible while device is locked.
                 || r.mDisplayContent.isKeyguardLocked()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5c83c4f..1e06375 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2637,7 +2637,7 @@
                     // If the activity is visible in multi-windowing mode, it may already be on
                     // the top (visible to user but not the global top), then the result code
                     // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
-                    final boolean wasTopOfVisibleRootTask = intentActivity.mVisibleRequested
+                    final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
                             && intentActivity.inMultiWindowMode()
                             && intentActivity == mTargetRootTask.topRunningActivity();
                     // We only want to move to the front, if we aren't going to launch on a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3145ab3..8a247cf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.HostingRecord;
 import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
@@ -2619,12 +2620,17 @@
         // ActivityStarter will acquire the lock where the places need, so execute the request
         // outside of the lock.
         try {
+            // We need to temporarily disable the explicit intent filter matching enforcement
+            // because Task does not store the resolved type of the intent data, causing filter
+            // mismatch in certain cases. (b/240373119)
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
             return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
                     callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
                     null, 0, 0, options, userId, task, "startActivityFromRecents",
                     false /* validateIncomingUser */, null /* originatingPendingIntent */,
                     false /* allowBackgroundActivityStart */);
         } finally {
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
                 mService.continueWindowLayout();
             }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 8680f1b..b16602e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -810,7 +810,7 @@
         if (activity == null) {
             return;
         }
-        if (!activity.mVisibleRequested) {
+        if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
         }
         activity.mLaunchTaskBehind = true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5ba70aa..d5802cf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -876,11 +876,11 @@
             final ActivityRecord activity = w.mActivityRecord;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
@@ -1705,7 +1705,7 @@
                         .notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
             }
             // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
-            final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+            final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
             if (handleTopActivityLaunchingInDifferentOrientation(
                     topCandidate, r, true /* checkOpening */)) {
                 // Display orientation should be deferred until the top fixed rotation is finished.
@@ -2101,13 +2101,13 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    boolean canShowTasksInRecents() {
+    boolean canShowTasksInHostDeviceRecents() {
         if (mDwpcHelper == null) {
             return true;
         }
-        return mDwpcHelper.canShowTasksInRecents();
+        return mDwpcHelper.canShowTasksInHostDeviceRecents();
     }
 
     /**
@@ -2712,7 +2712,7 @@
         mWmService.mWindowsChanged = true;
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
-        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.mVisibleRequested
+        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
                 && !mFixedRotationLaunchingApp.isVisible()
                 && !mDisplayRotation.isRotatingSeamlessly()) {
             clearFixedRotationLaunchingApp();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 9ac1762..b419f36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,7 +57,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -789,13 +788,7 @@
             if (!mDisplayContent.isDefaultDisplay) {
                 return;
             }
-            if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
-                    && !mDisplayContent.mTransitionController.isCollecting()) {
-                // Start a transition for waking. This is needed for showWhenLocked activities.
-                mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
-                        0 /* flags */, null /* trigger */, mDisplayContent);
-            }
-            mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+            mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                     mAwake /* waiting */);
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index eaa08fd..185e06e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1537,6 +1537,7 @@
         private int mHalfFoldSavedRotation = -1; // No saved rotation
         private DeviceStateController.FoldState mFoldState =
                 DeviceStateController.FoldState.UNKNOWN;
+        private boolean mInHalfFoldTransition = false;
 
         boolean overrideFrozenRotation() {
             return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1544,6 +1545,7 @@
 
         boolean shouldRevertOverriddenRotation() {
             return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+                    && mInHalfFoldTransition
                     && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
                     && mUserRotationMode
                     == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
@@ -1552,6 +1554,7 @@
         int revertOverriddenRotation() {
             int savedRotation = mHalfFoldSavedRotation;
             mHalfFoldSavedRotation = -1;
+            mInHalfFoldTransition = false;
             return savedRotation;
         }
 
@@ -1577,16 +1580,11 @@
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             } else {
-                // Revert the rotation to our saved value if we transition from HALF_FOLDED.
-                if (mHalfFoldSavedRotation != -1) {
-                    mRotation = mHalfFoldSavedRotation;
-                }
-                // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
-                // so we will override USER_ROTATION_LOCKED and allow a rotation).
+                mInHalfFoldTransition = true;
+                mFoldState = newState;
+                // Tell the device to update its orientation.
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
-                // Once we are rotated, set mFoldstate, effectively removing the lock override.
-                mFoldState = newState;
             }
         }
     }
@@ -1683,6 +1681,7 @@
 
     private static class RotationHistory {
         private static final int MAX_SIZE = 8;
+        private static final int NO_FOLD_CONTROLLER = -2;
         private static class Record {
             final @Surface.Rotation int mFromRotation;
             final @Surface.Rotation int mToRotation;
@@ -1694,6 +1693,9 @@
             final String mLastOrientationSource;
             final @ActivityInfo.ScreenOrientation int mSourceOrientation;
             final long mTimestamp = System.currentTimeMillis();
+            final int mHalfFoldSavedRotation;
+            final boolean mInHalfFoldTransition;
+            final DeviceStateController.FoldState mFoldState;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
                 mFromRotation = fromRotation;
@@ -1719,6 +1721,15 @@
                     mLastOrientationSource = null;
                     mSourceOrientation = SCREEN_ORIENTATION_UNSET;
                 }
+                if (dr.mFoldController != null) {
+                    mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
+                    mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
+                    mFoldState = dr.mFoldController.mFoldState;
+                } else {
+                    mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
+                    mInHalfFoldTransition = false;
+                    mFoldState = DeviceStateController.FoldState.UNKNOWN;
+                }
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -1735,6 +1746,12 @@
                 if (mNonDefaultRequestingTaskDisplayArea != null) {
                     pw.println(prefix + "  requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
                 }
+                if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
+                    pw.println(prefix + " halfFoldSavedRotation="
+                            + mHalfFoldSavedRotation
+                            + " mInHalfFoldTransition=" + mInHalfFoldTransition
+                            + " mFoldState=" + mFoldState);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 6f821b5..69fd00c 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -153,13 +153,13 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    public final boolean canShowTasksInRecents() {
+    public final boolean canShowTasksInHostDeviceRecents() {
         if (mDisplayWindowPolicyController == null) {
             return true;
         }
-        return mDisplayWindowPolicyController.canShowTasksInRecents();
+        return mDisplayWindowPolicyController.canShowTasksInHostDeviceRecents();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 7bb036d..bd83794 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -191,7 +191,7 @@
             if (!r.attachedToProcess()) {
                 makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
                         resumeTopActivity && isTop, r);
-            } else if (r.mVisibleRequested) {
+            } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
                 if (DEBUG_VISIBILITY) {
                     Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
@@ -244,7 +244,7 @@
         // invisible. If the app is already visible, it must have died while it was visible. In this
         // case, we'll show the dead window but will not restart the app. Otherwise we could end up
         // thrashing.
-        if (!isTop && r.mVisibleRequested && !r.isState(INITIALIZING)) {
+        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
             return;
         }
 
@@ -256,7 +256,7 @@
         if (r != starting) {
             r.startFreezingScreenLocked(configChanges);
         }
-        if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
+        if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
             }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1567fa7..e9badef 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -37,6 +37,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -176,7 +177,7 @@
         final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
                 || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
         if (aodRemoved) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
         if (!keyguardChanged && !aodChanged) {
             setWakeTransitionReady();
@@ -533,24 +534,25 @@
 
     private final Runnable mResetWaitTransition = () -> {
         synchronized (mWindowManager.mGlobalLock) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
     };
 
-    void updateDeferWakeTransition(boolean waiting) {
+    // Defer transition until AOD dismissed.
+    void updateDeferTransitionForAod(boolean waiting) {
         if (waiting == mWaitingForWakeTransition) {
             return;
         }
-        if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+        if (!mService.getTransitionController().isCollecting()) {
             return;
         }
-        // if aod is showing, defer the wake transition until aod state changed.
+        // if AOD is showing, defer the wake transition until AOD state changed.
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
             mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
-            // dismiss the deferring if the aod state change or cancel awake.
+            // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
             mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -648,6 +650,12 @@
             mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
                     && !mOccluded && !mKeyguardGoingAway
                     && mDismissingKeyguardActivity != null;
+            if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+                    && display.mWallpaperController.getWallpaperTarget() == null) {
+                // The occluding activity may be translucent or not fill screen. Then let wallpaper
+                // to check whether it should set itself as target to avoid blank background.
+                display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
 
             if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
                     && mTopTurnScreenOnActivity != null
@@ -657,10 +665,18 @@
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
 
+            boolean hasChange = false;
             if (lastOccluded != mOccluded) {
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+                hasChange = true;
             } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
                 controller.handleKeyguardGoingAwayChanged(display);
+                hasChange = true;
+            }
+            // Collect the participates for shell transition, so that transition won't happen too
+            // early since the transition was set ready.
+            if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+                display.mTransitionController.collect(top);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bb4c482..bcea6f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -673,6 +673,12 @@
                 + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
         pw.println(prefix + "  letterboxVerticalPositionMultiplier="
                 + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+        pw.println(prefix + "  letterboxPositionForHorizontalReachability="
+                + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+        pw.println(prefix + "  letterboxPositionForVerticalReachability="
+                + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
         pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
                 + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
         pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b..c827062 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1393,7 +1393,7 @@
         // Ignore the task if it is started on a display which is not allow to show its tasks on
         // Recents.
         if (task.getDisplayContent() != null
-                && !task.getDisplayContent().canShowTasksInRecents()) {
+                && !task.getDisplayContent().canShowTasksInHostDeviceRecents()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ffe3374..be90588 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -112,7 +112,7 @@
                 mTargetActivityType);
         ActivityRecord targetActivity = getTargetActivity(targetRootTask);
         if (targetActivity != null) {
-            if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
+            if (targetActivity.isVisibleRequested() || targetActivity.isTopRunningActivity()) {
                 // The activity is ready.
                 return;
             }
@@ -195,7 +195,7 @@
 
         // Send launch hint if we are actually launching the target. If it's already visible
         // (shouldn't happen in general) we don't need to send it.
-        if (targetActivity == null || !targetActivity.mVisibleRequested) {
+        if (targetActivity == null || !targetActivity.isVisibleRequested()) {
             mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
                     true /* forceSend */, targetActivity);
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 64cca87..1ee4d6b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2619,7 +2619,7 @@
         final ArrayList<Task> addedTasks = new ArrayList<>();
         forAllActivities((r) -> {
             final Task task = r.getTask();
-            if (r.mVisibleRequested && r.mStartingData == null && !addedTasks.contains(task)) {
+            if (r.isVisibleRequested() && r.mStartingData == null && !addedTasks.contains(task)) {
                 r.showStartingWindow(true /*taskSwitch*/);
                 addedTasks.add(task);
             }
@@ -2644,7 +2644,7 @@
         forAllLeafTasks(task -> {
             final int oldRank = task.mLayerRank;
             final ActivityRecord r = task.topRunningActivityLocked();
-            if (r != null && r.mVisibleRequested) {
+            if (r != null && r.isVisibleRequested()) {
                 task.mLayerRank = ++mTmpTaskLayerRank;
             } else {
                 task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a93767e..a74a4b0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2167,7 +2167,7 @@
     }
 
     private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
-        if (!isLeafTask() || !canStartChangeTransition()) {
+        if (!(isLeafTask() || mCreatedByOrganizer) || !canStartChangeTransition()) {
             return false;
         }
         final int newWinMode = getWindowingMode();
@@ -2455,7 +2455,7 @@
 
         final String myReason = reason + " adjustFocusToNextFocusableTask";
         final ActivityRecord top = focusableTask.topRunningActivity();
-        if (focusableTask.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
+        if (focusableTask.isActivityTypeHome() && (top == null || !top.isVisibleRequested())) {
             // If we will be focusing on the root home task next and its current top activity isn't
             // visible, then use the move the root home task to top to make the activity visible.
             focusableTask.getDisplayArea().moveHomeActivityToTop(myReason);
@@ -2768,7 +2768,7 @@
      */
     private static void getMaxVisibleBounds(ActivityRecord token, Rect out, boolean[] foundTop) {
         // skip hidden (or about to hide) apps
-        if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) {
+        if (token.mIsExiting || !token.isClientVisible() || !token.isVisibleRequested()) {
             return;
         }
         final WindowState win = token.findMainWindow();
@@ -3077,7 +3077,7 @@
      * this activity.
      */
     ActivityRecord getTopVisibleActivity() {
-        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.mVisibleRequested);
+        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.isVisibleRequested());
     }
 
     /**
@@ -5726,7 +5726,7 @@
         forAllActivities(r -> {
             if (!r.info.packageName.equals(packageName)) return;
             r.forceNewConfig = true;
-            if (starting != null && r == starting && r.mVisibleRequested) {
+            if (starting != null && r == starting && r.isVisibleRequested()) {
                 r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
             }
         });
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d69d949..66b868a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -346,7 +346,7 @@
         }
 
         void process(ActivityRecord start, boolean preserveWindow) {
-            if (start == null || !start.mVisibleRequested) {
+            if (start == null || !start.isVisibleRequested()) {
                 return;
             }
             reset(preserveWindow);
@@ -1356,7 +1356,7 @@
         if (next.attachedToProcess()) {
             if (DEBUG_SWITCH) {
                 Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
-                        + " visibleRequested=" + next.mVisibleRequested);
+                        + " visibleRequested=" + next.isVisibleRequested());
             }
 
             // If the previous activity is translucent, force a visibility update of
@@ -1370,7 +1370,7 @@
                     || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
 
             // This activity is now becoming visible.
-            if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+            if (!next.isVisibleRequested() || next.stopped || lastActivityTranslucent) {
                 next.app.addToPendingTop();
                 next.setVisibility(true);
             }
@@ -1421,7 +1421,7 @@
                     // Do over!
                     mTaskSupervisor.scheduleResumeTopActivities();
                 }
-                if (!next.mVisibleRequested || next.stopped) {
+                if (!next.isVisibleRequested() || next.stopped) {
                     next.setVisibility(true);
                 }
                 next.completeResumeLocked();
@@ -1732,7 +1732,7 @@
             } else if (prev.attachedToProcess()) {
                 ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                 + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
-                        prev.mVisibleRequested);
+                        prev.isVisibleRequested());
                 if (prev.deferRelaunchUntilPaused) {
                     // Complete the deferred relaunch that was waiting for pause to complete.
                     ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
@@ -1742,7 +1742,7 @@
                     // We can't clobber it, because the stop confirmation will not be handled.
                     // We don't need to schedule another stop, we only need to let it happen.
                     prev.setState(STOPPING, "completePausedLocked");
-                } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+                } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                     // Clear out any deferred client hide we might currently have.
                     prev.setDeferHidingClient(false);
                     // If we were visible then resumeTopActivities will release resources before
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2b11d54..2737456 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -639,8 +639,8 @@
                 if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
                     t.setCrop(targetLeash, null /* crop */);
                 } else {
-                    // Crop to the requested bounds.
-                    final Rect clipRect = target.getRequestedOverrideBounds();
+                    // Crop to the resolved override bounds.
+                    final Rect clipRect = target.getResolvedOverrideBounds();
                     t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
                 }
                 t.setCornerRadius(targetLeash, 0);
@@ -992,7 +992,7 @@
         // show here in the same way that we manually hide in finishTransaction.
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar == null || !ar.mVisibleRequested) continue;
+            if (ar == null || !ar.isVisibleRequested()) continue;
             transaction.show(ar.getSurfaceControl());
 
             // Also manually show any non-reported parents. This is necessary in a few cases
@@ -1246,7 +1246,7 @@
         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
-            if (r == null || !r.mVisibleRequested) continue;
+            if (r == null || !r.isVisibleRequested()) continue;
             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
             // ready due to starting-window.
@@ -1617,8 +1617,11 @@
 
         WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
 
-        // make leash based on highest (z-order) direct child of ancestor with a participant.
-        WindowContainer leashReference = sortedTargets.get(0);
+        // Make leash based on highest (z-order) direct child of ancestor with a participant.
+        // TODO(b/261418859): Handle the case when the target contains window containers which
+        // belong to a different display. As a workaround we use topApp, from which wallpaper
+        // window container is removed, instead of sortedTargets here.
+        WindowContainer leashReference = topApp;
         while (leashReference.getParent() != ancestor) {
             leashReference = leashReference.getParent();
         }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 99527b1..cf541fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -38,6 +38,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
@@ -116,6 +117,8 @@
      */
     boolean mBuildingFinishLayers = false;
 
+    private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
+
     TransitionController(ActivityTaskManagerService atm,
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
@@ -619,8 +622,16 @@
     private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
         if (mTransitionPlayerProc == null) return;
         if (isPlaying) {
+            mWakeT.setEarlyWakeupStart();
+            mWakeT.apply();
+            // Usually transitions put quite a load onto the system already (with all the things
+            // happening in app), so pause task snapshot persisting to not increase the load.
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
             mTransitionPlayerProc.setRunningRemoteAnimation(true);
         } else if (mPlayingTransitions.isEmpty()) {
+            mWakeT.setEarlyWakeupEnd();
+            mWakeT.apply();
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
             mTransitionPlayerProc.setRunningRemoteAnimation(false);
             mRemotePlayer.clear();
             return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3b30dd1..5de143d9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -725,9 +725,9 @@
         }
 
         final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null
-                && !wallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !wallpaperTarget.mActivityRecord.isVisibleRequested();
         final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
-                && !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !prevWallpaperTarget.mActivityRecord.isVisibleRequested();
 
         ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
                 + "old: %s hidden=%b new: %s hidden=%b",
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb..32cfb70 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -136,7 +136,7 @@
                 recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
             } else if ((wallpaperTarget.mActivityRecord == null
                     // Ignore invisible activity because it may be moving to background.
-                    || wallpaperTarget.mActivityRecord.mVisibleRequested)
+                    || wallpaperTarget.mActivityRecord.isVisibleRequested())
                     && wallpaperTarget.mToken.hasFixedRotationTransform()) {
                 // If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
                 // rotation
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 2838304..46a30fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1221,6 +1221,12 @@
             pw.println("Default position for vertical reachability: "
                     + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
                     mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+            pw.println("Current position for horizontal reachability:"
+                    + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+            pw.println("Current position for vertical reachability:"
+                    + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
             pw.println("Is education enabled: "
                     + mLetterboxConfiguration.getIsEducationEnabled());
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8f63e93..d2cd8f8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -765,7 +765,7 @@
         // - no longer visible OR
         // - not focusable (in PiP mode for instance)
         if (topDisplay == null
-                || !mPreQTopResumedActivity.mVisibleRequested
+                || !mPreQTopResumedActivity.isVisibleRequested()
                 || !mPreQTopResumedActivity.isFocusable()) {
             canUpdate = true;
         }
@@ -874,7 +874,7 @@
             // to those activities that are part of the package whose app-specific settings changed
             if (packageName.equals(r.packageName)
                     && r.applyAppSpecificConfig(nightMode, localesOverride)
-                    && r.mVisibleRequested) {
+                    && r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
         }
@@ -956,7 +956,7 @@
             }
             // Don't consider any activities that are currently not in a state where they
             // can be destroyed.
-            if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
+            if (r.isVisibleRequested() || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
                     || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
                 if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
                 continue;
@@ -1002,7 +1002,7 @@
                 final int displayId = r.getDisplayId();
                 final Context c = root.getDisplayUiContext(displayId);
 
-                if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
+                if (c != null && r.isVisibleRequested() && !displayContexts.contains(c)) {
                     displayContexts.add(c);
                 }
             }
@@ -1070,7 +1070,7 @@
             if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
                 stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
             }
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 if (r.isState(RESUMED)) {
                     stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
                 }
@@ -1282,7 +1282,7 @@
         }
         for (int i = activities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = activities.get(i);
-            if (r.mVisibleRequested || r.isVisible()) {
+            if (r.isVisibleRequested() || r.isVisible()) {
                 // While an activity launches a new activity, it's possible that the old activity
                 // is already requested to be hidden (mVisibleRequested=false), but this visibility
                 // is not yet committed, so isVisible()=true.
@@ -1503,7 +1503,7 @@
             Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
             overrideConfig.assetsSeq = assetSeq;
             r.onRequestedOverrideConfigurationChanged(overrideConfig);
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0, true);
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 73759d3..c6578ef 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1957,7 +1957,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mActivityRecord == null || mActivityRecord.mVisibleRequested
+        return (mActivityRecord == null || mActivityRecord.isVisibleRequested()
                 || mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
     }
 
@@ -1994,7 +1994,7 @@
         final ActivityRecord atoken = mActivityRecord;
         return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
                 && isVisibleByPolicy() && !isParentWindowHidden()
-                && (atoken == null || atoken.mVisibleRequested)
+                && (atoken == null || atoken.isVisibleRequested())
                 && !mAnimatingExit && !mDestroying;
     }
 
@@ -2101,7 +2101,7 @@
     boolean isDisplayed() {
         final ActivityRecord atoken = mActivityRecord;
         return isDrawn() && isVisibleByPolicy()
-                && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
+                && ((!isParentWindowHidden() && (atoken == null || atoken.isVisibleRequested()))
                         || isAnimating(TRANSITION | PARENTS));
     }
 
@@ -2123,7 +2123,7 @@
                 // a layout since they can request relayout when client visibility is false.
                 // TODO (b/157682066) investigate if we can clean up isVisible
                 || (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
-                || (atoken != null && !atoken.mVisibleRequested)
+                || (atoken != null && !atoken.isVisibleRequested())
                 || isParentWindowGoneForLayout()
                 || (mAnimatingExit && !isAnimatingLw())
                 || mDestroying;
@@ -2170,7 +2170,7 @@
             return;
         }
         if (mActivityRecord != null) {
-            if (!mActivityRecord.mVisibleRequested) return;
+            if (!mActivityRecord.isVisibleRequested()) return;
             if (mActivityRecord.allDrawn) {
                 // The allDrawn of activity is reset when the visibility is changed to visible, so
                 // the content should be ready if allDrawn is set.
@@ -2743,7 +2743,7 @@
                         + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
                 if (mActivityRecord != null) {
                     Slog.i(TAG_WM, "  mActivityRecord.visibleRequested="
-                            + mActivityRecord.mVisibleRequested);
+                            + mActivityRecord.isVisibleRequested());
                 }
             }
         }
@@ -3219,7 +3219,7 @@
         }
 
         return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
-                && mActivityRecord.mVisibleRequested;
+                && mActivityRecord.isVisibleRequested();
     }
 
     /**
@@ -3875,7 +3875,7 @@
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
         if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
-                || mActivityRecord.mVisibleRequested))) {
+                || mActivityRecord.isVisibleRequested()))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
             outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
@@ -4734,7 +4734,7 @@
                     + " during animation: policyVis=" + isVisibleByPolicy()
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.visibleRequested="
-                    + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                    + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                     + " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
                     + " animating=" + isAnimating(TRANSITION | PARENTS)
                     + " tok animating="
@@ -5195,7 +5195,7 @@
                         + " pv=" + isVisibleByPolicy()
                         + " mDrawState=" + mWinAnimator.mDrawState
                         + " ph=" + isParentWindowHidden()
-                        + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                        + " th=" + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                         + " a=" + isAnimating(TRANSITION | PARENTS));
             }
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
new file mode 100644
index 0000000..a565feb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.permission.access.appop
+
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import android.util.SparseIntArray
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.permission.access.AccessCheckingService
+import java.io.PrintWriter
+
+class AppOpsCheckingServiceCompatImpl(
+    private val accessCheckingService: AccessCheckingService
+) : AppOpsCheckingServiceInterface {
+    override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun getUidMode(uid: Int, op: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removePackage(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeUid(uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun areUidModesDefault(uid: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun clearAllModes() {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingPackageModeChanged(
+        changedListener: OnOpModeChangedListener,
+        packageName: String
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeListener(changedListener: OnOpModeChangedListener) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageModeChangedListeners(
+        packageName: String
+    ): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyWatchersOfChange(op: Int, uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChanged(
+        changedListener: OnOpModeChangedListener,
+        op: Int,
+        uid: Int,
+        packageName: String?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChangedForAllPkgsInUid(
+        op: Int,
+        uid: Int,
+        onlyForeground: Boolean,
+        callbackToIgnore: OnOpModeChangedListener?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundUidOps(
+        uid: Int,
+        foregroundOps: SparseBooleanArray?
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundPackageOps(
+        packageName: String,
+        foregroundOps: SparseBooleanArray?,
+        userId: Int
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun dumpListeners(
+        dumpOp: Int,
+        dumpUid: Int,
+        dumpPackage: String?,
+        printWriter: PrintWriter
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index faa2352..5f26d6f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -53,6 +53,11 @@
         snapshot.use {
             val packageStates = it.packageStates
 
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
             // Check contents
             assertThat(packageStates).containsExactly(
                 packageStateAll.packageName, packageStateAll,
@@ -78,9 +83,14 @@
             assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
 
             filteredThree.use {
-                val statesList = mutableListOf<PackageState>()
-                assertThat(it.forAllPackageStates { statesList += it })
-                assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+                // Check for unmodifiable
+                assertFailsWith(UnsupportedOperationException::class) {
+                    it.packageStates.clear()
+                }
+                assertThat(it.packageStates).containsExactly(
+                    packageStateAll.packageName, packageStateAll,
+                    packageStateUser10.packageName, packageStateUser10,
+                )
             }
 
             // Call after child close, parent open fails
@@ -96,7 +106,7 @@
 
         // Call after close fails
         assertClosedFailure { snapshot.packageStates }
-        assertClosedFailure { filteredOne.forAllPackageStates {} }
+        assertClosedFailure { filteredOne.packageStates }
         assertClosedFailure {
             filteredTwo.getPackageState(packageStateAll.packageName)
         }
@@ -116,9 +126,15 @@
                 .isEqualTo(packageStateUser0)
             assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
 
-            val statesList = mutableListOf<PackageState>()
-            assertThat(it.forAllPackageStates { statesList += it })
-            assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
+            assertThat(it.packageStates).containsExactly(
+                packageStateAll.packageName, packageStateAll,
+                packageStateUser0.packageName, packageStateUser0,
+            )
         }
 
         // Call after close fails
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index c87fd26..de09b19 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -592,6 +592,73 @@
     }
 
     /**
+     * Verify that offload broadcasts are not starved because of broadcasts in higher priority
+     * queues.
+     */
+    @Test
+    public void testOffloadStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1;
+        mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        // mix of broadcasts, with more than 2 normal
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+        // expect an offload one
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index be32b79..f105971 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -212,7 +212,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
         displayMode.supportedHdrTypes = new int[0];
         FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
-                0);
+                0, 0);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -393,7 +393,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -449,7 +449,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -504,7 +504,7 @@
                 createFakeDisplayMode(0, 1920, 1080, 60f),
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -538,6 +538,49 @@
     }
 
     @Test
+    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+                /* renderFrameRate */30f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(30f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+        // Change the render frame rate
+        display.dynamicInfo.renderFrameRate = 60f;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(60f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+    }
+
+    @Test
     public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
         Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
@@ -722,7 +765,7 @@
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
         final int activeMode = 0;
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
         display.desiredDisplayModeSpecs.defaultMode = 1;
 
         setUpDisplay(display);
@@ -979,11 +1022,13 @@
             dynamicInfo.activeDisplayModeId = 0;
         }
 
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                float renderFrameRate) {
             address = createDisplayAddress(port);
             info = createFakeDisplayInfo();
             dynamicInfo.supportedDisplayModes = modes;
             dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.renderFrameRate = renderFrameRate;
         }
 
         private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
new file mode 100644
index 0000000..5335f96
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<device name="Android">
+    <!-- Cellular modem related values. These constants are deprecated, but still supported and
+         need to be tested -->
+    <item name="radio.scanning">720</item>
+    <item name="modem.controller.sleep">70</item>
+    <item name="modem.controller.idle">360</item>
+    <item name="modem.controller.rx">1440</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+        <value>720</value>
+        <value>1080</value>
+        <value>1440</value>
+        <value>1800</value>
+        <value>2160</value>
+    </array>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
new file mode 100644
index 0000000..f57bc0f
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
new file mode 100644
index 0000000..4f5e674
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+        <active rat="LTE">
+            <receive>2000</receive>
+            <transmit level="0">800</transmit>
+            <transmit level="1">1200</transmit>
+            <transmit level="2">1600</transmit>
+            <transmit level="3">2000</transmit>
+            <transmit level="4">2400</transmit>
+        </active>
+        <active rat="NR" nrFrequency="DEFAULT">
+            <receive>2222</receive>
+            <transmit level="0">999</transmit>
+            <transmit level="1">1333</transmit>
+            <transmit level="2">1888</transmit>
+            <transmit level="3">2222</transmit>
+            <transmit level="4">2666</transmit>
+        </active>
+        <active rat="NR" nrFrequency="HIGH">
+            <receive>2727</receive>
+            <transmit level="0">1818</transmit>
+            <transmit level="1">2727</transmit>
+            <transmit level="2">3636</transmit>
+            <transmit level="3">4545</transmit>
+            <transmit level="4">5454</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MMWAVE">
+            <receive>3456</receive>
+            <transmit level="0">2345</transmit>
+            <transmit level="1">3456</transmit>
+            <transmit level="2">4567</transmit>
+            <transmit level="3">5678</transmit>
+            <transmit level="4">6789</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
new file mode 100644
index 0000000..ab016fb
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>10</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>20</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>30</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">40</transmit>
+            <transmit level="1">50</transmit>
+            <transmit level="2">60</transmit>
+            <transmit level="3">70</transmit>
+            <transmit level="4">80</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..cbe6d26
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+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.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.NetworkManagementService.Dependencies;
+import com.android.server.net.BaseNetworkObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiFunction;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkManagementServiceTest {
+    private NetworkManagementService mNMService;
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mCm;
+    @Mock private IBatteryStats.Stub mBatteryStatsService;
+    @Mock private INetd.Stub mNetdService;
+
+    private static final int TEST_UID = 111;
+
+    @NonNull
+    @Captor
+    private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
+    private final MockDependencies mDeps = new MockDependencies();
+
+    private final class MockDependencies extends Dependencies {
+        @Override
+        public IBinder getService(String name) {
+            switch (name) {
+                case BatteryStats.SERVICE_NAME:
+                    return mBatteryStatsService;
+                default:
+                    throw new UnsupportedOperationException("Unknown service " + name);
+            }
+        }
+
+        @Override
+        public void registerLocalService(NetworkManagementInternal nmi) {
+        }
+
+        @Override
+        public INetd getNetd() {
+            return mNetdService;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return Process.SYSTEM_UID;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doNothing().when(mNetdService)
+                .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+        doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+                eq(ConnectivityManager.class));
+        doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+        // Start the service and wait until it connects to our socket.
+        mNMService = NetworkManagementService.create(mContext, mDeps);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNMService.shutdown();
+    }
+
+    private static <T> T expectSoon(T mock) {
+        return verify(mock, timeout(200));
+    }
+
+    /**
+     * Tests that network observers work properly.
+     */
+    @Test
+    public void testNetworkObservers() throws Exception {
+        BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+        doReturn(new Binder()).when(observer).asBinder();  // Used by registerObserver.
+        mNMService.registerObserver(observer);
+
+        // Forget everything that happened to the mock so far, so we can explicitly verify
+        // everything that happens and does not happen to it from now on.
+
+        INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+        reset(observer);
+        // Now call unsolListener methods and ensure that the observer methods are
+        // called. After every method we expect a callback soon after; to ensure that
+        // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+        /**
+         * Interface changes.
+         */
+        unsolListener.onInterfaceAdded("rmnet12");
+        expectSoon(observer).interfaceAdded("rmnet12");
+
+        unsolListener.onInterfaceRemoved("eth1");
+        expectSoon(observer).interfaceRemoved("eth1");
+
+        unsolListener.onInterfaceChanged("clat4", true);
+        expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+        unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
+        expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+        /**
+         * Bandwidth control events.
+         */
+        unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
+        expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+        /**
+         * Interface class activity.
+         */
+        unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
+
+        /**
+         * IP address changes.
+         */
+        unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+        /**
+         * DNS information broadcasts.
+         */
+        unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
+        expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+                new String[]{"2001:db8::1"});
+
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+
+        // We don't check for negative lifetimes, only for parse errors.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+                new String[]{"::1"});
+
+        // No syntax checking on the addresses.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+
+        // Make sure nothing else was called.
+        verifyNoMoreInteractions(observer);
+    }
+
+    @Test
+    public void testFirewallEnabled() {
+        mNMService.setFirewallEnabled(true);
+        assertTrue(mNMService.isFirewallEnabled());
+
+        mNMService.setFirewallEnabled(false);
+        assertFalse(mNMService.isFirewallEnabled());
+    }
+
+    @Test
+    public void testNetworkRestrictedDefault() {
+        assertFalse(mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testMeteredNetworkRestrictions() throws RemoteException {
+        // Make sure the mocked netd method returns true.
+        doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
+
+        // Restrict usage of mobile data in background
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
+        assertTrue("Should be true since mobile data usage is restricted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setDataSaverModeEnabled(true);
+        verify(mNetdService).bandwidthEnableDataSaver(true);
+
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
+        assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
+        assertFalse("Should be false since data saver is on and the uid is allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
+
+        // remove uid from allowlist and turn datasaver off again
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+        verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
+        mNMService.setDataSaverModeEnabled(false);
+        verify(mNetdService).bandwidthEnableDataSaver(false);
+        assertFalse("Network should not be restricted when data saver is off",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testFirewallChains() {
+        final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+        // Dozable chain
+        final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+        isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+        // Powersaver chain
+        final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+        isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+        // Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+        isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+        // Restricted mode chain
+        final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
+        isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+        // Low Power Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+        isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+
+        final int[] chains = {
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_RESTRICTED,
+                FIREWALL_CHAIN_LOW_POWER_STANDBY
+        };
+        final int[] states = {
+                INetd.FIREWALL_RULE_ALLOW,
+                INetd.FIREWALL_RULE_DENY,
+                NetworkPolicyManager.FIREWALL_RULE_DEFAULT
+        };
+        BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+            return String.format("Unexpected value for chain: %s and state: %s",
+                    valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
+                    valueToString(INetd.class, "FIREWALL_RULE_", state));
+        };
+        for (int chain : chains) {
+            final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+            mNMService.setFirewallChainEnabled(chain, true);
+            verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
+            for (int state : states) {
+                mNMService.setFirewallUidRule(chain, TEST_UID, state);
+                assertEquals(errorMsg.apply(chain, state),
+                        expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
+            }
+            mNMService.setFirewallChainEnabled(chain, false);
+            verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 6e446f0..d47f063 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -638,6 +638,39 @@
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
+    @Test
+    public void testStopUser_invalidUser() {
+        int userId = -1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserController.stopUser(userId, /* force= */ true,
+                        /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                        /* keyEvictedCallback= */ null));
+    }
+
+    @Test
+    public void testStopUser_systemUser() {
+        int userId = UserHandle.USER_SYSTEM;
+
+        int r = mUserController.stopUser(userId, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+    }
+
+    @Test
+    public void testStopUser_currentUser() {
+        setUpUser(TEST_USER_ID1, /* flags= */ 0);
+        mUserController.startUser(TEST_USER_ID1, /* foreground= */ true);
+
+        int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_IS_CURRENT);
+    }
+
     /**
      * Test conditional delayed locking with mDelayUserDataLocking true.
      */
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 3ca648c..aaa1351 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.companion.virtual.audio;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.media.AudioAttributes.FLAG_SECURE;
 import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -86,8 +85,9 @@
                         /* pipBlockedCallback= */ null,
                         /* activityBlockedCallback= */ null,
                         /* secureWindowCallback= */ null,
-                        /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING,
-                        /* displayCategories= */ new ArrayList<>());
+                        /* displayCategories= */ new ArrayList<>(),
+                        /* recentsPolicy= */
+                        VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);
     }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 303b240..ca8e45a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1253,6 +1253,76 @@
     }
 
     /**
+     * Tests that there is a display change notification if the render frame rate is updated
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRate() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the render frame rate is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
      * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
      */
     @Test
@@ -1472,6 +1542,34 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
     private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
@@ -1541,6 +1639,15 @@
         updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
     }
 
+    private void updateRenderFrameRate(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            float renderFrameRate) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.renderFrameRate = renderFrameRate;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
     private void updateModeId(DisplayManagerService displayManager,
             FakeDisplayDevice displayDevice,
             int modeId) {
@@ -1580,6 +1687,7 @@
                     new Display.Mode(i + 1, width, height, refreshRates[i]);
         }
         displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
         displayDeviceInfo.width = width;
         displayDeviceInfo.height = height;
         final Rect zeroRect = new Rect();
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 3b0a22f..35a677e 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -344,6 +344,40 @@
         assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
     }
 
+    @Test
+    public void testBrightnessInitialisesWithInvalidFloat() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        // Set any value which initialises Display state
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+    }
+
+
     public class TestInjector extends PersistentDataStore.Injector {
         private InputStream mReadStream;
         private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 09cd47a..2cb46da 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -24,6 +24,8 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -52,6 +54,7 @@
     private Context mContextSpy;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
     private FakePowerManagerWrapper mPowerManager;
+    private TestCallback mCallback;
     private ArcTerminationActionFromAvr mAction;
 
     private FakeNativeWrapper mNativeWrapper;
@@ -112,7 +115,9 @@
             }
         };
         mHdmiCecLocalDeviceAudioSystem.init();
-        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+        mCallback = new TestCallback();
+        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem,
+                mCallback);
 
         mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
         hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -121,6 +126,20 @@
         mTestLooper.dispatchAll();
     }
 
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+
+        private int getResult() {
+            assertThat(mCallbackResult.size()).isEqualTo(1);
+            return mCallbackResult.get(0);
+        }
+    }
+
     @Test
     public void testSendMessage_sendFailed() {
         mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
@@ -133,6 +152,7 @@
         assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 
     @Test
@@ -149,6 +169,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
     }
 
     @Test
@@ -167,5 +188,28 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testReportArcTerminated_featureAbort() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                Constants.ADDR_TV,
+                Constants.ADDR_AUDIO_SYSTEM,
+                Constants.MESSAGE_TERMINATE_ARC,
+                Constants.ABORT_REFUSED);
+
+        mNativeWrapper.onCecMessage(arcTerminatedResponse);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 7c6c990..de2c218 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -535,6 +535,25 @@
     }
 
     @Test
+    public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+                new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));
+
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+                .isEqualTo(Constants.HANDLED);
+
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
+                ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
+    }
+
+    @Test
     public void handleRequestArcInit_arcIsNotSupported() throws Exception {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -880,4 +899,13 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
                 systemAudioModeRequest_fromAudioSystem);
     }
+
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index ef2b212..49a0a9a52 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -1036,6 +1036,7 @@
 
     @Test
     public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
         // Initialize the local devices excluding the audio system.
         mHdmiControlServiceSpy.clearCecLocalDevices();
         mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1053,6 +1054,7 @@
 
     @Test
     public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
         // Initialize the local devices excluding the audio system.
         mHdmiControlServiceSpy.clearCecLocalDevices();
         mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1073,6 +1075,10 @@
                 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                 HdmiControlManager.SOUNDBAR_MODE_DISABLED);
         mTestLooper.dispatchAll();
+
+        // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
         assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index dc47b5e..0589b3a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -22,6 +23,7 @@
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.test.RenamingDelegatingContext;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -38,9 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.time.Clock;
 import java.time.ZoneOffset;
-import java.util.Iterator;
 
 /**
  * Test reading and writing correctly from file.
@@ -93,11 +95,147 @@
         mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L);
     }
 
+    private void setUseSplitFiles(boolean useSplitFiles) throws Exception {
+        mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles);
+        waitForPendingIo();
+    }
+
     private void waitForPendingIo() throws Exception {
         assertTrue("Timed out waiting for persistence I/O to complete",
                 mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L));
     }
 
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runRemovingLastJob();
+    }
+
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runRemovingLastJob();
+    }
+
+    private void runRemovingLastJob() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove 1 job
+        mTaskStoreUnderTest.remove(JobStatus1, true);
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+
+        assertJobsEqual(JobStatus2, loaded);
+        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2));
+
+        // Remove 2nd job
+        mTaskStoreUnderTest.remove(JobStatus2, true);
+        waitForPendingIo();
+        jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runClearJobs();
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runClearJobs();
+    }
+
+    private void runClearJobs() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove all jobs
+        mTaskStoreUnderTest.clear();
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    @Test
+    public void testExtractUidFromJobFileName() {
+        File file = new File(mTestContext.getFilesDir(), "randomName");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "jobs.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "1000.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "10000");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+        assertEquals(1, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+        assertEquals(101023, JobStore.extractUidFromJobFileName(file));
+    }
+
     @Test
     public void testStringToIntArrayAndIntArrayToString() {
         final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 };
@@ -144,13 +282,13 @@
 
     @Test
     public void testWritingTwoJobsToDisk_singleFile() throws Exception {
-        mTaskStoreUnderTest.setUseSplitFiles(false);
+        setUseSplitFiles(false);
         runWritingTwoJobsToDisk();
     }
 
     @Test
     public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
-        mTaskStoreUnderTest.setUseSplitFiles(true);
+        setUseSplitFiles(true);
         runWritingTwoJobsToDisk();
     }
 
@@ -172,28 +310,44 @@
         final int uid2 = uid1 + 1;
         final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
         final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
-        mTaskStoreUnderTest.add(taskStatus1);
-        mTaskStoreUnderTest.add(taskStatus2);
+
+        runWritingJobsToDisk(taskStatus1, taskStatus2);
+    }
+
+    private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception {
+        ArraySet<JobStatus> expectedJobs = new ArraySet<>();
+        for (JobStatus jobStatus : jobStatuses) {
+            mTaskStoreUnderTest.add(jobStatus);
+            expectedJobs.add(jobStatus);
+        }
         waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
-        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
-        Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
-        JobStatus loaded1 = it.next();
-        JobStatus loaded2 = it.next();
+        assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size());
+        int count = 0;
+        final int expectedCount = expectedJobs.size();
+        for (JobStatus loaded : jobStatusSet.getAllJobs()) {
+            count++;
+            for (int i = 0; i < expectedJobs.size(); ++i) {
+                JobStatus expected = expectedJobs.valueAt(i);
 
-        // Reverse them so we know which comparison to make.
-        if (loaded1.getJobId() != 8) {
-            JobStatus tmp = loaded1;
-            loaded1 = loaded2;
-            loaded2 = tmp;
+                try {
+                    assertJobsEqual(expected, loaded);
+                    expectedJobs.remove(expected);
+                    break;
+                } catch (AssertionError e) {
+                    // Not equal. Move along.
+                }
+            }
         }
-
-        assertJobsEqual(taskStatus1, loaded1);
-        assertJobsEqual(taskStatus2, loaded2);
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
+        assertEquals("Loaded more jobs than expected", expectedCount, count);
+        if (expectedJobs.size() > 0) {
+            fail("Not all expected jobs were restored");
+        }
+        for (JobStatus jobStatus : jobStatuses) {
+            assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus));
+        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 0000000..c81fbb4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,781 @@
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileAppsInternal;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+    private static final String PACKAGE_ONE = "com.one";
+    private static final String FEATURE_ID = "feature.one";
+    private static final int PACKAGE_ONE_UID = 1111;
+    private static final ComponentName ACTIVITY_COMPONENT =
+            new ComponentName("com.one", "test");
+
+    private static final String PACKAGE_TWO = "com.two";
+    private static final int PACKAGE_TWO_UID = 2222;
+
+    private static final int PRIMARY_USER = 0;
+    private static final int PROFILE_OF_PRIMARY_USER = 10;
+    private static final int SECONDARY_USER = 11;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    private TestInjector mTestInjector;
+    private ActivityInfo mActivityInfo;
+    private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+    private IApplicationThread mIApplicationThread;
+
+    private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+    @Before
+    public void initCrossProfileAppsServiceImpl() {
+        mTestInjector = new TestInjector();
+        LocalServices.removeServiceForTest(CrossProfileAppsInternal.class);
+        mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Before
+    public void setupEnabledProfiles() {
+        mUserEnabled.put(PRIMARY_USER, true);
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+        mUserEnabled.put(SECONDARY_USER, true);
+
+        when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+                invocation -> {
+                    List<Integer> users = new ArrayList<>();
+                    final int targetUser = invocation.getArgument(0);
+                    users.add(targetUser);
+
+                    int profileUserId = -1;
+                    if (targetUser == PRIMARY_USER) {
+                        profileUserId = PROFILE_OF_PRIMARY_USER;
+                    } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+                        profileUserId = PRIMARY_USER;
+                    }
+
+                    if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+                        users.add(profileUserId);
+                    }
+                    return users.stream().mapToInt(i -> i).toArray();
+                });
+    }
+
+    @Before
+    public void setupCaller() {
+        mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+        mTestInjector.setCallingUserId(PRIMARY_USER);
+    }
+
+    @Before
+    public void setupPackage() throws Exception {
+        // PACKAGE_ONE are installed in all users.
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+        // Packages are resolved to their corresponding UID.
+        doAnswer(invocation -> {
+            final int uid = invocation.getArgument(0);
+            final String packageName = invocation.getArgument(1);
+            if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+                return null;
+            } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+                return null;
+            }
+            throw new SecurityException("Not matching");
+        }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+        // The intent is resolved to the ACTIVITY_COMPONENT.
+        mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+        mTestInjector.setCallingUserId(SECONDARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void getTargetUserProfiles_fakeCaller() throws Exception {
+        mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+    }
+
+    @Test
+    public void startActivityAsUser_currentUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_currentUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notInstalled() {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_fakeCaller() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_fakeCaller() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notExported() throws Exception {
+        mActivityInfo.exported = false;
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notExported() {
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+                    .thenReturn(new PermissionInfo());
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+        mActivityInfo.exported = false;
+
+
+        // There's a bug in static mocking if the APK is large - so here is the next best thing...
+        doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+                .getSystemServiceName(PermissionCheckerManager.class);
+        PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+                .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+                        AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+                        anyBoolean(), anyInt());
+        doReturn(permissionCheckerManager).when(mContext).getSystemService(
+                Context.PERMISSION_CHECKER_SERVICE);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_anotherPackage() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_anotherPackage() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_secondaryUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_secondaryUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                /* targetTask */ null,
+                /* options */ null);
+
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        eq(PRIMARY_USER));
+    }
+
+    @Test
+    public void startActivityAsUser_sameTask_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+        Binder targetTask = new Binder();
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                targetTask,
+                options);
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        eq(targetTask),
+                        anyInt(),
+                        eq(options),
+                        eq(PRIMARY_USER));
+    }
+
+    private void mockAppsInstalled(String packageName, int user, boolean installed) {
+        when(mPackageManagerInternal.getPackageInfo(
+                eq(packageName),
+                anyLong(),
+                anyInt(),
+                eq(user)))
+                .thenReturn(installed ? createInstalledPackageInfo() : null);
+    }
+
+    private PackageInfo createInstalledPackageInfo() {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.enabled = true;
+        return packageInfo;
+    }
+
+    private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = componentName.getPackageName();
+        activityInfo.name = componentName.getClassName();
+        activityInfo.exported = true;
+        resolveInfo.activityInfo = activityInfo;
+        mActivityInfo = activityInfo;
+
+        when(mPackageManagerInternal.queryIntentActivities(
+                any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(resolveInfo));
+    }
+
+    private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+        private int mCallingUid;
+        private int mCallingUserId;
+        private int mCallingPid;
+
+        public void setCallingUid(int uid) {
+            mCallingUid = uid;
+        }
+
+        public void setCallingPid(int pid) {
+            mCallingPid = pid;
+        }
+
+        public void setCallingUserId(int userId) {
+            mCallingUserId = userId;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return mCallingUid;
+        }
+
+        @Override
+        public int getCallingPid() {
+            return mCallingPid;
+        }
+
+        @Override
+        public int getCallingUserId() {
+            return mCallingUserId;
+        }
+
+        @Override
+        public UserHandle getCallingUserHandle() {
+            return UserHandle.of(mCallingUserId);
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return 0;
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {
+        }
+
+        @Override
+        public void withCleanCallingIdentity(ThrowingRunnable action) {
+            action.run();
+        }
+
+        @Override
+        public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+            return action.get();
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        @Override
+        public ActivityManagerInternal getActivityManagerInternal() {
+            return mActivityManagerInternal;
+        }
+
+        @Override
+        public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+            return mActivityTaskManagerInternal;
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return mDevicePolicyManagerInternal;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+        }
+
+        @Override
+        public void killUid(int uid) {
+            PermissionManagerService.killUid(
+                    UserHandle.getAppId(uid),
+                    UserHandle.getUserId(uid),
+                    PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 1815e2a..7c1962c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.annotation.XmlRes;
 import android.content.Context;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -50,6 +51,7 @@
                     .powerProfileModeledOnly()
                     .includePowerModels()
                     .build();
+    private final Context mContext;
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
@@ -67,14 +69,19 @@
     }
 
     public BatteryUsageStatsRule(long currentTime, File historyDir) {
-        Context context = InstrumentationRegistry.getContext();
-        mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+        mContext = InstrumentationRegistry.getContext();
+        mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
         mMockClock.currentTime = currentTime;
         mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
         mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.onSystemReady();
     }
 
+    public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
+        mPowerProfile.forceInitForTesting(mContext, xmlId);
+        return this;
+    }
+
     public BatteryUsageStatsRule setAveragePower(String key, double value) {
         when(mPowerProfile.getAveragePower(key)).thenReturn(value);
         when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 8262fca..5bc73bd 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -34,6 +35,8 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -43,7 +46,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.PowerProfile;
+import com.android.frameworks.servicestests.R;
 
 import com.google.common.collect.Range;
 
@@ -52,28 +55,25 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class MobileRadioPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
     @Mock
     NetworkStatsManager mNetworkStatsManager;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
-            .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
-                    new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
-            .initMeasuredEnergyStatsLocked();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
 
     @Test
     public void testCounterBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -85,9 +85,15 @@
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -97,10 +103,31 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
@@ -108,34 +135,331 @@
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
 
-        mStatsRule.setTime(12_000, 12_000);
+        mStatsRule.setTime(10_000, 10_000);
 
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(2.2444);
+                .isWithin(PRECISION).of(1.27888);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
+                .isWithin(PRECISION).of(0.94);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_multipleDefinedRat() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{10, 11, 12, 13, 14}, 15);
+        ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{20, 21, 22, 23, 24}, 25);
+        ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                new int[]{30, 31, 32, 33, 34}, 35);
+        ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                new int[]{40, 41, 42, 43, 44}, 45);
+        ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                new int[]{50, 51, 52, 53, 54}, 55);
+        ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                new int[]{60, 61, 62, 63, 64}, 65);
+
+        ActivityStatsTechSpecificInfo[] ratInfos =
+                new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+                        nrHighFreqInfo, nrMmwaveFreqInfo};
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+        // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+        // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+        // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+        // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+        // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+        // _________________
+        // =    3957753 mA-ms or 1.09938 mA-h active consumption
+        //
+        // Idle drain rate * idle duration
+        //   360 mA * 3000 ms = 1080000 mA-ms
+        // Sleep drain rate * sleep duration
+        //   70 mA * 2000 ms = 140000 mA-ms
+        // _________________
+        // =    5177753 mA-ms or 1.43826 mA-h total consumption
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.43826);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.09938);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.82453);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.27484);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_legacyPowerProfile() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[]{100, 200, 300, 400, 500}, 600);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.27888);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.94);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
     public void testTimerBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -148,13 +472,19 @@
                 TelephonyManager.SIM_STATE_READY,
                 2000, 2000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Found a cell
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -164,10 +494,25 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
@@ -177,7 +522,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
                 mNetworkStatsManager);
@@ -199,6 +544,8 @@
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
+        mStatsRule.setTime(12_000, 12_000);
+
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
                 .powerProfileModeledOnly()
                 .includePowerModels()
@@ -217,6 +564,10 @@
                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
 
+
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.6);
+
         assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
         assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
         assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
@@ -224,6 +575,8 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -264,12 +617,6 @@
 
         mStatsRule.apply(calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(1.53934);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
@@ -282,10 +629,18 @@
                 .isWithin(PRECISION).of(1.53934);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.53934);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     @Test
     public void testMeasuredEnergyBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -317,7 +672,7 @@
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
@@ -326,7 +681,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index a03a1b4..ebcb498 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -234,6 +234,37 @@
     }
 
     @Test
+    public void testScheduleRepostsForLongTagPersistedNotification() throws Exception {
+        String longTag = "A".repeat(66000);
+        NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+        mSnoozeHelper.snooze(r, 0);
+
+        // We store the full key in temp storage.
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mSnoozeHelper.writeXml(serializer);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+        mSnoozeHelper.readXml(parser, 4);
+
+        mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+        // We trim the key in persistent storage.
+        verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+    }
+
+    @Test
     public void testSnoozeForTime() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 85c4975..4e001fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -106,7 +106,7 @@
         mTransition.mParticipants.add(mTopActivity);
         mTopActivity.mTransitionController.moveToCollecting(mTransition);
         // becomes invisible when covered by mTopActivity
-        mTrampolineActivity.mVisibleRequested = false;
+        mTrampolineActivity.setVisibleRequested(false);
     }
 
     private <T> T verifyAsync(T mock) {
@@ -235,7 +235,7 @@
     public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched(mTopActivity);
 
-        mTopActivity.mVisibleRequested = true;
+        mTopActivity.setVisibleRequested(true);
         doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
@@ -258,7 +258,7 @@
         notifyActivityLaunching(noDrawnActivity.intent);
         notifyAndVerifyActivityLaunched(noDrawnActivity);
 
-        noDrawnActivity.mVisibleRequested = false;
+        noDrawnActivity.setVisibleRequested(false);
         mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
 
         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
@@ -286,7 +286,7 @@
 
         clearInvocations(mLaunchObserver);
         mLaunchTopByTrampoline = true;
-        mTopActivity.mVisibleRequested = false;
+        mTopActivity.setVisibleRequested(false);
         notifyActivityLaunching(mTopActivity.intent);
         // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
         // the launch event is still valid.
@@ -314,7 +314,7 @@
         // Create an invisible event that should be cancelled after the next event starts.
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
         onActivityLaunched(prev);
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
 
         mActivityOptions = ActivityOptions.makeBasic();
         mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
@@ -561,7 +561,7 @@
     @Test
     public void testConsecutiveLaunchWithDifferentWindowingMode() {
         mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
-        mTrampolineActivity.mVisibleRequested = true;
+        mTrampolineActivity.setVisibleRequested(true);
         onActivityLaunched(mTrampolineActivity);
         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index a410eed..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -917,7 +917,7 @@
         // Prepare the activity record to be ready for immediate removal. It should be invisible and
         // have no process. Otherwise, request to finish it will send a message to client first.
         activity.setState(STOPPED, "test");
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         // Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
         // this will cause NPE when updating task's process.
@@ -927,7 +927,7 @@
         // next activity reports idle to destroy it.
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "test");
 
@@ -1082,7 +1082,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1099,7 +1099,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(PAUSED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1118,7 +1118,7 @@
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
         new ActivityBuilder(mAtm).build();
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         activity.finishIfPossible("test", false /* oomAdj */);
@@ -1136,7 +1136,7 @@
         final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final ActivityRecord activity = createActivityWithTask();
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1273,7 +1273,7 @@
         final ActivityRecord currentTop = createActivityWithTask();
         final Task task = currentTop.getTask();
 
-        currentTop.mVisibleRequested = currentTop.nowVisible = true;
+        currentTop.setVisibleRequested(currentTop.nowVisible = true);
 
         // Simulates that {@code currentTop} starts an existing activity from background (so its
         // state is stopped) and the starting flow just goes to place it at top.
@@ -1300,7 +1300,7 @@
         final ActivityRecord bottomActivity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(bottomActivity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         // simulating bottomActivity as a trampoline activity.
         bottomActivity.setState(RESUMED, "test");
         bottomActivity.finishIfPossible("test", false);
@@ -1316,13 +1316,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as not visible, so that we will wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1346,13 +1346,13 @@
         final ActivityRecord topActivity = createActivityWithTask();
         mDisplayContent.setIsSleeping(true);
         doReturn(true).when(activity).shouldBeVisible();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
 
         // Mark the activity behind (on a separate task) as not visible
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1370,13 +1370,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as not visible, so that we would wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1394,12 +1394,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1417,12 +1417,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1440,12 +1440,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1454,7 +1454,7 @@
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord focusedActivity = stack.getTopMostActivity();
         focusedActivity.nowVisible = true;
-        focusedActivity.mVisibleRequested = true;
+        focusedActivity.setVisibleRequested(true);
         focusedActivity.setState(RESUMED, "test");
         stack.setResumedActivity(focusedActivity, "test");
 
@@ -1476,7 +1476,7 @@
         int displayId = activity.getDisplayId();
         doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -1515,12 +1515,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        firstActivity.mVisibleRequested = false;
+        firstActivity.setVisibleRequested(false);
         firstActivity.nowVisible = false;
         firstActivity.setState(STOPPED, "test");
 
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        secondActivity.mVisibleRequested = true;
+        secondActivity.setVisibleRequested(true);
         secondActivity.nowVisible = true;
         secondActivity.setState(secondActivityState, "test");
 
@@ -1530,7 +1530,7 @@
         } else {
             translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
         }
-        translucentActivity.mVisibleRequested = true;
+        translucentActivity.setVisibleRequested(true);
         translucentActivity.nowVisible = true;
         translucentActivity.setState(RESUMED, "test");
 
@@ -1546,7 +1546,7 @@
 
         // Finish the first activity
         firstActivity.finishing = true;
-        firstActivity.mVisibleRequested = true;
+        firstActivity.setVisibleRequested(true);
         firstActivity.completeFinishing("test");
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
                 0 /* configChanges */ , false /* preserveWindows */,
@@ -1614,7 +1614,7 @@
         }, true /* traverseTopToBottom */);
         activity.setState(STARTED, "test");
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         // Try to finish the last activity above the home stack.
         activity.completeFinishing("test");
@@ -1909,7 +1909,7 @@
         // Simulate that the activity requests the same orientation as display.
         activity.setOrientation(display.getConfiguration().orientation);
         // Skip the real freezing.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         clearInvocations(activity);
         activity.onCancelFixedRotationTransform(originalRotation);
         // The implementation of cancellation must be executed.
@@ -2535,7 +2535,7 @@
 
         activity.setOccludesParent(true);
         activity.setVisible(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         // Can not specify orientation if app isn't visible even though it occludes parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
@@ -2910,7 +2910,7 @@
         task.addChild(taskFragment2, POSITION_TOP);
         final ActivityRecord activity2 = new ActivityBuilder(mAtm)
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         taskFragment2.addChild(activity2);
         assertTrue(activity2.isResizeable());
         activity1.reparent(taskFragment1, POSITION_TOP);
@@ -3055,7 +3055,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3064,7 +3064,7 @@
         // until we verify no logic relies on this behavior, we'll keep this as is.
         activity.setVisibility(true);
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3075,7 +3075,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3083,7 +3083,7 @@
         // animation should be applied on this activity.
         activity.setVisibility(false);
         assertTrue(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3095,7 +3095,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3103,7 +3103,7 @@
         // animation should be applied on this activity.
         activity.setVisibility(true);
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3126,7 +3126,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3134,7 +3134,7 @@
         // transition should be applied on this activity.
         activity.setVisibility(false);
         assertFalse(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3549,12 +3549,12 @@
         activity.reparent(taskFragment, POSITION_TOP);
 
         // Ensure the activity visibility is updated even it is not shown to current user.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(false).when(activity).showToCurrentUser();
         spyOn(taskFragment);
         doReturn(false).when(taskFragment).shouldBeVisible(any());
         display.ensureActivitiesVisible(null, 0, false, false);
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
     }
 
     @Test
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 0743ef4..ee31748 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -514,7 +514,9 @@
                 .setCreateActivity(true)
                 .build()
                 .getTopMostActivity();
-        splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
+
+        splitPrimaryActivity.setVisibleRequested(true);
+        splitSecondActivity.setVisibleRequested(true);
 
         assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
         assertEquals(splitOrg.mSecondary, splitSecondActivity.getRootTask());
@@ -527,7 +529,7 @@
                 .setCreateActivity(true).build().getTopMostActivity();
         final ActivityRecord translucentActivity = new TaskBuilder(mSupervisor)
                 .setCreateActivity(true).build().getTopMostActivity();
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                 false /* mockGetRootTask */);
@@ -1050,7 +1052,7 @@
                         ACTIVITY_TYPE_STANDARD, false /* onTop */));
         // Activity should start invisible since we are bringing it to front.
         singleTaskActivity.setVisible(false);
-        singleTaskActivity.mVisibleRequested = false;
+        singleTaskActivity.setVisibleRequested(false);
 
         // Create another activity on top of the secondary display.
         final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -1268,7 +1270,7 @@
         final ActivityStarter starter = prepareStarter(0 /* flags */);
         final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
         starter.mStartActivity = target;
-        target.mVisibleRequested = false;
+        target.setVisibleRequested(false);
         target.setTurnScreenOn(true);
         // Assume the flag was consumed by relayout.
         target.setCurrentLaunchCanTurnScreenOn(false);
@@ -1589,10 +1591,10 @@
         final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
 
         activityBot.setVisible(false);
-        activityBot.mVisibleRequested = false;
+        activityBot.setVisibleRequested(false);
 
         assertTrue(activityTop.isVisible());
-        assertTrue(activityTop.mVisibleRequested);
+        assertTrue(activityTop.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
                         | FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2fccd64..368b750 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -344,7 +344,7 @@
 
         // Assume the activity is finishing and hidden because it was crashed.
         activity.finishing = true;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setVisible(false);
         activity.getTask().setPausingActivity(activity);
         homeActivity.setState(PAUSED, "test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index fb94147c..c73237e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -122,7 +122,7 @@
         final ActivityRecord top = createActivityRecord(task);
         top.setState(ActivityRecord.State.RESUMED, "test");
         behind.setState(ActivityRecord.State.STARTED, "test");
-        behind.mVisibleRequested = true;
+        behind.setVisibleRequested(true);
 
         task.removeActivities("test", false /* excludingTaskOverlay */);
         assertFalse(mDisplayContent.mAppTransition.isReady());
@@ -294,7 +294,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -319,12 +319,12 @@
         //                   +- [Task2] - [ActivityRecord2] (opening, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.mRequestForceTransition = true;
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         activity2.mRequestForceTransition = true;
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -391,7 +391,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         attrs.setTitle("AppWindow2");
         final TestWindowState appWindow2 = createWindowState(attrs, activity2);
         appWindow2.mWillReplaceWindow = true;
@@ -424,17 +424,17 @@
         //                               +- [ActivityRecord4] (invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
                 activity3.getTask());
         activity4.setVisible(false);
-        activity4.mVisibleRequested = false;
+        activity4.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -459,7 +459,7 @@
         //                            +- [ActivityRecord2] (closing, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
 
@@ -490,7 +490,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
@@ -528,13 +528,13 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         activity3.setOccludesParent(false);
@@ -567,7 +567,7 @@
         final Task parentTask = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -600,10 +600,10 @@
         splitRoot1.setAdjacentTaskFragment(splitRoot2);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -625,12 +625,12 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -654,11 +654,11 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -683,11 +683,11 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity2);
@@ -710,13 +710,13 @@
         //                   +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final Task task2 = createTask(mDisplayContent);
         task2.mRemoveWithTaskOrganizer = true;
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -744,7 +744,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(task);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -1260,6 +1260,8 @@
     @Test
     public void testTransitionGoodToGoForTaskFragments_detachedApp() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
         final Task task = createTask(mDisplayContent);
         final TaskFragment changeTaskFragment =
                 createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 437eeb1..6b814e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -385,11 +385,11 @@
         // Simulate activity1 launches activity2.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.allDrawn = true;
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.allDrawn = true;
 
         dc.mClosingApps.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d151188..bc23fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -590,7 +590,7 @@
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Make sure top focused display not changed if there is a focused app.
-        window1.mActivityRecord.mVisibleRequested = false;
+        window1.mActivityRecord.setVisibleRequested(false);
         window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
         updateFocusedWindow();
         assertTrue(!window1.isFocused());
@@ -1106,7 +1106,7 @@
     public void testOrientationBehind() {
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
         final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
         assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index db1d15a..ba68a25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -243,7 +243,7 @@
         }
 
         @Override
-        public boolean canShowTasksInRecents() {
+        public boolean canShowTasksInHostDeviceRecents() {
             return true;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index ac2df62..6f2e3f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -819,7 +819,7 @@
     @Test
     public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
         final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        doReturn(false).when(displayContent).canShowTasksInRecents();
+        doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
         final Task task = displayContent.getDefaultTaskDisplayArea().createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
         mRecentTasks.add(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58ea..08635ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -172,12 +172,12 @@
         // executed.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
 
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
                 mDefaultDisplay.getRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a2b4cb8..de3a526 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -111,7 +111,7 @@
         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                 mRecentsComponent, true /* getRecentsAnimation */);
         // The launch-behind state should make the recents activity visible.
-        assertTrue(recentActivity.mVisibleRequested);
+        assertTrue(recentActivity.isVisibleRequested());
         assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
                 mAtm.mDemoteTopAppReasons);
         assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
@@ -119,7 +119,7 @@
         // Simulate the animation is cancelled without changing the stack order.
         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
         // The non-top recents activity should be invisible by the restored launch-behind state.
-        assertFalse(recentActivity.mVisibleRequested);
+        assertFalse(recentActivity.isVisibleRequested());
         assertEquals(0, mAtm.mDemoteTopAppReasons);
     }
 
@@ -164,7 +164,7 @@
         // The activity is started in background so it should be invisible and will be stopped.
         assertThat(recentsActivity).isNotNull();
         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
-        assertFalse(recentsActivity.mVisibleRequested);
+        assertFalse(recentsActivity.isVisibleRequested());
 
         // Assume it is stopped to test next use case.
         recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
@@ -360,7 +360,7 @@
                 true);
 
         // Ensure we find the task for the right user and it is made visible
-        assertTrue(otherUserHomeActivity.mVisibleRequested);
+        assertTrue(otherUserHomeActivity.isVisibleRequested());
     }
 
     private void startRecentsActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 881cc5f..fb29d3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1068,7 +1068,7 @@
         activity.app = null;
         overlayActivity.app = null;
         // Simulate the process is dead
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(DESTROYED, "Test");
 
         assertEquals(2, task.getChildCount());
@@ -1205,7 +1205,7 @@
 
         // There is still an activity1 in rootTask1 so the activity2 should be added to finishing
         // list that will be destroyed until idle.
-        rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+        rootTask2.getTopNonFinishingActivity().setVisibleRequested(true);
         final ActivityRecord activity2 = finishTopActivity(rootTask2);
         assertEquals(STOPPING, activity2.getState());
         assertThat(mSupervisor.mStoppingActivities).contains(activity2);
@@ -1410,7 +1410,7 @@
         new ActivityBuilder(mAtm).setTask(task).build();
         // The scenario we are testing is when the app isn't visible yet.
         nonTopVisibleActivity.setVisible(false);
-        nonTopVisibleActivity.mVisibleRequested = false;
+        nonTopVisibleActivity.setVisibleRequested(false);
         doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
         doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f84865b..a17e124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -174,7 +174,7 @@
         final Task rootTask = new TaskBuilder(mSupervisor).build();
         final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task1).build();
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         assertEquals(1, task1.mLayerRank);
@@ -183,7 +183,7 @@
 
         final Task task2 = new TaskBuilder(mSupervisor).build();
         final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         // Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
@@ -200,8 +200,8 @@
         assertEquals(2, task2.mLayerRank);
 
         // The rank should be updated to invisible when device went to sleep.
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(false);
         doReturn(true).when(mAtm).isSleepingOrShuttingDownLocked();
         doReturn(true).when(mRootWindowContainer).putTasksToSleep(anyBoolean(), anyBoolean());
         mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e65610f..13ea99a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -169,7 +169,7 @@
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.setSavedState(null /* savedState */);
         mActivity.setState(RESUMED, "testRestart");
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -553,7 +553,7 @@
         resizeDisplay(display, 900, 1800);
 
         mActivity.setState(STOPPED, "testSizeCompatMode");
-        mActivity.mVisibleRequested = false;
+        mActivity.setVisibleRequested(false);
         mActivity.visibleIgnoringKeyguard = false;
         mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
         mActivity.app.computeProcessActivityState();
@@ -605,7 +605,7 @@
         // Make the activity resizable again by restarting it
         clearInvocations(mTask);
         mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
         mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
@@ -3188,7 +3188,7 @@
             task.mResizeMode = activity.info.resizeMode;
             task.getRootActivity().info.resizeMode = activity.info.resizeMode;
         }
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         if (maxAspect >= 0) {
             activity.info.setMaxAspectRatio(maxAspect);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d5fb1a8..91f8d8d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -398,7 +398,7 @@
                     .setParentTask(rootHomeTask).setCreateTask(true).build();
         }
         homeActivity.setVisible(false);
-        homeActivity.mVisibleRequested = true;
+        homeActivity.setVisibleRequested(true);
         assertFalse(rootHomeTask.isVisible());
 
         assertEquals(defaultTaskDisplayArea.getOrientation(), rootHomeTask.getOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2b49314..834302e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -370,7 +370,8 @@
         mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
-        assertTaskFragmentParentInfoChangedTransaction(task);
+        // There will not be TaskFragmentParentInfoChanged because Task visible request is changed
+        // before the organized TaskFragment is added to the Task.
         assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
     }
 
@@ -1159,6 +1160,7 @@
         doReturn(false).when(task).shouldBeVisible(any());
 
         // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+        clearInvocations(mOrganizer);
         mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
         verify(mOrganizer).onTransactionReady(any());
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 59a31b1..aaf07b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -135,8 +135,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -199,9 +199,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
-        opening2.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
+        opening2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -250,8 +250,8 @@
         fillChangeMap(changes, tda);
 
         // End states.
-        showing.mVisibleRequested = true;
-        showing2.mVisibleRequested = true;
+        showing.setVisibleRequested(true);
+        showing2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -286,16 +286,16 @@
 
         final Task openTask = createTask(mDisplayContent);
         final ActivityRecord opening = createActivityRecord(openTask);
-        opening.mVisibleRequested = false; // starts invisible
+        opening.setVisibleRequested(false); // starts invisible
         final Task closeTask = createTask(mDisplayContent);
         final ActivityRecord closing = createActivityRecord(closeTask);
-        closing.mVisibleRequested = true; // starts visible
+        closing.setVisibleRequested(true); // starts visible
 
         transition.collectExistenceChange(openTask);
         transition.collect(opening);
         transition.collect(closing);
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
@@ -323,7 +323,7 @@
                     WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
         }
 
         // doesn't matter which order collected since participants is a set
@@ -331,7 +331,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -360,7 +360,7 @@
                     WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
             act.visibleIgnoringKeyguard = (i % 2) == 0;
             if (i == showWallpaperTask) {
                 doReturn(true).when(act).showWallpaper();
@@ -381,7 +381,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -417,9 +417,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
-        hiding.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
+        hiding.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(hiding);
@@ -449,9 +449,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(showing);
@@ -531,19 +531,19 @@
     @Test
     public void testOpenActivityInTheSameTaskWithDisplayChange() {
         final ActivityRecord closing = createActivityRecord(mDisplayContent);
-        closing.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
         final Task task = closing.getTask();
         makeTaskOrganized(task);
         final ActivityRecord opening = createActivityRecord(task);
-        opening.mVisibleRequested = false;
+        opening.setVisibleRequested(false);
         makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
         final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         for (WindowContainer<?> wc : wcs) {
             transition.collect(wc);
         }
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
         final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
         for (WindowContainer<?> wc : wcs) {
             wc.getWindowConfiguration().setRotation(newRotation);
@@ -586,9 +586,9 @@
         changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, openTask);
         // End states.
-        changeInChange.mVisibleRequested = true;
-        openInOpen.mVisibleRequested = true;
-        openInChange.mVisibleRequested = true;
+        changeInChange.setVisibleRequested(true);
+        openInOpen.setVisibleRequested(true);
+        openInChange.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -644,8 +644,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -685,8 +685,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -962,7 +962,7 @@
         home.mTransitionController.requestStartTransition(transition, home.getTask(),
                 null /* remoteTransition */, null /* displayChange */);
         transition.collectExistenceChange(home);
-        home.mVisibleRequested = true;
+        home.setVisibleRequested(true);
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
         doReturn(true).when(home).hasFixedRotationTransform(any());
         player.startTransition();
@@ -998,12 +998,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task1);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1011,9 +1011,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1029,8 +1029,8 @@
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
 
         openTransition.finishTransition();
 
@@ -1072,12 +1072,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task2);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1085,9 +1085,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1107,8 +1107,8 @@
         closeTransition.collectExistenceChange(activity2);
         closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         // Using abort to force-finish the sync (since we obviously can't wait for drawing).
@@ -1166,8 +1166,8 @@
         changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
         // End states.
-        activity0.mVisibleRequested = false;
-        activity1.mVisibleRequested = true;
+        activity0.setVisibleRequested(false);
+        activity1.setVisibleRequested(true);
 
         participants.add(activity0);
         participants.add(activity1);
@@ -1210,9 +1210,9 @@
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
                 false /* exChg */));
         // End states.
-        closingActivity.mVisibleRequested = false;
-        openingActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        closingActivity.setVisibleRequested(false);
+        openingActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
 
         participants.add(closingActivity);
         participants.add(openingActivity);
@@ -1255,8 +1255,8 @@
                 false /* exChg */));
         changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         // End states.
-        nonEmbeddedActivity.mVisibleRequested = false;
-        embeddedActivity.mVisibleRequested = true;
+        nonEmbeddedActivity.setVisibleRequested(false);
+        embeddedActivity.setVisibleRequested(true);
         embeddedTf.setBounds(new Rect(0, 0, 500, 500));
 
         participants.add(nonEmbeddedActivity);
@@ -1285,11 +1285,11 @@
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: reset bounds to fill Task.
         activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1313,11 +1313,11 @@
         task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: fills Task without override.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1340,12 +1340,12 @@
         final Task lastParent = createTask(mDisplayContent);
         final Task newParent = createTask(mDisplayContent);
         final ActivityRecord activity = createActivityRecord(lastParent);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         changes.put(activity, new Transition.ChangeInfo(activity));
         activity.reparent(newParent, POSITION_TOP);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1365,7 +1365,7 @@
         final Task task = createTask(mDisplayContent);
         task.setBounds(new Rect(0, 0, 2000, 1000));
         final ActivityRecord activity = createActivityRecord(task);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
@@ -1413,13 +1413,13 @@
         task.setTaskDescription(taskDescription);
 
         // Start states:
-        embeddedActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        embeddedActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
         changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
         // End states:
-        embeddedActivity.mVisibleRequested = false;
-        nonEmbeddedActivity.mVisibleRequested = true;
+        embeddedActivity.setVisibleRequested(false);
+        nonEmbeddedActivity.setVisibleRequested(true);
 
         participants.add(embeddedTf);
         participants.add(nonEmbeddedActivity);
@@ -1532,7 +1532,7 @@
         final ActivityRecord activity = createActivityRecord(lastParent);
         doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
         doNothing().when(activity).setDropInputMode(anyInt());
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                 activity.mTransitionController, mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 45e1141..2fccb88a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -95,7 +95,7 @@
         final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setVisibility(false, false);
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 94b5b93..a100b9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -313,12 +313,12 @@
         r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
                 mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
         // Invisible requested activity should not share its rotation transform.
-        r.mVisibleRequested = false;
+        r.setVisibleRequested(false);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertFalse(wallpaperToken.hasFixedRotationTransform());
 
         // Wallpaper should link the transform of its target.
-        r.mVisibleRequested = true;
+        r.setVisibleRequested(true);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
         assertTrue(r.hasFixedRotationTransform());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 871030f..3d777f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -205,7 +205,7 @@
         win.mViewVisibility = View.VISIBLE;
         win.mHasSurface = true;
         win.mActivityRecord.mAppStopped = true;
-        win.mActivityRecord.mVisibleRequested = false;
+        win.mActivityRecord.setVisibleRequested(false);
         win.mActivityRecord.setVisible(false);
         mWm.mWindowMap.put(win.mClient.asBinder(), win);
         final int w = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index c73e237..e5e9f54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -992,7 +992,7 @@
         final Task task = createTask(rootTaskController1);
         final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
 
-        w.mActivityRecord.mVisibleRequested = true;
+        w.mActivityRecord.setVisibleRequested(true);
         w.mActivityRecord.setVisible(true);
 
         BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 3abf7ce..8bd4148 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -324,7 +324,7 @@
     @Test
     public void testComputeOomAdjFromActivities() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         final int[] callbackResult = { 0 };
         final int visible = 1;
         final int paused = 2;
@@ -359,7 +359,7 @@
         assertEquals(visible, callbackResult[0]);
 
         callbackResult[0] = 0;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(PAUSED, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(paused, callbackResult[0]);
@@ -380,7 +380,7 @@
         final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
         spyOn(tracker);
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(STARTED, "test");
 
         verify(tracker).onAnyActivityVisible(mWpc);
@@ -398,7 +398,7 @@
         assertTrue(mWpc.hasForegroundActivities());
 
         activity.setVisibility(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         verify(tracker).onAllActivitiesInvisible(mWpc);
@@ -413,7 +413,7 @@
     @Test
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
@@ -423,7 +423,7 @@
     @Test
     public void testTopActivityUiModeChangeForDifferentPackage_noScheduledConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
         verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1b79dd3..435c39c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -264,7 +264,7 @@
 
         // Verify that app window can still be IME target as long as it is visible (even if
         // it is going to become invisible).
-        appWindow.mActivityRecord.mVisibleRequested = false;
+        appWindow.mActivityRecord.setVisibleRequested(false);
         assertTrue(appWindow.canBeImeTarget());
 
         // Make windows invisible
@@ -720,7 +720,7 @@
 
         // No need to wait for a window of invisible activity even if the window has surface.
         final WindowState invisibleApp = mAppWindow;
-        invisibleApp.mActivityRecord.mVisibleRequested = false;
+        invisibleApp.mActivityRecord.setVisibleRequested(false);
         invisibleApp.mActivityRecord.allDrawn = false;
         outWaitingForDrawn.clear();
         invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
@@ -738,7 +738,7 @@
         assertFalse(startingApp.getOrientationChanging());
 
         // Even if the display is frozen, invisible requested window should not be affected.
-        startingApp.mActivityRecord.mVisibleRequested = false;
+        startingApp.mActivityRecord.setVisibleRequested(false);
         mWm.startFreezingDisplay(0, 0, mDisplayContent);
         doReturn(true).when(mWm.mPolicy).isScreenOn();
         startingApp.getWindowFrames().setInsetsChanged(true);
@@ -813,7 +813,7 @@
         final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
                 "App window");
         doReturn(true).when(embeddedActivity).isVisible();
-        embeddedActivity.mVisibleRequested = true;
+        embeddedActivity.setVisibleRequested(true);
         makeWindowVisible(win);
         win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
         // Set the bounds twice:
@@ -838,7 +838,7 @@
     @Test
     public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
-        win0.mActivityRecord.mVisibleRequested = false;
+        win0.mActivityRecord.setVisibleRequested(false);
         assertFalse(win0.canReceiveTouchInput());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5a261bc65..019b14d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -738,7 +738,7 @@
         activity.onDisplayChanged(dc);
         activity.setOccludesParent(true);
         activity.setVisible(true);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
     }
 
     /**
@@ -1240,7 +1240,7 @@
                     mTask.moveToFront("createActivity");
                 }
                 if (mVisible) {
-                    activity.mVisibleRequested = true;
+                    activity.setVisibleRequested(true);
                     activity.setVisible(true);
                 }
             }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4fd2b78..b3a1f2b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1864,6 +1864,21 @@
                         mResponseStatsTracker.dump(idpw);
                     }
                     return;
+                } else if ("app-component-usage".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
+                        if (!mLastTimeComponentUsedGlobal.isEmpty()) {
+                            ipw.println("App Component Usages:");
+                            ipw.increaseIndent();
+                            for (String pkg : mLastTimeComponentUsedGlobal.keySet()) {
+                                ipw.println("package=" + pkg
+                                            + " lastUsed=" + UserUsageStatsService.formatDateTime(
+                                                    mLastTimeComponentUsedGlobal.get(pkg), true));
+                            }
+                            ipw.decreaseIndent();
+                        }
+                    }
+                    return;
                 } else if (arg != null && !arg.startsWith("-")) {
                     // Anything else that doesn't start with '-' is a pkg to filter
                     pkgs.add(arg);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6be2f77..7c600b1 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,6 +138,12 @@
      */
     public static final int FREQUENCY_RANGE_MMWAVE = 4;
 
+    /**
+     * Number of frequency ranges.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_COUNT = 5;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "DUPLEX_MODE_",